Skip to main content

SmithersProvider

The <SmithersProvider> component provides database and execution context to all child components. It’s required at the root of every Smithers workflow.

Basic Usage

import { createSmithersDB } from "smithers/smithers-orchestrator/src/db";
import { SmithersProvider } from "smithers/smithers-orchestrator/src/components/SmithersProvider";

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}>
      <Claude model="sonnet">
        Do the work.
      </Claude>
    </SmithersProvider>
  );
}

Props

db
SmithersDB
required
The PGlite 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",
    maxRetries: 3,
    verbose: true,
  }}
>

Context Values

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

function MyComponent() {
  const {
    db,              // Database instance
    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
  } = useSmithers();

  return (
    <Claude onFinished={() => {
      if (someCondition) {
        requestStop();
      }
    }}>
      Work with context access.
    </Claude>
  );
}

Request Stop

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

  return (
    <Claude
      onFinished={(result) => {
        if (result.output.includes("CRITICAL")) {
          requestStop();
        }
      }}
    >
      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>
  );
}

Request Rebase

Request a VCS rebase operation:
function RebaseWorkflow() {
  const { requestRebase, isRebaseRequested } = useSmithers();

  return (
    <Claude
      onFinished={(result) => {
        if (result.output.includes("conflicts detected")) {
          requestRebase();
        }
      }}
    >
      Check for merge conflicts.
    </Claude>
  );
}

Accessing the Database

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

  onMount(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");
  });

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

Complete Example

import { createSmithersRoot } from "smithers";
import { createSmithersDB } from "smithers/smithers-orchestrator/src/db";
import { SmithersProvider, useSmithers } from "smithers";

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}>
        <Orchestration globalTimeout={3600000}>
          <WorkflowContent />
        </Orchestration>
      </SmithersProvider>
    );
  }

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

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

function WorkflowContent() {
  const { db, requestStop, isStopRequested } = useSmithers();
  const [phase, setPhase] = createSignal("start");

  onMount(async () => {
    const savedPhase = await db.state.get("phase");
    if (savedPhase) setPhase(savedPhase);
  });

  const updatePhase = async (newPhase: string) => {
    setPhase(newPhase);
    await db.state.set("phase", newPhase);
  };

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

  return (
    <Ralph maxIterations={10}>
      {phase() === "start" && (
        <Claude onFinished={() => updatePhase("done")}>
          Complete the task.
        </Claude>
      )}
    </Ralph>
  );
}

main();

Best Practices

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

// Error - no context
<Claude>...</Claude>
Ensure writes are flushed:
const root = createSmithersRoot();
await root.mount(Workflow);
await db.close();  // Always close
Resume interrupted workflows:
const incomplete = await db.execution.findIncomplete();
const executionId = incomplete?.id ??
  await db.execution.start("New", "file.tsx");