Skip to main content

1. Project Setup

mkdir code-review && cd code-review
bun init -y
bun add smithers-orchestrator ai @ai-sdk/anthropic zod
// tsconfig.json
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "jsxImportSource": "smithers-orchestrator",
    "strict": true,
    "noEmit": true,
    "skipLibCheck": true
  }
}
export ANTHROPIC_API_KEY="sk-ant-..."
code-review/
  tsconfig.json
  package.json
  workflow.tsx      # Workflow definition
  main.ts          # Runner (optional -- CLI works too)

2. Define Schemas

Each Zod schema passed to createSmithers becomes a named, auto-created SQLite output table.
/** @jsxImportSource smithers-orchestrator */
// workflow.tsx
import { createSmithers, Task, Sequence } from "smithers-orchestrator";
import { z } from "zod";

const { Workflow, smithers, outputs } = createSmithers({
  analysis: z.object({
    summary: z.string(),
    issues: z.array(z.object({
      file: z.string(),
      line: z.number(),
      severity: z.enum(["low", "medium", "high"]),
      description: z.string(),
    })),
  }),
  fix: z.object({
    patch: z.string(),
    explanation: z.string(),
    filesChanged: z.array(z.string()),
  }),
  report: z.object({
    title: z.string(),
    body: z.string(),
    issueCount: z.number(),
    fixedCount: z.number(),
  }),
});
outputs provides typed references (outputs.analysis instead of the string "analysis"). Typos become compile errors. runId, nodeId, and iteration columns are auto-added.

3. Configure Agents

This example uses the Vercel AI SDK with Anthropic Claude models.
/** @jsxImportSource smithers-orchestrator */
// workflow.tsx (continued)
import { ToolLoopAgent as Agent } from "ai";
import { anthropic } from "@ai-sdk/anthropic";

const analyst = new Agent({
  model: anthropic("claude-sonnet-4-20250514"),
  instructions: "You are a senior code reviewer. Analyze code for bugs, security issues, and quality problems. Return structured JSON.",
});

const fixer = new Agent({
  model: anthropic("claude-sonnet-4-20250514"),
  instructions: "You are a senior engineer who writes minimal, correct fixes. Return structured JSON with a unified diff patch.",
});

const reporter = new Agent({
  model: anthropic("claude-sonnet-4-20250514"),
  instructions: "You are a technical writer. Summarize code review findings into a clear report. Return structured JSON.",
});

4. Build the Workflow

/** @jsxImportSource smithers-orchestrator */
// workflow.tsx (continued)
export default smithers((ctx) => {
  const analysis = ctx.outputMaybe(outputs.analysis, { nodeId: "analyze" });
  const fix = ctx.outputMaybe(outputs.fix, { nodeId: "fix" });

  return (
    <Workflow name="code-review">
      <Sequence>
        <Task id="analyze" output={outputs.analysis} agent={analyst}>
          {`Review this code for bugs and issues:

Repository: ${ctx.input.repo}
Focus area: ${ctx.input.focusArea ?? "general"}

Return JSON with:
- summary (string): overall assessment
- issues (array): each with file, line, severity, and description`}
        </Task>

        {analysis ? (
          <Task id="fix" output={outputs.fix} agent={fixer}>
            {`Fix these issues:

${analysis.issues.map((i) => `- [${i.severity}] ${i.file}:${i.line} - ${i.description}`).join("\n")}

Return JSON with:
- patch (string): unified diff
- explanation (string): what you changed and why
- filesChanged (string[]): list of modified files`}
          </Task>
        ) : null}

        {fix ? (
          <Task id="report" output={outputs.report} agent={reporter}>
            {`Write a code review report.

Analysis summary: ${analysis!.summary}
Issues found: ${analysis!.issues.length}
Fix explanation: ${fix.explanation}
Files changed: ${fix.filesChanged.join(", ")}

Return JSON with:
- title (string)
- body (string): markdown report
- issueCount (number)
- fixedCount (number)`}
          </Task>
        ) : null}
      </Sequence>
    </Workflow>
  );
});
  • ctx.outputMaybe() returns undefined until the task completes. Safe for reactive control flow.
  • {analysis ? <Task .../> : null} gates downstream tasks on upstream completion inside a Sequence.
  • ctx.input is the runtime input object (here: { repo: string, focusArea?: string }). See the data model.

5. Create the Runner

// main.ts
import { runWorkflow } from "smithers-orchestrator";
import workflow from "./workflow";

const result = await runWorkflow(workflow, {
  input: { repo: "/path/to/my-project", focusArea: "authentication" },
  onProgress: (event) => {
    if (event.type === "NodeStarted") {
      console.log(`Starting: ${event.nodeId}`);
    }
    if (event.type === "NodeFinished") {
      console.log(`Finished: ${event.nodeId}`);
    }
  },
});

console.log("Status:", result.status);
console.log("Run ID:", result.runId);

if (result.status === "finished") {
  console.log("Run finished. Inspect the persisted report row in Step 7.");
}
Because the final schema key here is report rather than output, result.output stays undefined. Rename that schema key to output if you want runWorkflow() to return it directly.

6. Run It

bun run main.ts
Or via the CLI (no main.ts needed):
bunx smithers-orchestrator up workflow.tsx --input '{"repo": "/path/to/my-project", "focusArea": "authentication"}'

7. Inspect Results

RUN_ID="your-run-id"
bunx smithers-orchestrator inspect "$RUN_ID"
bunx smithers-orchestrator graph workflow.tsx --run-id "$RUN_ID"
bunx smithers-orchestrator logs "$RUN_ID" --tail 5 --follow false
Query SQLite directly:
sqlite3 smithers.db "SELECT * FROM analysis WHERE run_id = '$RUN_ID';"
sqlite3 smithers.db "SELECT * FROM report WHERE run_id = '$RUN_ID';"

Execution Model

The engine renders the JSX tree repeatedly, following the execution model:
  1. Render 1 — Only analyze is mounted. Engine executes it.
  2. Render 2ctx.outputMaybe(outputs.analysis) returns data. fix mounts and executes.
  3. Render 3 — Both outputs available. report mounts and executes.
  4. Render 4 — All tasks finished. Run completes.
On crash, resume skips completed tasks:
bunx smithers-orchestrator up workflow.tsx --run-id "$RUN_ID" --resume true

Adding Tools

/** @jsxImportSource smithers-orchestrator */
import { read, grep, bash } from "smithers-orchestrator";

const analyst = new Agent({
  model: anthropic("claude-sonnet-4-20250514"),
  instructions: "You are a senior code reviewer.",
  tools: { read, grep, bash },
});
Tools are sandboxed to the workflow root by default. See Built-in Tools.

Next Steps