Skip to content

Tool System

The ToolRegistry (src/tools/registry.ts) manages tool registration and lookup:

const registry = new ToolRegistry();
registry.register(bashTool); // Register a builtin tool
registry.registerMCP(server); // Register an MCP server
registry.find('bash'); // Lookup by name
registry.getEntries(); // All registered tools
registry.getMCPServers(); // All MCP servers
registry.scopedView({ deniedTools: ['bash'] }); // Filtered view for role-based scoping

Returns a filtered copy of registered tools based on allowedTools (whitelist) and/or deniedTools (blacklist). Used by spawn_agent to enforce role-based tool restrictions on child agents.

Every tool implements the ToolEntry interface:

interface ToolEntry<TInput = unknown> {
definition: BetaTool; // Name, description, input_schema
handler: ToolHandler<TInput>; // (input, agent) => Promise<string>
}

The handler receives the parsed input and a reference to the calling IAgent (for accessing memory, promptUser, etc.).

PropertyValue
Sourcesrc/tools/builtin/bash.ts
Timeout120s (configurable via timeout_ms)
Max buffer10MB
Eager streamingYes

Executes shell commands via execSync. Returns stdout on success, combined stdout+stderr on failure. Uses an env var allowlist — only safe prefixes (PATH, HOME, NODE_, GIT_, etc.) are passed to subprocesses. All secrets and API keys are stripped. See Security.

Isolation env filtering (setIsolationEnv()): Available as an extension point for Pro. When a tenant context is active (via nodyn-pro), the env passed to subprocesses is further restricted based on isolation level. Air-gapped tenants receive a minimal env (PATH, HOME, TMPDIR only). Sandboxed tenants can inject custom env vars via IsolationConfig.envVars. Shared and scoped levels use the default allowlist.

PropertyValue
Sourcesrc/tools/builtin/fs.ts
Inputpath (absolute)
Eager streamingYes

Reads file contents as UTF-8.

PropertyValue
Sourcesrc/tools/builtin/fs.ts
Inputpath, content
Eager streamingYes

Writes content to a file. Creates parent directories as needed. Resolves symlinks before writing to prevent symlink traversal attacks.

PropertyValue
Sourcesrc/tools/builtin/memory.ts
Inputnamespace (knowledge/methods/project-state/learnings), content
Eager streamingYes

Appends content to a knowledge namespace. Deduplicates — skips if content already exists.

PropertyValue
Sourcesrc/tools/builtin/memory.ts
Inputnamespace
Eager streamingYes

Returns the contents of a knowledge namespace.

PropertyValue
Sourcesrc/tools/builtin/spawn.ts
Inputagents array of SpawnSpec
Eager streamingYes
Timeout10 minutes per agent

Creates a new Agent instance per sub-agent. Supports role-based spawning — set role to a role ID (e.g. researcher, creator, operator, collector) to apply the role’s model, system prompt, and tool restrictions automatically.

SpawnSpec fields:

FieldTypeDescription
namestring (required)Agent display name
taskstring (required)Task to execute
rolestringRole ID — loads defaults from the role registry
contextstringAdditional context prepended to task as <context> block
isolated_memorybooleanIf true, child has no access to parent knowledge
system_promptstringOverride role/default system prompt
modelModelTierOverride role/default model
thinkingThinkingModeOverride thinking mode
effortEffortLevelOverride accuracy level
toolsstring[]Explicit tool whitelist (overrides role scoping)
max_turnsnumberMax iterations
max_tokensnumberMax output tokens
max_budget_usdnumberCost budget

3-tier resolution order: explicit spec fields > role defaults > global defaults (sonnet, all tools).

Tool scoping:

  1. spec.tools set → filter parent tools to only those names
  2. Role has allowedTools/deniedTools → apply role scoping
  3. Default → all parent tools minus spawn_agent

spawn_agent is always excluded from children to prevent recursion.

Agents run in parallel via Promise.allSettled. If all fail, throws AggregateError with details. Cancellation via AbortController with 10-minute timeout.

PropertyValue
Sourcesrc/tools/builtin/ask-user.ts
ModesSelect, confirm, freeform, tabbed

The agent should use ask_user proactively — don’t guess preferences or decisions. options should always be provided when the set of possible answers is finite (yes/no, file picks, deploy targets, approach selection). Free-text only for truly open-ended input.

Three input modes:

  1. Single question with options (preferred): { question: "...", options: ["Yes", "No"] } — 2-5 clear, distinct choices
  2. Tabbed multi-question: { question: "...", questions: [...] } — sequential tabs with navigation
  3. Non-interactive fallback: Returns “Interactive input not available” when no promptUser callback

In Slack, questions with options render as interactive buttons. Questions without options show a “Reply in this thread” prompt for free-text input.

PropertyValue
Sourcesrc/tools/builtin/batch-files.ts
Operationsrename, move, transform
Inputpattern, directory, operation, plus operation-specific fields
  • rename: Renames matching files using $1 substitution pattern
  • move: Moves matching files to a destination directory
  • transform: Find-and-replace in file contents (skips files > 10MB)
PropertyValue
Sourcesrc/tools/builtin/http.ts
MethodsGET, POST, PUT, DELETE, PATCH, HEAD
Response limit100KB (configurable via http_response_limit, truncated with hint)
SSRF protectionYes

Makes HTTP requests with full SSRF protection (see Security). Returns status, headers, and body. JSON responses are pretty-printed.

Network policy enforcement (setNetworkPolicy()): Available as an extension point for Pro. When a tenant context is active (via nodyn-pro), the http tool enforces the tenant’s network policy. allow-all (default) permits any request. allow-list restricts requests to hostnames in IsolationConfig.allowedHosts. deny-all blocks all outbound HTTP requests. See Security.

API profile enforcement: When API profiles are loaded (see API Store), requests to API-like URLs without a registered profile are blocked. Per-API rate limits from profiles are enforced automatically.

PropertyValue
Sourcesrc/tools/builtin/api-setup.ts
Actionscreate, update, delete, list

Create and manage API profiles that teach the agent how to correctly use external APIs. Profiles include endpoints, auth method, rate limits, guidelines, and common mistakes. Created profiles are validated (must include endpoints, guidelines, avoid rules, and auth), written to ~/.nodyn/apis/, and activated immediately (hot-reload). See API Store.

PropertyValue
Sourcesrc/tools/builtin/pipeline.ts
Inputsteps[] or pipeline_id, on_failure?, context?, retry?, modifications?
Eager streamingYes
Max steps20

Execute a multi-step workflow. Two modes: provide steps[] for inline execution, or pipeline_id to run a stored workflow (from plan_task or saved workflows). Steps without input_from dependencies execute in parallel automatically. Supports retry: true for re-running failed steps, and modifications (remove/update_task) for stored workflows. Step results truncated at 50KB (configurable via pipeline_step_result_limit).

PropertyValue
Sourcesrc/tools/builtin/task.ts
Inputtitle, description?, priority?, due_date?, scope?, tags?, parent_task_id?, schedule?, watch_url?, watch_interval_minutes?, pipeline_id?
Eager streamingYes

Creates a task in the SQLite task store. Supports scope validation against active scopes, subtask creation via parent_task_id, and date validation (YYYY-MM-DD). Returns the created task summary.

Task types determined by input fields:

TypeCreated whenBehavior
manualDefault (no scheduling fields)Standard task, executed on demand
scheduledschedule field set (cron expression or shorthand like every 5 minutes)WorkerLoop picks up at scheduled times
watchwatch_url field setWorkerLoop polls the URL at watch_interval_minutes (default: 60), triggers on content change
pipelinepipeline_id field setWorkerLoop executes the stored pipeline workflow

Background tasks with assignee='nodyn' auto-trigger immediately (nextRunAt=now).

PropertyValue
Sourcesrc/tools/builtin/task.ts
Inputtask_id, status?, priority?, due_date?, title?, description?, tags?
Eager streamingYes

Updates task fields. Setting status: "completed" also completes all subtasks and sets completed_at. Task IDs support prefix matching.

PropertyValue
Sourcesrc/tools/builtin/task.ts
Inputscope?, status?, due? (today/week/overdue), limit?
Eager streamingYes

Lists tasks filtered by scope, status, or due date range. Results are ordered by priority (urgent > high > medium > low), then by due date. Default limit: 20.

plan_task — Structured Planning with Workflow Bridge

Section titled “plan_task — Structured Planning with Workflow Bridge”
PropertyValue
Sourcesrc/tools/builtin/plan-task.ts
Inputsummary, context?, phases?, steps?
RegistrationAt init (always available)

Presents a structured plan to the user for approval before executing complex tasks. On approval with phases, auto-converts to a runnable workflow and returns a pipeline_id for run_pipeline.

Schema (simplified for LLM reliability — runtime assigns model, role, and effort):

  • summary: What will be done (plain language)
  • context: Exploration findings (summary, findings[])
  • phases[]: Steps with name, steps[], verification?, depends_on?, assignee? ("agent" | "user")
  • steps[]: Legacy flat step list (no workflow conversion)

Workflow bridge: Agent phases are auto-converted to executable workflow steps. User phases (assignee: "user") excluded — handled via ask_user or task_create. Cost estimate shown before approval.

Presentation: Business-friendly numbered list. User phases marked [your input needed]. No model names or dependency syntax.

PropertyValue
Sourcesrc/tools/builtin/process.ts
Inputname, description?
RegistrationDynamic (with workflow tools)

Captures the current session’s work as a reusable process template. Reads tool calls from run_history (structured data, not LLM knowledge), filters internal tools, and uses a fast-tier call (~$0.001) to name steps and identify parameters (fixed vs. variable elements).

Returns a ProcessRecord with typed steps, parameters (with source classification: user_input, relative_date, context), and defaults. Stored in SQLite processes table for cross-session availability.

PropertyValue
Sourcesrc/tools/builtin/process.ts
Inputprocess_id, parameter_values?
RegistrationDynamic (with workflow tools)

Converts a captured process into a reusable workflow. Steps are organized into a dependency graph. Parameters become workflow context variables with {{param}} templates. Returns pipeline_id for run_pipeline.

Built-in Anthropic server tool (web_search_20250305). Added automatically to every agent’s tool list. No custom handler needed.

web_research — Web Search Tool (Tavily / Brave)

Section titled “web_research — Web Search Tool (Tavily / Brave)”

Client-side web search and content extraction tool. Registered conditionally when TAVILY_API_KEY or BRAVE_API_KEY is set.

Actions:

  • search — Search the web. Returns numbered results with title, URL, snippet, and optionally full content (Tavily). Supports max_results (default 5, max 20), topic (general/news/finance, Tavily only), and time_range (day/week/month/year).
  • read — Extract main content from a URL using Readability. Returns clean text with title, word count, and truncation indicator. SSRF-protected.

Providers:

  • Tavily (default): Free tier 1,000 searches/month. Returns AI-optimized parsed content. Key prefix: tvly-.
  • Brave: Privacy-focused, own index. Requires X-Subscription-Token header.

Configuration: TAVILY_API_KEY env var, or search_api_key + search_provider in ~/.nodyn/config.json.

Most builtin tools enable eager_input_streaming: true in their definition:

definition: {
name: 'bash',
eager_input_streaming: true,
// ...
}

This enables 5x faster delivery of the first tool parameter from the streaming API.

Note: The worker pool is a Pro feature provided by nodyn-pro. It registers via Orchestrator Hooks (onInit/onShutdown).

The WorkerPool runs tools off the main thread using node:worker_threads:

  • Pool size: 4 workers (default)
  • Safe tools: bash, read_file, write_file
  • Resource limits: 256MB max old generation per worker
  • Queue: Tasks queue when all workers are busy
  • Auto-recovery: Workers respawn on crash
  • Cancellation: AbortSignal support — terminates worker and respawns
  • Spawn failure handling: EAGAIN / ERR_WORKER_INIT_FAILED are caught in try/catch — no uncaught throw in event handlers
  • Backoff on init failure: ERR_WORKER_INIT_FAILED triggers exponential backoff: 1s → 2s → 4s → … → 30s cap before retrying
  • Normal crashes: Non-init worker crashes still respawn immediately (no backoff)

The agent automatically routes worker-safe tools through the pool:

const result = this.workerPool && this.workerPool.isWorkerSafe(tc.name)
? await this.workerPool.execute(tc.name, tc.input)
: await tool.handler(tc.input, this);

Available when GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET are configured. Users authenticate via /google auth (OAuth 2.0 device flow). Service account auth supported via GOOGLE_SERVICE_ACCOUNT_KEY for headless/Docker deployments. Default OAuth scopes are read-only; write scopes opt-in via google_oauth_scopes config or requestScope() at runtime.

Security: All Google tool responses are scanned for prompt injection via scanToolResult(). All read handlers wrap external content with wrapUntrustedData() boundary markers. ToolCallTracker detects exfiltration patterns (Google read → email send, HTTP POST, or sensitive file read). Write actions require user confirmation and are blocked in autonomous mode. See Security: Google Workspace Injection Hardening.

Interact with Gmail via REST API.

ActionDescriptionConfirmation
searchSearch emails using Gmail query syntax (from:, is:unread, etc.)No
readRead full email content (HTML→text conversion)No
sendCompose and send new emailYes
replyReply to thread (sets In-Reply-To/References headers)Yes
draftSave draft without sendingNo
archiveRemove INBOX labelYes
mark_readRemove UNREAD labelNo
labelsList all labels with countsNo

Scopes: gmail.readonly (search/read/labels), gmail.send (send/reply/draft), gmail.modify (archive/mark_read).

Interact with Google Sheets via REST API.

ActionDescriptionConfirmation
readRead range as markdown tableNo
writeOverwrite range with valuesYes
appendAppend rows after existing dataNo
createCreate new spreadsheetNo
listList spreadsheets from DriveNo
formatApply batchUpdate formattingNo

Scopes: spreadsheets.readonly (read/list), spreadsheets (write/append/create/format).

Interact with Google Drive via REST API.

ActionDescriptionConfirmation
searchSearch files with Drive query syntaxNo
readRead file content (auto-exports Google Docs as text)No
uploadUpload file (multipart)Yes
create_docCreate Google Doc from text contentYes
listList folder contentsNo
moveMove file between foldersYes
shareShare file with email/roleYes

Scopes: drive.readonly (search/read/list), drive.file (upload/create_doc), drive (move/share).

Interact with Google Calendar via REST API.

ActionDescriptionConfirmation
list_eventsList upcoming events (default: next 7 days)No
create_eventCreate event (with optional attendees, sends invites)Yes
update_eventUpdate existing eventYes
delete_eventDelete eventYes
free_busyCheck availability across calendarsNo

Scopes: calendar.readonly (list_events/free_busy), calendar.events (create/update/delete).

Interact with Google Docs via REST API.

ActionDescriptionConfirmation
readRead document as markdownNo
createCreate document from markdown contentYes
appendAppend text to end of documentNo
replaceFind and replace textYes

Scopes: documents.readonly (read), documents (create/append/replace).