Skip to main content
tools bundles all five tools keyed by name:
API reference: Tools lists every built-in tool and helper, its options, and links to source and tests.
The smthrs/tools subpath also exports lower-level helpers:

Sandboxing

Tools sandbox to rootDir (default: workflow directory).

Tool call state

Smithers creates the _smithers_tool_calls table and exposes adapter methods to insert and list rows. The engine durably records the start of every defineTool() invocation before executing it, then reads those rows on retry to detect previously invoked non-idempotent side-effect tools. This start-first record is intentional: a process crash after the external side effect cannot erase the evidence needed for replay protection. Each invocation receives a unique callToken. Completion and failure updates match only that token, so a late completion cannot overwrite a replacement row that reused the task key after time travel.

defineTool

defineTool() wraps custom AI SDK tools with Smithers runtime context, deterministic idempotency keys, side-effect metadata, and the side-effect snapshot hook.
  • ctx.idempotencyKey is stable across retries and resumes for the same task iteration.
  • sideEffect: true opts the tool into side-effect tracking.
  • idempotent: false marks the tool for retry warnings when a previous attempt has a recorded _smithers_tool_calls row.
  • The engine persists the durable start row through the Smithers DB adapter before execute runs.

Side Effects and Idempotency

Every custom tool that modifies external state must declare sideEffect: true; this is how Smithers protects your workflow during retries and resumes. Without it, Smithers treats the tool as a pure read and replays it freely: duplicate emails, double-charged payments, duplicate records. The two flags work together: With sideEffect: true and idempotent: false, Smithers does two things on retry:
  1. Warns the agent. The retry prompt lists non-idempotent tools already called.
  2. Provides a stable idempotency key. ctx.idempotencyKey is deterministic per task + iteration; pass it to external APIs that support idempotency (Stripe, AWS) to deduplicate.
If execute has sideEffect: true, idempotent: false but omits ctx, Smithers logs a startup warning: almost always a bug, since retries need ctx.idempotencyKey to be safe.

What counts as a side effect

If you cannot undo it with git reset, mark it as a side effect. A side effect is any mutation the runtime should not blindly repeat on retry: an external API call, a database write, a sent message, a triggered webhook. The built-in write and edit tools are sideEffect: true, idempotent: false: their file mutations aren’t safe to blindly replay on retry, so like bash, all three built-in mutating tools are treated conservatively.

read

Read a file from the sandbox.
Returns file contents as UTF-8. Throws "File too large" if size exceeds maxOutputBytes.

write

Write content to a file. Creates parent directories as needed.
Returns "ok"; throws "Content too large" if content exceeds maxOutputBytes. Logs content hash (SHA-256) and byte size, not full content.

edit

Apply a unified diff patch to an existing file.
Returns "ok"; the file must exist. Reads, patches via applyPatch, writes back. Throws on size limits ("Patch too large", "File too large") or mismatched context ("Failed to apply patch"). Logs patch hash and byte size.

grep

Search for a regex pattern using ripgrep.
Returns matching lines with file paths and line numbers (rg -n format). Exit 1 (no matches) returns empty string; exit 2 throws stderr as error. Requires ripgrep in PATH.

bash

Run an executable directly with arguments.
Use args for arguments. If you need shell syntax such as pipes or redirects, invoke a shell explicitly, for example cmd: "sh", args: ["-lc", "..."]. Returns combined stdout and stderr; working directory defaults to rootDir. Timeout: 60s (killed with SIGKILL via process group). Non-zero exit codes throw.

Network Blocking

Controlled by allowNetwork in RunOptions, --allow-network on CLI, or server config. Default: blocked. Blocked means no egress, not no sockets. Loopback stays reachable (localhost, 127.0.0.0/8, ::1, 0.0.0.0, *.localhost, unix sockets), so local test compositions (a dev server, a local postgres, a socket simulator) run under the default. When blocked, Smithers matches the invoked executable, arguments that are themselves remote URLs, and git’s resolved subcommand. Argument text is never scanned, so local commands that merely mention a network tool or a URL still run: git commit -m "fetch upstream", echo "run npm install". Local git commands (git status, git diff, git log) are allowed. Denials throw TOOL_NETWORK_DISABLED / TOOL_GIT_REMOTE_DISABLED with a message naming the allowNetwork escape hatch.
Enforcement vs. bypassable denylist. True OS-level network isolation runs only on macOS: a blocked bash executes under sandbox-exec with a deny-egress profile that still permits loopback and unix sockets (when available). On Linux and every other platform, including Smithers’ CI, cloud, and production environments, allowNetwork:false cannot enforce a kernel sandbox and falls back to the executable/URL denylist above: best-effort defense-in-depth, not a security boundary, trivially bypassed by an interpreter (python -c assembling a URL from parts, bash /dev/tcp), a nested shell, or a renamed binary. Unenforced isolation logs a TOOL_NETWORK_ISOLATION_UNENFORCED observability warning. Do not rely on allowNetwork:false to sandbox untrusted code: run it under a real <Sandbox> provider with egress controls instead.

Using Tools with Agents

Pass tools to an AI SDK agent and assign the agent to a <Task>:
The full bundle works too:

Configuration

See Also