> ## Documentation Index
> Fetch the complete documentation index at: https://smithers-feat-claude-workflow-mirror.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Built-in Tools

> Sandboxed file and shell tools for AI agent tasks, with exact input schemas, security policies, and usage examples.

```ts theme={null}
import { tools, read, write, edit, grep, bash, defineTool, getDefinedToolMetadata } from "smithers-orchestrator";
```

`tools` bundles all five tools keyed by name:

```ts theme={null}
const { read, write, edit, grep, bash } = tools;
```

<Note>API reference: [Tools](/reference/tools) lists every built-in tool and helper, its options, and links to source and tests.</Note>

The `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 to `rootDir` (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](#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](https://ai-sdk.dev) tools with Smithers runtime context, deterministic idempotency keys, side-effect metadata, and the side-effect snapshot hook.

```ts theme={null}
import { defineTool } from "smithers-orchestrator";
import { z } from "zod";

const placeOrder = defineTool({
  name: "wholefoods.place_order",
  description: "Place a grocery order",
  schema: z.object({
    sku: z.string(),
  }),
  sideEffect: true,
  idempotent: false,
  async execute(args, ctx) {
    return await wholeFoods.placeOrder({
      sku: args.sku,
      idempotencyKey: ctx.idempotencyKey,
    });
  },
});
```

* `ctx.idempotencyKey` is stable across retries and resumes for the same task iteration.
* `sideEffect: true` opts the tool into Smithers side-effect tracking.
* `idempotent: false` marks the tool for retry warnings when a previous attempt has a recorded `_smithers_tool_calls` row.
* `defineTool()` does not persist `_smithers_tool_calls` rows 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** declare `sideEffect: true`. This is how Smithers protects your [workflow](/workflows/overview) 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. |

With `sideEffect: true` and `idempotent: false`, Smithers does two things on retry:

1. **Warns the agent.** The retry prompt lists which non-idempotent tools were already called.
2. **Provides a stable idempotency key.** `ctx.idempotencyKey` is deterministic for a given task + iteration; pass it to external APIs that support idempotency (Stripe, AWS) to deduplicate.

If your `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.

```ts theme={null}
// ✗ Bad: non-idempotent side effect without ctx
const sendEmail = defineTool({
  name: "email.send",
  schema: z.object({ to: z.string(), body: z.string() }),
  sideEffect: true,
  idempotent: false,
  async execute(args) {  // ← missing ctx parameter, Smithers warns
    await mailer.send(args);
  },
});

// ✓ Good: uses ctx.idempotencyKey to deduplicate
const sendEmail = defineTool({
  name: "email.send",
  schema: z.object({ to: z.string(), body: z.string() }),
  sideEffect: true,
  idempotent: false,
  async execute(args, ctx) {
    await mailer.send({ ...args, idempotencyKey: ctx.idempotencyKey });
  },
});
```

### What counts as a side effect

The rule is simple: **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. 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.

```ts theme={null}
{ path: string }  // relative to rootDir or absolute
```

Returns file contents as UTF-8. Throws `"File too large"` if size exceeds `maxOutputBytes`.

```ts theme={null}
import { ToolLoopAgent as Agent } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { read, grep } from "smithers-orchestrator";

const codeAgent = new Agent({
  model: anthropic("claude-fable-5"),
  tools: { read, grep },
});
```

```tsx theme={null}
{/* outputs comes from createSmithers() */}
<Task id="review" output={outputs.review} agent={codeAgent}>
  Read the file src/auth.ts and identify any security vulnerabilities.
</Task>
```

***

## write

Write content to a file. Creates parent directories as needed.

```ts theme={null}
{
  path: string      // relative to rootDir or absolute
  content: string
}
```

Returns `"ok"`. Throws `"Content too large"` if content exceeds `maxOutputBytes`. Logs content hash (SHA-256) and byte size; full content is not stored.

```ts theme={null}
import { ToolLoopAgent as Agent } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { write, read } from "smithers-orchestrator";

const writerAgent = new Agent({
  model: anthropic("claude-fable-5"),
  tools: { write, read },
});
```

***

## edit

Apply a unified diff patch to an existing file.

```ts theme={null}
{
  path: string    // file to patch
  patch: string   // unified diff format
}
```

Returns `"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.

```
--- a/src/auth.ts
+++ b/src/auth.ts
@@ -10,3 +10,4 @@
   const token = jwt.sign(payload, secret);
+  logger.info("Token issued", { userId: payload.sub });
   return token;
```

***

## grep

Search for a regex pattern using `ripgrep`.

```ts theme={null}
{
  pattern: string    // regex
  path?: string      // directory or file (default: rootDir)
}
```

Returns matching lines with file paths and line numbers (`rg -n` format). Exit code 1 (no matches) returns empty string. Exit code 2 throws stderr as error. Requires `ripgrep` in PATH.

```
src/auth.ts:15:  if (token.expired()) {
src/auth.ts:42:  validateToken(token);
tests/auth.test.ts:8:  const token = createTestToken();
```

***

## bash

Run an executable directly with arguments.

```ts theme={null}
{
  cmd: string                     // executable path/name; no shell parsing
  args?: string[]                 // arguments
  opts?: { cwd?: string }        // working directory (sandboxed)
}
```

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.

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 |

Local git commands (`git status`, `git diff`, `git log`) are allowed.

```ts theme={null}
import { ToolLoopAgent as Agent } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { bash } from "smithers-orchestrator";

const devAgent = new Agent({
  model: anthropic("claude-fable-5"),
  tools: { bash },
});
```

```tsx theme={null}
{/* outputs comes from createSmithers() */}
<Task id="lint" output={outputs.lint} agent={devAgent}>
  Run the linter on src/ and report any issues.
</Task>
```

***

## Using Tools with Agents

Pass tools to an [AI SDK](https://ai-sdk.dev) agent and assign the agent to a [`<Task>`](/components/task):

```tsx theme={null}
import { ToolLoopAgent as Agent } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { createSmithers, read, write, edit, grep, bash, Task } from "smithers-orchestrator";
import { z } from "zod";

const codeAgent = new Agent({
  model: anthropic("claude-fable-5"),
  tools: { read, write, edit, grep, bash },
  instructions: "You are a senior engineer. Use the available tools to complete tasks.",
});

const { Workflow, smithers, outputs } = createSmithers({
  result: z.object({ summary: z.string() }),
});

export default smithers((ctx) => (
  <Workflow name="refactor">
    <Task id="refactor" output={outputs.result} agent={codeAgent}>
      {`Refactor the function in ${ctx.input.file} to improve readability.`}
    </Task>
  </Workflow>
));
```

The full bundle works too:

```ts theme={null}
import { ToolLoopAgent as Agent } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { tools } from "smithers-orchestrator";

const fullAgent = new Agent({
  model: anthropic("claude-fable-5"),
  tools,
});
```

## 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`    |

```ts theme={null}
import { Effect } from "effect";
import { runWorkflow } from "smithers-orchestrator";

const result = await Effect.runPromise(runWorkflow(workflow, {
  input: { file: "src/auth.ts" },
  rootDir: "/home/project",
  allowNetwork: false,
  maxOutputBytes: 500_000,
  toolTimeoutMs: 120_000,
}));
```

## See Also

* [Agents and Tools](/agents/overview)
* [Sandbox](/components/sandbox)
* [Common External Tools](/integrations/common-tools)
* [Tools Agent Example](/examples/tools-agent)
