API reference: Authoring and Components list every authoring helper and JSX element, with options and links to source and tests.
Setup
Most projects should usebunx smithers-orchestrator init; it scaffolds everything below.
To embed into an existing codebase:
smithers-orchestrator bundles React and ships the JSX runtime at smithers-orchestrator/jsx-runtime: skip react and react-dom. jsxImportSource (below) routes JSX through the workflow reconciler, never React DOM. The one required dev dep is @types/react (React ships no bundled types; the JSX transform resolves its namespace from it). Only the browser UI surface, smithers-orchestrator/gateway-react, takes react/react-dom as peers.
Minimal tsconfig.json:
jsxImportSource is the only non-standard line; it routes JSX through smithers-orchestrator/jsx-runtime instead of React DOM.
Optional MDX prompts: add bun add -d @types/mdx and a preload.ts that calls mdxPlugin(), register it in bunfig.toml as preload = ["./preload.ts"].
Verify with bunx tsc --noEmit and bunx smithers-orchestrator --help.
A minimal workflow
createSmithers is a named export; the lowercase smithers it returns is not a separate top-level import. smithers((ctx) => ...) returns the SmithersWorkflow value the workflow file exports. The input schema above types ctx.input.repo; omit it and ctx.input is unknown, so guard or parse it yourself.
outputs.analysis is the typed reference for the Zod schema, so typos are compile errors. The task body here is a JSX expression ({...}) returning a plain object: a static return, no LLM call. Real tasks pass a run prop or an AI model; see the Task component reference.
Reactivity
The tree re-renders every frame.ctx is just an optional function argument: drop it and write smithers(() => ( ... )) if a workflow never reads ctx.input or ctx.outputMaybe. When used, ctx exposes ctx.input and ctx.outputMaybe(ref, { nodeId }), which returns a completed task’s output, or undefined if it hasn’t run yet:
report Task doesn’t exist in the plan until analysis completes: no placeholder, no skipped node, the conditional IS the dependency. Static DAG tools require declaring optional nodes upfront; the JSX conditional instead evaluates fresh each frame, so report simply isn’t there while analysis is undefined.
For a task that just consumes one upstream output, <Task deps={{ analyze: outputs.analysis }}> with a (deps) => ... children callback expresses the same gating more directly. Reach for ctx.outputMaybe when the logic is structural: branching on content, loops, counts.
Read next
- Tour: six-step worked example with agents, schemas, approvals, resume.
- How It Works: the render → execute → persist loop.
- Components: full prop surface for every JSX element.