Phase Component
The <Phase> component groups related steps semantically, providing structure and observability to workflows.
Basic Usage
import { Phase, Step } from "smithers-orchestrator";
<Phase name="Research">
<Step name="analyze">
<Claude allowedTools={["Read", "Glob", "Grep"]}>
Research the codebase.
</Claude>
</Step>
</Phase>
A Phase requires at least one <Step> child to advance automatically. Phases advance when all Steps complete. Without Steps, use onComplete with manual phase progression.
Props
Name of the phase for logging and tracking. Must be unique within the orchestration.<Phase name="Implementation">
<Step name="code">
<Claude>Implement the feature.</Claude>
</Step>
</Phase>
Skip this phase if condition returns truthy. Takes precedence over automatic state management.const hasTests = useQueryValue(db.db, "SELECT value FROM state WHERE key = 'has_tests'");
<Phase name="WriteTests" skipIf={() => hasTests}>
<Step name="tests">
<Claude>Write unit tests.</Claude>
</Step>
</Phase>
Callback when phase starts (becomes active).<Phase
name="Research"
onStart={() => console.log("Starting research phase")}
>
<Step name="analyze">
<Claude>Research the codebase.</Claude>
</Step>
</Phase>
Callback when phase completes.<Phase
name="Research"
onComplete={() => db.state.set('research_done', 'true')}
>
<Step name="analyze">
<Claude>Research the codebase.</Claude>
</Step>
</Phase>
Runtime Semantics
Understanding how phases execute is critical for building correct workflows:
┌─────────────────────────────────────────────────────────────────┐
│ PhaseRegistryProvider (SmithersProvider/Ralph) │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Phase 1 [active] → children rendered ││
│ │ └─ Step 1.1 [done] ││
│ │ └─ Step 1.2 [running] → when done, Phase 1 advances ││
│ ├─────────────────────────────────────────────────────────────┤│
│ │ Phase 2 [pending] → children NOT rendered ││
│ │ └─ Step 2.1 ││
│ ├─────────────────────────────────────────────────────────────┤│
│ │ Phase 3 [pending] → children NOT rendered ││
│ │ └─ Step 3.1 ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
Key behaviors:
-
Phases advance when all Steps complete - The phase monitors its child Steps via
StepRegistryProvider. When every Step reaches completion, the phase automatically advances to the next.
-
A Phase with no Steps will not automatically advance - Without Steps, the phase has no completion signal. Use
onComplete callback with manual phase control if you need stepless phases.
-
Only the active phase renders children - Pending/completed phases render their
<phase> element in output (for observability) but skip child execution.
-
Must be under PhaseRegistryProvider - Phases require context from
SmithersProvider or Ralph. Without it, all phases render as active (no sequencing).
-
Sequential by default - Phases execute in declaration order. Use
skipIf to conditionally skip phases.
Multi-Phase Workflow
import { useSmithers, If } from "smithers-orchestrator";
import { useQueryValue } from "smithers-orchestrator/reactive-sqlite";
function FeatureWorkflow() {
const { db } = useSmithers();
const phase = useQueryValue<string>(
db.db,
"SELECT value FROM state WHERE key = 'phase'"
) ?? "research";
const setPhase = (p: string) => db.state.set('phase', p);
return (
<SmithersProvider db={db} executionId={executionId} maxIterations={10}>
<If condition={phase === "research"}>
<Phase name="Research">
<Step name="find-files">
<Claude
allowedTools={["Glob", "Grep"]}
onFinished={() => setPhase("implement")}
>
Find relevant files.
</Claude>
</Step>
</Phase>
</If>
<If condition={phase === "implement"}>
<Phase name="Implementation">
<Step name="write-code">
<Claude
allowedTools={["Edit", "Write"]}
onFinished={() => setPhase("test")}
>
Implement the solution.
</Claude>
</Step>
</Phase>
</If>
<If condition={phase === "test"}>
<Phase name="Testing">
<Step name="run-tests">
<Claude
allowedTools={["Bash"]}
onFinished={(r) => {
if (r.output.includes("PASS")) {
setPhase("done");
} else {
setPhase("implement");
}
}}
>
Run the tests.
</Claude>
</Step>
</Phase>
</If>
</SmithersProvider>
);
}
Database Tracking
Phases are tracked in the database:
// Phases are automatically tracked when using SmithersProvider
const phases = await db.phases.list({ executionId });
// [
// { id: "p1", name: "Research", status: "complete", ... },
// { id: "p2", name: "Implementation", status: "running", ... },
// ]