Skip to main content

State API

The state API provides key-value storage with full history tracking.

Contract

  • Sync: All methods are synchronous.
  • Storage: Values are JSON-serialized on write and parsed on read (get, getAll).
  • History: old_value and new_value are stored as JSON strings; timestamps are ISO strings.

Methods

All State API methods are synchronous (use SQLite’s synchronous API via bun:sqlite).

get

Get a value by key.
const phase = db.state.get<string>("phase");
// "review" | null

set

Set a value with optional trigger.
db.state.set("phase", "review");

// With trigger for tracking what caused the change
db.state.set("phase", "review", "tests_passed");

setMany

Set multiple values atomically.
db.state.setMany({
  phase: "complete",
  completedAt: Date.now(),
  result: { success: true },
});

// With trigger
db.state.setMany(
  { phase: "review", reviewer: "claude" },
  "implementation_done"
);

getAll

Get all state as an object.
const state = db.state.getAll();
// { phase: "review", attempts: 3, data: {...} }

reset

Clear all state.
db.state.reset();

history

Get state transition history.
// History for a specific key (positional args: key, limit)
const phaseHistory = db.state.history("phase", 10);
// [
//   { id: "t1", key: "phase", old_value: null, new_value: "start", trigger: "init", ... },
//   { id: "t2", key: "phase", old_value: "start", new_value: "review", trigger: "tests_passed", ... },
// ]

// All history (no key filter)
const allHistory = db.state.history(undefined, 50);

// Default: last 100 transitions
const recentHistory = db.state.history();

has

Check if a key exists in state.
if (db.state.has("phase")) {
  console.log("Phase key exists");
}

delete

Delete a key from state (with optional trigger for tracking).
db.state.delete("tempData");

// With trigger for auditing
db.state.delete("cache", "cleanup");

Usage Patterns

Initialize from Database

import { useSmithers } from "smithers-orchestrator";
import { useQueryValue } from "smithers-orchestrator/reactive-sqlite";

function WorkflowBody() {
  const { db, reactiveDb } = useSmithers();
  const { data: phaseJson } = useQueryValue<string>(
    reactiveDb,
    "SELECT value FROM state WHERE key = 'phase'"
  );
  const phase = phaseJson ? JSON.parse(phaseJson) : "start";

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

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

export function Workflow({ db, executionId }) {
  return (
    <SmithersProvider db={db} executionId={executionId} maxIterations={10}>
      <WorkflowBody />
    </SmithersProvider>
  );
}

Track Causality with Triggers

<Claude
  onFinished={(result) => {
    if (result.output.includes("PASS")) {
      db.state.set("phase", "complete", "tests_passed");
    } else {
      db.state.set("phase", "retry", "tests_failed");
    }
  }}
>
  Run tests.
</Claude>

Manual Checkpoint and Rollback

// Save checkpoint
const checkpoint = db.state.getAll();

// Try risky operation
try {
  riskyOperation();
  db.state.set("status", "success");
} catch (error) {
  // Roll back by resetting and restoring keys
  db.state.reset();
  db.state.setMany(checkpoint, "error_rollback");
}

Types

interface StateModule {
  get: <T>(key: string) => T | null;
  set: <T>(key: string, value: T, trigger?: string) => void;
  setMany: (updates: Record<string, unknown>, trigger?: string) => void;
  getAll: () => Record<string, unknown>;
  reset: () => void;
  history: (key?: string, limit?: number) => Transition[];
  has: (key: string) => boolean;
  delete: (key: string, trigger?: string) => void;
}

interface Transition {
  id: string;
  execution_id?: string;
  key: string;
  old_value: string | null;
  new_value: string;
  trigger?: string;
  trigger_agent_id?: string;
  created_at: string;
}