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').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.
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.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 throughctx.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.
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.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.

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.
What you skipped (and where to find it)
- Time travel (replay a frame, fork a run, diff two runs):
bunx smithers-orchestrator replay|fork|diff|timeline. See How It Works → Time travel. - Scorers (attach evaluators to Tasks): see Recipes → Scoring tasks.
- Memory (cross-run facts and message history): see How It Works → Memory.
- RAG, voice, OpenAPI tools: opt-in fragments. See the index in llms.txt.
- Tool sandbox (read/grep/bash with path containment): see Recipes → Tools.
Read next
- How It Works: the render → execute → persist loop.
- Components: JSX surface reference.
- CLI: every command in one table.
- Recipes: patterns from production workflows.