Skip to main content

SmithersProvider

The <SmithersProvider> component provides unified context to all child components, including database access, execution state, task orchestration, global timeouts, and stop conditions. It’s required at the root of every Smithers workflow. SmithersProvider includes the Ralph loop internally. The <Ralph> component is deprecated, but its loop props (maxIterations, onIteration, onComplete) are supported here and documented below.

Basic Usage

import { createSmithersDB, SmithersProvider, Claude } from "smithers-orchestrator";

const db = await createSmithersDB({ path: ".smithers/my-workflow" });
const executionId = await db.execution.start("My Workflow", "workflow.tsx");

function Workflow() {
  return (
    <SmithersProvider db={db} executionId={executionId} maxIterations={10}>
      <Claude model="sonnet">
        Do the work.
      </Claude>
    </SmithersProvider>
  );
}

Props

db
SmithersDB
required
The database instance for state persistence.
const db = await createSmithersDB({ path: ".smithers/data" });
<SmithersProvider db={db} executionId={executionId}>
executionId
string
required
Unique identifier for the current execution.
const executionId = await db.execution.start("Task", "task.tsx");
<SmithersProvider db={db} executionId={executionId}>
config
SmithersConfig
Optional configuration for the workflow.
<SmithersProvider
  db={db}
  executionId={executionId}
  config={{
    defaultModel: "sonnet",
    verbose: true,
  }}
>
maxIterations
number
default:"100"
Maximum number of iteration loops before stopping.
<SmithersProvider db={db} executionId={executionId} maxIterations={10}>
  {/* Will stop after 10 iterations */}
</SmithersProvider>
onIteration
(iteration: number) => void
Called at the start of each iteration. Useful for logging or progress tracking.
<SmithersProvider
  db={db}
  executionId={executionId}
  onIteration={(i) => console.log(`Starting iteration ${i}`)}
>
onComplete
() => void
Called when orchestration completes (all tasks finished or max iterations reached).
<SmithersProvider
  db={db}
  executionId={executionId}
  onComplete={() => console.log("Workflow finished")}
>
globalTimeout
number
Maximum time in milliseconds for the entire workflow.
<SmithersProvider db={db} executionId={executionId} globalTimeout={3600000}>
  {/* Will stop after 1 hour */}
</SmithersProvider>
stopConditions
GlobalStopCondition[]
Array of conditions that can stop the workflow early.
<SmithersProvider
  db={db}
  executionId={executionId}
  stopConditions={[
    { type: "total_tokens", value: 100000 },
    { type: "total_agents", value: 50 },
  ]}
>
snapshotBeforeStart
boolean
Create a VCS snapshot before starting (useful for rollback).
<SmithersProvider db={db} executionId={executionId} snapshotBeforeStart>
onError
(error: Error) => void
Called when workflow encounters an error.
<SmithersProvider
  db={db}
  executionId={executionId}
  onError={(err) => console.error("Workflow failed:", err)}
>
onStopRequested
(reason: string) => void
Called when a stop is requested (via stop condition or manual request).
<SmithersProvider
  db={db}
  executionId={executionId}
  onStopRequested={(reason) => console.log("Stop:", reason)}
>
cleanupOnComplete
boolean
Close the database when workflow completes.
<SmithersProvider db={db} executionId={executionId} cleanupOnComplete>

Context Values

Child components can access the context using the useSmithers hook:
import { useSmithers } from "smithers-orchestrator";

function MyComponent() {
  const {
    db,                // Database instance (SmithersDB)
    executionId,       // Current execution ID
    config,            // Configuration
    requestStop,       // Request workflow stop
    requestRebase,     // Request rebase operation
    isStopRequested,   // Check if stop was requested
    isRebaseRequested, // Check if rebase was requested
    ralphCount,        // Current iteration count
    reactiveDb,        // Raw ReactiveDatabase for useQuery
  } = useSmithers();

  return (
    <Claude model={config?.defaultModel}>
      Current iteration: {ralphCount}. Work with context access.
    </Claude>
  );
}

Task Tracking

Components can register and complete tasks using the database-backed task system:
import { useSmithers, useMount } from "smithers-orchestrator";
import { useRef } from "react";

function CustomAsyncStep() {
  const { db } = useSmithers();
  const taskIdRef = useRef<string | null>(null);

  useMount(() => {
    // Register a task with the database
    taskIdRef.current = db.tasks.start('custom-step', 'Processing data');

    fetchData().then((data) => {
      processData(data);
      // Mark task complete
      if (taskIdRef.current) {
        db.tasks.complete(taskIdRef.current);
      }
    });
  });

  return <step-node />;
}
The orchestration loop monitors the tasks table and automatically advances to the next iteration when all running tasks complete.

Request Stop

Stop the workflow programmatically:
function EmergencyStop() {
  const { requestStop, isStopRequested } = useSmithers();

  return (
    <Claude
      onFinished={(result) => {
        if (result.output.includes("CRITICAL")) {
          requestStop("Critical issue found");
        }
      }}
    >
      Check for critical issues.
    </Claude>
  );
}

function ConditionalWork() {
  const { isStopRequested } = useSmithers();

  // Don't render if stop was requested
  if (isStopRequested()) {
    return null;
  }

  return (
    <Claude>Continue working.</Claude>
  );
}

Accessing the Database

Access the database directly from any child component:
function DatabaseAccessExample() {
  const { db, executionId } = useSmithers();

  useMount(async () => {
    // Read state
    const phase = await db.state.get("phase");

    // Write state
    await db.state.set("lastAccess", Date.now());

    // Log an agent
    await db.agents.start("Custom agent", "sonnet");

    // Track a task
    const taskId = db.tasks.start("database-example", "Fetching data");
    // ... do work ...
    db.tasks.complete(taskId);
  });

  return <Claude>Work with database access.</Claude>;
}

Reactive Queries

Use reactive database queries with the useQuery hook:
import { useQuery } from "smithers-orchestrator/reactive-sqlite";

function TaskMonitor() {
  const { reactiveDb } = useSmithers();

  // Automatically re-renders when tasks table changes
  const { data: runningTasks } = useQuery(
    reactiveDb,
    "SELECT * FROM tasks WHERE status = 'running'"
  );

  return (
    <div>
      Running tasks: {runningTasks?.length ?? 0}
    </div>
  );
}

Complete Example

import { createSmithersRoot, createSmithersDB } from "smithers-orchestrator";
import { SmithersProvider, Claude, useSmithers } from "smithers-orchestrator";

async function main() {
  const db = await createSmithersDB({ path: ".smithers/example" });

  // Check for incomplete execution to resume
  let executionId: string;
  const incomplete = await db.execution.findIncomplete();

  if (incomplete) {
    executionId = incomplete.id;
    console.log("Resuming execution:", executionId);
  } else {
    executionId = await db.execution.start("Example", "example.tsx");
  }

  function Workflow() {
    return (
      <SmithersProvider
        db={db}
        executionId={executionId}
        maxIterations={10}
        globalTimeout={3600000}
        stopConditions={[
          { type: "total_tokens", value: 100000 }
        ]}
        onIteration={(i) => console.log(`Iteration ${i}`)}
        onComplete={() => console.log("Workflow complete")}
      >
        <WorkflowContent />
      </SmithersProvider>
    );
  }

  const root = createSmithersRoot();
  await root.mount(Workflow);

  await db.execution.complete(executionId);
  await db.close();
}

function WorkflowContent() {
  const { db, reactiveDb, requestStop, isStopRequested } = useSmithers();
  
  const phase = useQueryValue<string>(
    reactiveDb,
    "SELECT value FROM state WHERE key = 'phase'"
  ) ?? "start";
  
  const setPhase = (p: string) => db.state.set('phase', p);

  if (isStopRequested()) {
    return <Stop reason="Stop requested" />;
  }

  return (
    <>
      <If condition={phase === "start"}>
        <Claude onFinished={() => setPhase("done")}>
          Complete the task.
        </Claude>
      </If>
    </>
  );
}

main();

Best Practices

All Smithers components require the provider:
// Correct
<SmithersProvider db={db} executionId={executionId}>
  <Claude>...</Claude>
</SmithersProvider>

// Error - no context
<Claude>...</Claude>
Always set a reasonable iteration limit:
<SmithersProvider
  db={db}
  executionId={executionId}
  maxIterations={10}  // Prevents infinite loops
>
Register async work with the database for proper orchestration:
const taskId = db.tasks.start('async-work');
try {
  await doAsyncWork();
} finally {
  db.tasks.complete(taskId);
}
Ensure writes are flushed:
const root = createSmithersRoot();
await root.mount(Workflow);
await db.close();  // Always close