Skip to main content
A code-review workflow built one capability at a time. Each step is a diff against the previous. Reading time: 15 minutes.

1. Install and scaffold

init creates .smithers/ with seeded workflows, prompts, and components. The bun deps add Smithers and Zod (schemas); codex login makes your Codex subscription available to workflow workers.
Zod v4 is required. Smithers introspects your output schemas via Zod v4 internals, so pin zod@^4. A Zod v3 schema fails when building an agent command with the cryptic error undefined is not an object (evaluating 'schema._zod.def').
A minimal tsconfig.json:
jsxImportSource is the only line specific to Smithers; it routes JSX through the workflow runtime instead of React DOM.

2. One-task workflow

createSmithers registers Zod schemas; each becomes a durable output relation managed by the runtime. outputs.greeting is the typed reference for the greeting schema; using it as the output prop gives compile-time checks (typo outputs.greting is a type error). The input key is special: its schema types ctx.input (so ctx.input.name is a checked string, not unknown). Every other key is an output table. Omit input and ctx.input is untyped, forcing a defensive guard on each field (ctx.input?.name ?? "world"). Input fields also arrive as supplied, with no Zod defaults applied, so coalesce any field you do not require. The other schemas (greeting here) are the workflow’s outputs. This Task has no agent, just a literal value. Run it.
Inspect:
For a controller or custom monitor, read the same output through Gateway getNodeOutput({ runId, nodeId: "greet" }). Do not query the backing store; SQLite/PGlite/Postgres are interchangeable runtime details.

3. Add an agent task

Replace the literal Task with an agent Task whose output is structured.
The runtime injects a JSON-schema description of outputs.analysis into the prompt, parses the agent’s response, validates against Zod, and persists. Validation failure triggers a retry.

4. A second task that depends on the first

Tasks see each other’s outputs through ctx.outputMaybe(...). An incomplete upstream returns undefined; on the next render frame the upstream output appears and the downstream Task mounts. For the common case of one Task consuming exactly one upstream output, <Task deps={{ analyze: outputs.analysis }}> with a (deps) => ... children callback is the more ergonomic form; reach for ctx.outputMaybe when the guard needs to inspect content or gate more than one sibling.
Render 1: only analyze is mounted. Render 2 (after analyze finishes): analysis is populated, fix mounts and runs. That is the entire reactivity story: no hooks, no subscriptions, JSX conditionals over persisted state. Same shape works for branching, parallel groups, and loops. A ?: conditional is the inline form; <Branch> is the declarative form when you want explicit then/else (it takes those as props, not children):

5. An approval gate

Pause for a human. The runtime persists the pending decision and exits cleanly; the operating agent relays the question to the human, then approves or denies through the CLI; resume picks up from the gate.
Operator side (you, the agent, run these on the human’s behalf; never hand them to the human):
onDeny controls behavior on rejection: "fail" aborts the run, "continue" proceeds without the approved branch, "skip" skips the gated tasks.

6. Crash, then resume

Every completed task’s output sits in SQLite. A crash, kill, or restart loses no work; the next run with --resume true skips finished tasks.
A Smithers run is killed partway through, then resumes: the completed task is skipped, the in-flight task re-runs as a new attempt, and the run finishes

The same crash-and-resume mechanic: a run is killed mid-task, then resuming skips the finished work and re-runs only the interrupted task from its last persisted frame.

In-flight attempts are marked stale and re-tried; finished tasks are not. Resume is deterministic: same input + same code = same task IDs. For unattended recovery, run the supervisor:
It auto-resumes runs whose owner process died.

What you skipped (and where to find it)

  • How It Works: the render → execute → persist loop.
  • Components: JSX surface reference.
  • CLI: every command in one table.
  • Recipes: patterns from production workflows.