Phases API
The db.phases module tracks workflow phases. Phases group related steps and provide high-level progress tracking.
Contract
- Sync: All methods are synchronous.
- Return shape: raw rows with snake_case fields.
- Time: Timestamps are ISO strings (not
Date).
Overview
import { createSmithersDB } from "smithers-orchestrator";
const db = createSmithersDB({ path: ".smithers/my-workflow" });
// Start a phase
const phaseId = db.phases.start("implementation", 0);
// Complete the phase
db.phases.complete(phaseId);
Methods
start
Starts a new phase.
function start(name: string, iteration?: number): string
The Ralph iteration number (for tracking across loop iterations).
Returns: The phase ID.
complete
Marks a phase as completed.
function complete(id: string): void
The phase ID to complete.
fail
Marks a phase as failed.
function fail(id: string): void
current
Gets the currently active phase.
function current(): Phase | null
list
Lists all phases for an execution.
function list(executionId: string): Phase[]
Phase Type
interface Phase {
id: string;
execution_id: string;
name: string;
iteration: number;
status: 'pending' | 'running' | 'completed' | 'skipped' | 'failed';
started_at: string | null;
completed_at: string | null;
duration_ms: number | null;
agents_count: number;
created_at: string;
}
Usage with the Phase Component
The <Phase> component automatically integrates with this module:
import { Phase, Step } from "smithers-orchestrator";
<SmithersProvider db={db} executionId={executionId}>
<Phase name="research">
<Step name="research">
<Claude>Research the topic.</Claude>
</Step>
</Phase>
<Phase name="implementation">
<Step name="implement">
<Claude>Implement the feature.</Claude>
</Step>
</Phase>
<Phase name="testing">
<Step name="test">
<Claude>Write and run tests.</Claude>
</Step>
</Phase>
</SmithersProvider>
Multi-Iteration Phases
Phases track their iteration number for workflows that loop:
<SmithersProvider db={db} executionId={executionId} maxIterations={5}>
<Phase name="implement">
{/* This phase will be tracked with iteration 0, 1, 2, etc. */}
<Step name="implement">
<Claude>Implement the feature.</Claude>
</Step>
</Phase>
</SmithersProvider>
Query phases by iteration:
const phases = db.phases.list(executionId);
// Group by iteration
const byIteration = phases.reduce((acc, phase) => {
if (!acc[phase.iteration]) acc[phase.iteration] = [];
acc[phase.iteration].push(phase);
return acc;
}, {});
// Check iteration 2 progress
const iteration2 = byIteration[2] || [];
const completed = iteration2.filter((p) => p.status === "completed");
State-Driven Phases
Common pattern with database state:
import { useSmithers } from "smithers-orchestrator";
import { useQueryValue } from "smithers-orchestrator/reactive-sqlite";
function StateDrivenWorkflowBody() {
const { db, reactiveDb } = useSmithers();
const { data: phaseJson } = useQueryValue<string>(
reactiveDb,
"SELECT value FROM state WHERE key = 'currentPhase'"
);
const phase = phaseJson ? JSON.parse(phaseJson) : null;
return (
<>
<If condition={phase === "implement"}>
<Phase name="Implementation">
<Step name="implement">
<Claude onFinished={() => db.state.set("currentPhase", "review")}>
Implement the feature.
</Claude>
</Step>
</Phase>
</If>
<If condition={phase === "review"}>
<Phase name="Review">
<Step name="review">
<Review
onFinished={(r) => {
if (r.approved) {
db.state.set("currentPhase", "complete");
} else {
db.state.set("currentPhase", "implement");
}
}}
/>
</Step>
</Phase>
</If>
</>
);
}
export function StateDrivenWorkflow({ db, executionId }) {
return (
<SmithersProvider db={db} executionId={executionId} maxIterations={10}>
<StateDrivenWorkflowBody />
</SmithersProvider>
);
}
Querying Phases
// Get current phase
const currentPhase = db.phases.current();
if (currentPhase) {
console.log(`Current phase: ${currentPhase.name}`);
}
// List all phases
const allPhases = db.phases.list(executionId);
// Calculate total duration
const totalDuration = allPhases
.filter((p) => p.duration_ms)
.reduce((sum, p) => sum + p.duration_ms!, 0);
// Find failed phases
const failed = allPhases.filter((p) => p.status === "failed");