> ## Documentation Index
> Fetch the complete documentation index at: https://smithers.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Worktree Feature Workflow

> A deliberately Codex-first pipeline that discovers tickets from a PRD, implements them with GPT-5.6 Terra, validates, reviews with GPT-5.6 Sol, and generates reports, with Claude only as an unavailable-Codex fallback.

# Worktree Feature: Full Pipeline

<Note>
  The most complex Smithers example: role-specific Codex agents, with sequential
  Claude fallback, through a full development lifecycle.
</Note>

## Pipeline

1. **Discover**: Read PRD, break into ordered independent tickets
2. **Implement**: Write code end-to-end per ticket with GPT-5.6 Terra
3. **Validate**: Run `bun test`
4. **Review**: GPT-5.6 Sol reviews; Claude only if Codex is unavailable
5. **ReviewFix**: Address review issues
6. **Report**: Generate final report

Steps 2--5 loop via `<Loop>` until the Codex-first review seat approves or max
iterations are reached. Agent arrays are sequential fallback chains, not
parallel reviewers.

## Schema Setup: smithers.ts

Schema definitions live in `smithers.ts`; see [Worktree Feature Schemas](./worktree-feature-schemas) for the full field list. The file ends with the `createSmithers(...)` registration call:

```tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
// scripts/worktree-feature/smithers.ts
export const { Workflow, Task, useCtx, smithers, tables, outputs } = createSmithers({
  discover: DiscoverOutput,
  implement: ImplementOutput,
  validate: ValidateOutput,
  review: ReviewOutput,
  reviewFix: ReviewFixOutput,
  report: ReportOutput,
}, {
  dbPath: `${process.env.HOME}/.cache/smithers/worktree-feature.db`,
  journalMode: "DELETE",
});
```

## Entry Point: workflow\.tsx

```tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
// scripts/worktree-feature/workflow.tsx
import { Sequence, Branch } from "smithers-orchestrator";
import { Discover, TicketPipeline } from "./components";
import { Workflow, smithers, outputs } from "./smithers";

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

  return (
    <Workflow name="worktree-feature">
      <Sequence>
        <Branch if={tickets.length === 0} then={<Discover />} />
        {unfinishedTickets.map((ticket: any) => (
          <TicketPipeline key={ticket.id} ticket={ticket} />
        ))}
      </Sequence>
    </Workflow>
  );
});
```

## Agents: agents.ts

```tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
// scripts/worktree-feature/agents.ts
import { ToolLoopAgent as Agent, stepCountIs } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { openai } from "@ai-sdk/openai";
import { ClaudeCodeAgent, CodexAgent } from "smithers-orchestrator";
import { SYSTEM_PROMPT } from "./system-prompt";

const USE_CLI = process.env.USE_CLI_AGENTS !== "0";
const UNSAFE = process.env.SMITHERS_UNSAFE === "1";

// Claude is retained only as the unavailable-Codex fallback. It can switch
// between API and CLI execution without changing the role chains below.
const claudeApi = new Agent({
  model: anthropic("claude-fable-5"),
  instructions: SYSTEM_PROMPT,
  stopWhen: stepCountIs(100),
});

const claudeCli = new ClaudeCodeAgent({
  model: "claude-fable-5",
  systemPrompt: SYSTEM_PROMPT,
  dangerouslySkipPermissions: UNSAFE,
  timeoutMs: 30 * 60 * 1000,
});

const claudeFallback = USE_CLI ? claudeCli : claudeApi;

// This example deliberately runs its build lane on Codex Terra (the default
// implementer is Claude Opus 5; see /reference/sota-models); Luna is
// reserved for trivial, minimal-risk edits. Sol, below, takes review.
const codexTerra = new CodexAgent({
  model: "gpt-5.6-terra",
  config: { model_reasoning_effort: "medium" },
  systemPrompt: SYSTEM_PROMPT,
  yolo: UNSAFE,
  timeoutMs: 30 * 60 * 1000,
});

// Sol owns judgment-heavy review.
const codexSol = new CodexAgent({
  model: "gpt-5.6-sol",
  config: { model_reasoning_effort: "xhigh" },
  systemPrompt: SYSTEM_PROMPT,
  yolo: UNSAFE,
  timeoutMs: 30 * 60 * 1000,
});

// Smithers tries entries in order and advances only when the preceding agent
// is unavailable or fails. Claude is never a parallel hot-path reviewer.
export const implementAgents = [codexTerra, claudeFallback];
export const reviewAgents = [codexSol, claudeFallback];
```

The `Implement` task receives `agent={implementAgents}`; the review component
below receives `agent={reviewAgents}`.

## Validation Loop: ValidationLoop.tsx

```tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
// scripts/worktree-feature/components/ValidationLoop.tsx
import { Loop, Sequence } from "smithers-orchestrator";
import { Implement } from "./Implement";
import { Validate } from "./Validate";
import { Review } from "./Review";
import { ReviewFix } from "./ReviewFix";
import { useCtx } from "../smithers";

const MAX_REVIEW_ROUNDS = 3;

export function ValidationLoop({ ticket }: { ticket: { id: string } }) {
  const ctx = useCtx();
  const ticketId = ticket.id;

  const review = ctx.latest("review", `${ticketId}:review`);
  const approved = !!review?.approved;

  return (
    <Loop
      id={`${ticketId}:impl-review-loop`}
      until={approved}
      maxIterations={MAX_REVIEW_ROUNDS}
      onMaxReached="return-last"
    >
      <Sequence>
        <Implement ticket={ticket} />
        <Validate ticket={ticket} />
        <Review ticket={ticket} />
        <ReviewFix ticket={ticket} />
      </Sequence>
    </Loop>
  );
}
```

## Codex-First Review: Review\.tsx

```tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
// scripts/worktree-feature/components/Review.tsx
import { Task, useCtx, outputs } from "../smithers";
import { reviewAgents } from "../agents";
import ReviewPrompt from "./Review.mdx";

export function Review({ ticket }: { ticket: { id: string; title: string } }) {
  const ctx = useCtx();
  const ticketId = ticket.id;
  const latestValidate = ctx.latest("validate", `${ticketId}:validate`);

  if (!latestValidate?.allPassed) return null;

  return (
    <Task
      id={`${ticketId}:review`}
      output={outputs.review}
      agent={reviewAgents}
      timeoutMs={15 * 60 * 1000}
      continueOnFail
    >
      <ReviewPrompt ticketId={ticketId} reviewer="review" />
    </Task>
  );
}
```

## Ticket Pipeline: TicketPipeline.tsx

```tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
// scripts/worktree-feature/components/TicketPipeline.tsx
import { Sequence } from "smithers-orchestrator";
import { ValidationLoop } from "./ValidationLoop";
import { Report } from "./Report";
import { useCtx } from "../smithers";

export function TicketPipeline({ ticket }: { ticket: { id: string } }) {
  const ctx = useCtx();
  const latestReport = ctx.latest("report", `${ticket.id}:report`);
  const ticketComplete = latestReport != null;

  return (
    <Sequence key={ticket.id} skipIf={ticketComplete}>
      <ValidationLoop ticket={ticket} />
      <Report ticket={ticket} />
    </Sequence>
  );
}
```

## Running

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
cd scripts/worktree-feature
bun install
./run.sh
```

## Key Patterns

* **`createSmithers`** registers 6 output schemas; generates typed `tables`, `outputs`, and `Task` components.
* **`CodexAgent` / `ClaudeCodeAgent`** run real agents with full filesystem access.
* **Role-specific chains** route implementation to Terra and review to Sol, falling back to Claude only when Codex is unavailable or fails.
* **`<Loop>`** iterates implement/validate/review/fix until the Codex-first review seat approves or `MAX_REVIEW_ROUNDS` is exhausted.
* **`ctx.latest(schemaKey, nodeId)`** reads the highest-iteration output for a task.
* **MDX prompts** -- `.mdx` files serve as JSX-interpolated prompt templates.
* **`skipIf`** skips already-completed tickets on resume.
* **`continueOnFail`** prevents a single review failure from blocking the pipeline.
* **Dynamic ticket mapping** -- `unfinishedTickets.map()` renders one `TicketPipeline` per ticket.
