Skip to main content
For 1-5 tasks, a single-file pattern suffices. For anything larger:

Directory Layout

scripts/my-workflow/
  workflow.tsx          # Root workflow -- thin, just composition
  smithers.ts           # createSmithers() + schema registry
  agents.ts             # Agent definitions (CLI + API SDK)
  config.ts             # Shared constants (max iterations, etc.)
  system-prompt.ts      # Build system prompt from MDX + docs
  preload.ts            # MDX plugin registration
  bunfig.toml           # preload = ["./preload.ts"]
  package.json
  tsconfig.json
  run.sh                # Shell script to launch workflow
  components/
    index.ts            # Re-export all components
    Discover.tsx        # Step component
    Discover.schema.ts  # Zod schema for output
    Discover.mdx        # Prompt template
    Implement.tsx
    Implement.schema.ts
    Implement.mdx
    Validate.tsx
    Validate.schema.ts
    Validate.mdx
    Review.tsx
    Review.schema.ts
    Review.mdx
    ReviewFix.tsx
    ReviewFix.schema.ts
    ReviewFix.mdx
    Report.tsx
    Report.schema.ts
    Report.mdx
    TicketPipeline.tsx   # Composed pipeline per ticket
    ValidationLoop.tsx   # Loop: implement -> validate -> review -> fix
  prompts/
    system-prompt.mdx    # Master system prompt template
    *.md                 # Domain-specific context docs

Rationale

PrincipleEffect
MDX promptsPrompt engineering separated from orchestration logic
Schema filesPer-step .schema.ts with Zod; auto-creates SQLite tables, validates output
One component per stepComposable, independently testable
Thin workflow.tsxRoot file only composes components
Shared agents.tsSingle place to configure and swap models

Key Files

smithers.ts — Schema Registry

import { createSmithers } from "smithers-orchestrator";
import { DiscoverOutput } from "./components/Discover.schema";
import { ImplementOutput } from "./components/Implement.schema";
import { ValidateOutput } from "./components/Validate.schema";
import { ReviewOutput } from "./components/Review.schema";
import { ReviewFixOutput } from "./components/ReviewFix.schema";
import { ReportOutput } from "./components/Report.schema";

export const { Workflow, Task, useCtx, smithers, tables, outputs } = createSmithers({
  discover: DiscoverOutput,
  implement: ImplementOutput,
  validate: ValidateOutput,
  review: ReviewOutput,
  reviewFix: ReviewFixOutput,
  report: ReportOutput,
}, { dbPath: "./my-workflow.db" });

agents.ts

See Model Selection for the dual-agent setup pattern.

config.ts

export const MAX_REVIEW_ROUNDS = 3;
export const IMPLEMENT_TIMEOUT_MS = 45 * 60 * 1000;
export const REVIEW_TIMEOUT_MS = 15 * 60 * 1000;

preload.ts + bunfig.toml

// preload.ts
import { mdxPlugin } from "smithers-orchestrator";
mdxPlugin();
# bunfig.toml
preload = ["./preload.ts"]

workflow.tsx

import { Sequence, Branch } from "smithers-orchestrator";
import { Discover, TicketPipeline } from "./components";
import { Ticket } from "./components/Discover.schema";
import { Workflow, smithers, tables } from "./smithers";

export default smithers((ctx) => {
  const discoverOutput = ctx.latest(tables.discover, "discover-codex");
  const unfinishedTickets = ctx
    .latestArray(discoverOutput?.tickets, Ticket)
    .filter((t) => !ctx.latest(tables.report, `${t.id}:report`)) as Ticket[];

  return (
    <Workflow name="my-workflow">
      <Sequence>
        <Branch if={unfinishedTickets.length === 0} then={<Discover />} />
        {unfinishedTickets.map((ticket) => (
          <TicketPipeline key={ticket.id} ticket={ticket} />
        ))}
      </Sequence>
    </Workflow>
  );
});

components/index.ts

export { Discover } from "./Discover";
export { Implement } from "./Implement";
export { Validate } from "./Validate";
export { Review } from "./Review";
export { ReviewFix } from "./ReviewFix";
export { Report } from "./Report";
export { ValidationLoop } from "./ValidationLoop";
export { TicketPipeline } from "./TicketPipeline";
export type { Ticket } from "./Discover.schema";

run.sh

#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"

cd "$SCRIPT_DIR"

export USE_CLI_AGENTS=1
export SMITHERS_UNSAFE=1

echo "Starting workflow"
bunx smithers-orchestrator up workflow.tsx --input '{}' --root "$ROOT_DIR"

package.json

{
  "name": "my-workflow",
  "type": "module",
  "scripts": {
    "start": "bun run workflow.tsx",
    "resume": "smithers up workflow.tsx --run-id <run-id> --resume true",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "@ai-sdk/anthropic": "^3.0.36",
    "@ai-sdk/openai": "^2.0.0",
    "@mdx-js/esbuild": "^3.1.1",
    "@mdx-js/mdx": "^3.1.1",
    "@types/mdx": "^2.0.13",
    "ai": "^6.0.69",
    "react-dom": "^19.2.4",
    "smithers-orchestrator": "latest",
    "zod": "^4.3.6"
  },
  "devDependencies": {
    "@types/node": "^25.2.2",
    "@types/react": "^19.2.13",
    "@types/react-dom": "^19.2.3",
    "typescript": "^5.9.3"
  }
}

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "lib": ["ES2022"],
    "jsx": "react-jsx",
    "jsxImportSource": "smithers-orchestrator",
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "noEmit": true,
    "strict": true,
    "skipLibCheck": true,
    "types": ["@types/mdx", "@types/react-dom", "@types/node"]
  },
  "include": ["**/*.ts", "**/*.tsx", "**/*.mdx"],
  "exclude": ["node_modules"]
}

The Component Pattern

Each step follows a three-file pattern: schema, prompt, component.

1. Schema (Component.schema.ts)

import { z } from "zod";

export const ImplementOutput = z.object({
  filesCreated: z.array(z.string()).nullable(),
  filesModified: z.array(z.string()).nullable(),
  commitMessages: z.array(z.string()),
  whatWasDone: z.string(),
  allTestsPassing: z.boolean(),
  testOutput: z.string(),
});
export type ImplementOutput = z.infer<typeof ImplementOutput>;

2. Prompt (Component.mdx)

IMPLEMENTATION -- Ticket: {props.ticketId} -- {props.ticketTitle}

{props.ticketDescription}

ACCEPTANCE CRITERIA:
- {props.acceptanceCriteria}

{props.previousImplementation
  ? `PREVIOUS ATTEMPT:\n${props.previousImplementation.whatWasDone}\nFix issues from previous attempt.`
  : ""}

{props.reviewFixes
  ? `REVIEW FIXES NEEDED:\n${props.reviewFixes}`
  : ""}

**REQUIRED OUTPUT** -- JSON matching this schema:
{props.schema}
{props.schema} is auto-injected by Smithers from the Zod schema.

3. Component (Component.tsx)

import { Task, useCtx, tables, outputs } from "../smithers";
import { codex } from "../agents";
import ImplementPrompt from "./Implement.mdx";
import type { Ticket } from "./Discover.schema";

export function Implement({ ticket }: { ticket: Ticket }) {
  const ctx = useCtx();
  const ticketId = ticket.id;
  const latestValidate = ctx.latest(tables.validate, `${ticketId}:validate`);

  return (
    <Task id={`${ticketId}:implement`} output={outputs.implement} agent={codex} timeoutMs={45 * 60 * 1000}>
      <ImplementPrompt
        ticketId={ticketId}
        ticketTitle={ticket.title}
        ticketDescription={ticket.description}
        acceptanceCriteria={ticket.acceptanceCriteria?.join("\n- ") ?? ""}
        validationFeedback={latestValidate ?? null}
      />
    </Task>
  );
}

Next Steps