smithers-orchestrator
Author Smithers (smithers-orchestrator) Bun/TSX JSX workflows for Claude Code: durable, resumable, observable orchestration with SQLite state (phases/steps), tool-scoped agents, optional parallelism, and workflow logs.
Installation
Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.
View source ↗GuidesSkill instructions
Smithers Orchestrator (smithers-orchestrator)
JSX workflow engine for Claude Code: render → execute active nodes → persist to SQLite → rerender until stable/stop. Durable by default when you store control-flow in db.state (SQLite), not React useState. maxIterations is mandatory for safety.
Use when
- Multi-step repo automation (PR finalize, CI recovery, release smoke tests, refactors)
- Needs resume after interruption (power/network/tool failures)
- Needs observability (phases/steps/tools/agents logged + NDJSON streams)
- Needs constrained tool permissions per agent step
Fast requirements (ask once, then write code)
- Goal + acceptance checks (tests pass? lint clean? diff reviewed?)
- Allowed tools per step (Read/Write/Edit/Bash/…)
- Stop limits:
maxIterations, optional timeout + stop conditions - Branching needs: retries, human checkpoint(s), optional phases (
skipIf)
Setup
Claude Code plugin install (optional; for end-users running Smithers inside Claude Code)
/plugin marketplace add evmts/smithers
/plugin install smithers@smithers
Project install + TSX config (Bun)
bun add smithers-orchestrator
Option A: tsconfig.json (recommended)
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "smithers-orchestrator"
}
}
Option B: Per-file pragma
If not using tsconfig, add this pragma at the top of each .tsx file:
/** @jsxImportSource smithers-orchestrator */
bunfig.toml (optional, for dev mode)
[dev]
jsx = "react"
Canonical workflow template (resumable, phases+steps)
Create workflows/smithers-example.tsx:
#!/usr/bin/env bun
import {
createSmithersDB,
createSmithersRoot,
SmithersProvider,
Phase,
Step,
Claude,
} from "smithers-orchestrator";
const db = createSmithersDB({ path: ".smithers/smithers-example" });
// Resume if an execution is incomplete; otherwise start a new one.
const existing = db.execution.findIncomplete();
const executionId =
existing?.id ??
db.execution.start({
name: "Smithers Example",
script: "workflows/smithers-example.tsx",
});
function Workflow() {
return (
<SmithersProvider
db={db}
executionId={executionId}
maxIterations={25}
globalTimeout={30 * 60 * 1000}
>
<Phase name="Plan">
<Step name="scan">
<Claude model="sonnet" allowedTools={["Read", "Glob", "Grep"]}>
Inspect the repo and produce a short plan:
- key files
- risks
- commands to run
</Claude>
</Step>
</Phase>
<Phase name="Implement">
<Step name="changes">
<Claude model="sonnet" allowedTools={["Read", "Edit", "Write"]}>
Implement the planned changes.
Keep edits minimal and consistent with project style.
</Claude>
</Step>
</Phase>
<Phase name="Verify">
<Step name="tests">
<Claude model="sonnet" allowedTools={["Bash", "Read"]}>
Run the project's tests and fix failures until green.
</Claude>
</Step>
</Phase>
<Phase name="Report">
<Step name="summary">
<Claude model="haiku" allowedTools={["Read"]}>
Summarize what changed and how it was verified.
</Claude>
</Step>
</Phase>
</SmithersProvider>
);
}
const root = createSmithersRoot({ db, executionId });
await root.mount(Workflow);
db.close();
Run:
bun workflows/smithers-example.tsx
Notes:
- DB APIs are synchronous; close DB at end.
- Resume relies on
db.execution.findIncomplete().
Execution model (what to rely on)
Durable iteration ("Ralphing") rules
- Workflow progresses via rerenders; each rerender is an "iteration".
- Always set
maxIterations(default is 100 if you omit it). - For durable branching/retry: store state in
db.state(SQLite); read with reactive queries (useQueryValue, etc.) rather than ReactuseState.
Phases + Steps (sequential semantics)
- A
<Phase>auto-advances when all child<Step>s complete. - Phase requires ≥1 Step to auto-advance; otherwise it will not progress without manual logic (
onComplete+ your own state). - Only the active phase renders children under the registry context (provided by
SmithersProvider).
Parallel (experimental)
-
<Parallel>runs children concurrently inside a Step; semantics are experimental and have known limitations.
Task (presentational)
-
<Task>is just a checkbox-like rendering element; it is notdb.tasks.*.
SQLite state (db.state): minimal contract
State values are JSON-serialized on write and parsed on read (get, getAll). History stores old/new as JSON strings; add optional trigger strings for causality/audit.
| Method | Use |
|---|---|
db.state.get<T>(key) |
read (parsed) |
db.state.set(key, value, trigger?) |
write + optional cause |
db.state.setMany(obj, trigger?) |
atomic multi-write |
db.state.getAll() |
full state object |
db.state.history(key?, limit?) |
transitions (old/new JSON strings) |
db.state.has(key) / db.state.delete(key, trigger?) / db.state.reset() |
utilities |
Logging & monitoring (where to look)
Default log layout: .smithers/logs/ plus execution-scoped logs under .smithers/executions/<execution-id>/logs/ including stream.ndjson + stream.summary.json.
NDJSON event types:
-
text-end,reasoning-end,tool-call,tool-result,error
Optional summarization (large outputs) via Haiku:
-
ANTHROPIC_API_KEYrequired - Thresholds:
SMITHERS_SUMMARY_THRESHOLD(default 50 lines),SMITHERS_SUMMARY_CHAR_THRESHOLD(default 4000),SMITHERS_SUMMARY_MAX_CHARS(default 20000) - Model:
SMITHERS_SUMMARY_MODEL(defaultclaude-3-haiku-20240307)
Anti-Patterns (CRITICAL)
Stop Conditions Are An Anti-Pattern
Hill climbing never stops - it either fails to climb or hits diminishing returns. Don't ask "are we done?"
// ❌ BAD: Explicit "done" check
<Ralph condition={() => !isDone()}>...</Ralph>
// ✅ BETTER: Stop when no improvement in last N runs
<Ralph condition={() => recentImprovements.slice(-5).some(i => i > 0)}>
Over-Engineering Phases Reduces LLM Flexibility
Agents are capable. Give them large tasks with related context. Don't micromanage into tiny steps.
// ❌ BAD: Over-engineered into many phases
<Phase><Claude>Analyze</Claude></Phase>
<Phase><Claude>Plan</Claude></Phase>
<Phase><Claude>Implement step 1</Claude></Phase>
<Phase><Claude>Implement step 2</Claude></Phase>
<Phase><Claude>Test</Claude></Phase>
<Phase><Claude>Review</Claude></Phase>
// ✅ BETTER: Single comprehensive prompt
<Claude>{`Implement feature X with tests.
Analyze patterns, follow conventions, ensure 80% coverage.
Report: files changed, tests added, coverage delta.`}</Claude>
// ✅ OR: Ralph for iterative improvement
<Ralph condition={coverageBelow80} maxIterations={10}>
<Claude>{`Improve coverage to 80%. Report what you improved.`}</Claude>
</Ralph>
When Phases DO Make Sense
Only when context completely shifts (different language, different codebase, no shared context):
// ✅ GOOD: Distinct context shifts
<Phase name="backend"><Claude>Implement Go API</Claude></Phase>
<Phase name="frontend"><Claude>Implement React UI</Claude></Phase>
Rule of Thumb
If you're writing more than 3 phases, you're over-engineering. Give the agent a comprehensive prompt and let it ralph.
Agent Components
Claude
Claude CLI agent for AI tasks.
<Claude
model="sonnet" // "sonnet" | "opus" | "haiku"
allowedTools={["Read", "Bash"]}
maxTurns={10}
permissionMode="bypassPermissions"
>
Your prompt here
</Claude>
Amp
Amp CLI agent (alternative to Claude CLI). Uses amp CLI under the hood.
<Amp
mode="smart" // "smart" (SOTA) | "rush" (faster/cheaper)
maxTurns={50} // handled via prompt, not CLI flag
permissionMode="bypassPermissions"
systemPrompt="Be concise"
labels={["feature", "urgent"]}
>
Your prompt here
</Amp>
| Prop | Type | Default | Description |
|---|---|---|---|
children |
ReactNode |
- | The prompt text |
mode |
'smart' | 'rush' |
'smart' |
Agent mode |
maxTurns |
number |
- | Turn limit (prompt-injected) |
systemPrompt |
string |
- | System prompt |
permissionMode |
'default' | 'acceptEdits' | 'bypassPermissions' |
'default' |
Permission handling |
timeout |
number |
- | Timeout in ms |
cwd |
string |
- | Working directory |
continueThread |
boolean |
- | Continue from last thread |
resumeThread |
string |
- | Resume specific thread ID |
labels |
string[] |
- | Thread labels |
Guardrails for agents generating Smithers code
-
Prefer single
<Claude>with comprehensive prompt over many phases for related tasks. - Use
<Ralph>for iterative improvement with improvement-based stop conditions (not "done" checks). - Use phases only when context completely shifts (different language/codebase).
- Use
db.statefor any branching/retry/human checkpoints. - Scope tool permissions per
<Claude>or<Amp>call and choose conservativepermissionModeby default. - Never omit
maxIterations.
Pointer files
This plugin is intentionally "single manual": full patterns live in EXAMPLES.md (end-to-end runnable scripts).
Examples
Read the usage examples in the source repository.
View source ↗Source history
- Indexed on
- 2026-01-20
- Index updated
- 2026-01-27
- Branch
- main
Index and sync dates describe this directory. View the source repository for code history.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!