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.
smithers-orchestrator/tools subpath also exports lower-level helpers for advanced integrations:
| Export | Purpose |
|---|---|
readFileTool, writeFileTool, editFileTool, grepTool, bashTool | Call the underlying implementation directly instead of the AI SDK tool wrapper. |
getDefinedToolMetadata(tool) | Read Smithers metadata (name, sideEffect, idempotent) from a defineTool() result. |
getToolContext(), runWithToolContext(ctx, fn) | Inspect or provide the task-local tool runtime context. |
getToolIdempotencyKey(ctx?), nextToolSeq(ctx) | Build stable idempotency keys and task-local tool-call sequence numbers. |
BASH_TOOL_MAX_* constants | Upper bounds for bash command length, args, cwd, output bytes, and timeout. |
Sandboxing
All tools are sandboxed torootDir (defaults to the workflow directory). Paths are resolved relative to this root; escapes via symlinks are rejected.
| Policy | Behavior |
|---|---|
| Path resolution | Relative paths resolve against rootDir. Absolute paths must fall within root. |
| Symlinks | Rejected if target is outside sandbox. |
| Output size | Process output is truncated to maxOutputBytes (default 200KB); read, write, and edit reject files, content, or patches that exceed it. |
| Timeouts | bash and grep default to 60s; exceeded processes killed with SIGKILL. |
| Network | bash blocks network commands by default. See bash. |
Tool call state
Smithers creates the_smithers_tool_calls table and exposes adapter methods to insert and list rows. The current engine reads that table on retry to build warnings for previously recorded non-idempotent side-effect tool calls. The defineTool() wrapper itself does not insert a durable row for every call; it attaches metadata, provides ctx.idempotencyKey, and runs the side-effect snapshot hook when a task supplies one.
| Field | Description |
|---|---|
runId | Workflow run ID |
nodeId | Task node that invoked the tool |
iteration | Loop iteration |
attempt | Retry attempt number |
seq | Sequential call counter within the task |
toolName | read, write, edit, grep, or bash |
inputJson | Serialized input arguments |
outputJson | Serialized output (truncated if over limit) |
startedAtMs | Start timestamp |
finishedAtMs | End timestamp |
status | "success" or "error" |
errorJson | Error details (if "error") |
defineTool
defineTool() wraps custom AI SDK tools with Smithers runtime context, deterministic idempotency keys, side-effect metadata, and the side-effect snapshot hook.
ctx.idempotencyKeyis stable across retries and resumes for the same task iteration.sideEffect: trueopts the tool into Smithers side-effect tracking.idempotent: falsemarks the tool for retry warnings when a previous attempt has a recorded_smithers_tool_callsrow.defineTool()does not persist_smithers_tool_callsrows directly; durable rows come from runtime paths that call the Smithers DB adapter.
Side Effects and Idempotency
Every custom tool that modifies external state must declaresideEffect: 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, potentially sending duplicate emails, double-charging payments, or creating duplicate records.
The two flags work together:
sideEffect | idempotent | Smithers behavior |
|---|---|---|
false (default) | true (default) | Pure read. Safe to replay on retry. No warnings. |
true | true | Mutates external state, but calling twice with the same input produces the same result (e.g. an upsert, a PUT request). Safe to replay. No warnings. |
true | false | Mutates external state and is not safe to replay (e.g. sending an email, placing an order, charging a payment). On retry, Smithers injects a warning listing the tool as already called so the agent can verify external state before calling again. |
sideEffect: true and idempotent: false, Smithers does two things on retry:
- Warns the agent. The retry prompt lists which non-idempotent tools were already called.
- Provides a stable idempotency key.
ctx.idempotencyKeyis deterministic for a given task + iteration; pass it to external APIs that support idempotency (Stripe, AWS) to deduplicate.
execute function has sideEffect: true, idempotent: false but omits the ctx parameter, Smithers logs a startup warning. This is almost always a bug: you need ctx.idempotencyKey to handle retries safely.
What counts as a side effect
The rule is simple: if you cannot undo it withgit reset, mark it as a side effect.
A side effect is any mutation the runtime should not blindly repeat on retry. If a custom tool talks to an external API, writes to a database, sends a message, or triggers a webhook, mark it.
The built-in write and edit tools are registered as sideEffect: true and idempotent: false because their file mutations are not safe to blindly replay on retry; like bash, they are treated conservatively. All three built-in mutating tools (write, edit, bash) are side-effecting.
| Tool | Side effect? | Why |
|---|---|---|
Built-in read, grep | No | Pure reads |
Built-in write, edit | Yes | Sandboxed file writes are tracked by git, but replaying on retry could overwrite content that diverged since the first call |
Built-in bash | Yes | Arbitrary shell commands may not be safe to repeat |
| Custom tool calling an external API | Yes | Mutates state outside the sandbox |
| Custom tool writing to a database | Yes | External persistent state |
| Custom tool sending a Slack message | Yes | Irreversible external communication |
| Custom tool creating a GitHub PR | Yes | External state visible to others |
read
Read a file from the sandbox."File too large" if size exceeds maxOutputBytes.
write
Write content to a file. Creates parent directories as needed."ok". Throws "Content too large" if content exceeds maxOutputBytes. Logs content hash (SHA-256) and byte size; full content is not stored.
edit
Apply a unified diff patch to an existing file."ok". The file must exist. Reads current contents, applies the patch 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 usingripgrep.
rg -n format). Exit code 1 (no matches) returns empty string. Exit code 2 throws stderr as error. Requires ripgrep in PATH.
bash
Run an executable directly with arguments.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 byallowNetwork in RunOptions, --allow-network on CLI, or server config. Default: blocked.
When blocked, Smithers tokenizes cmd plus args. Executable basenames are
matched for known network tools, URL tokens are blocked by prefix, and git
plus a remote-operation token is blocked.
| Category | Match |
|---|---|
| HTTP clients | executable basename curl or wget |
| URL tokens | any token starting with http:// or https:// |
| Package managers | executable basename npm, bun, or pip |
| Git remote ops | git plus a push, pull, fetch, clone, or remote token |
git status, git diff, git log) are allowed.
Using Tools with Agents
Pass tools to an AI SDK agent and assign the agent to a<Task>:
Configuration
| Option | Default | Description |
|---|---|---|
rootDir | Workflow directory | Sandbox root |
allowNetwork | false | Allow network commands in bash |
maxOutputBytes | 200000 (200KB) | Max output size per tool |
toolTimeoutMs | 60000 (60s) | Timeout for bash and grep |