claude-plugin/. Installing it wires four things into every Claude Code session:
- The
smithersMCP server (bunx smthrs --mcp):list_workflows,run_workflow,watch_run,resolve_approval,ask_human, and the rest of the semantic tool surface. - The
smithersskill: teaches Claude to run durable, multi-step, or background work through Smithers instead of hand-rolled subagent trees, and to consulthttps://smithers.sh/llms-full.txtbefore writing any workflow code. - The
/workflowsmirror: a generic Workflow-tool script,workflows/smithers-run.mjs, that launches or attaches to a Smithers run and mirrors it live into Claude Code’s/workflowsprogress tree, with zero per-workflow setup. - A background monitor (experimental):
smithers claude monitorstreams approval, human-input, failure, stalled-heartbeat, and retry-churn notifications into the session even with no mirror running, and emits a periodicrun-progressdigest whenever a followed run has produced no notification for a full window, so a detached run that stays alive but keeps retrying can never go hours without handing the session a status to relay. It follows only runs this session subscribed to, either automatically (smithers claude tick, or a Claude-launched detachedup/workflow run/MCPrun_workflow) or explicitly (smithers claude subscribe RUN_ID). The workspace store is shared across sessions, so skipping this scope would wake each session for every other session’s runs and for pre-existing runs at startup (--all-runsrestores that unscoped behavior); finished and cancelled runs also stay unbroadcast by default, since a followed run already reports completion through its/workflowsmirror (--transitions allstreams them anyway).
The /workflows mirror
Claude Code’s/workflows tree can only be populated from inside a session, by a Workflow-tool script; the plugin ships one generic script for this. Claude launches it like this:
args: { runId: "run-123" } instead attaches to an existing run (after a restart, or for a run started elsewhere). The SessionStart hook announces the script’s absolute path in every .smithers project, with the count of non-terminal runs to re-attach to.
New mirror launches automatically persist startedBy.harness = "claude-code"
and the native CLAUDE_CODE_SESSION_ID when available. An optional
args.startedByPrompt is the sole route for an attribution prompt; workflow
input and transcripts are never copied into durable attribution. Attaching by
runId leaves the original attribution unchanged.
What the mirror renders:
- Phases from the workflow’s real container tree (
Workflow name,Sequence label,Parallel label, loop containers), labeled from the run’s persisted frames, so data-dependent fan-outs and loop iterations appear as they materialize. - One row per node: agent, human, approval, and dynamically discovered nodes by default (
mirrorAllNodes: trueinargsadds compute/static rows). A running node gets a live watcher row resolving with its output; a node finished before a slot freed gets a completion row instead. - Approval and human-request banners as narrator lines, with the exact resolving command.
- Continue-as-new: the mirror switches to a run’s new runId automatically.
- Caps: at most 6 live watcher rows at once (the sync loop must never queue behind its own watchers), a 900-agent budget, and per-phase summary rows above 150 nodes; every cap logs when it engages.
bunx smthrs cancel RUN_ID (or the gateway cancelRun RPC).
The smithers claude protocol
The mirror’s agents each run one CLI command and relay its JSON; all derivation and blocking happen inside the CLI, against the durable store, under a frozen wire contract ("contract": 1 in every response):
seq), the latest frame’s phase plan, every node as {nodeId, label, phase, kind, state}, the nodeIds changed after --after-seq, truncated outputs of newly-terminal nodes, pending approvals and human requests, and continuedAs for continued runs. --wait blocks until a mirror-relevant event lands (tool-call and heartbeat chatter is ignored), the run reaches a terminal status, or the timeout expires ("timedOut": true, exit 0); it also waits for a not-yet-existing store or run, so a mirror launched right after workflow run --detach can’t race the engine’s first write. Ticking a non-terminal run also records the session’s subscription for claude monitor.
{state, output, vanished, timedOut}. A node pruned by a later frame reports state: "skipped", vanished: true. Watchers re-invoke on timedOut: true: how a row outlives the 10-minute Bash cap on multi-hour nodes.
approval-pending, human-request, run-finished, run-failed, run-cancelled, run-continued, run-stalled, node-retrying), each with a summary and the resolving command. Subscriptions are recorded in .smithers/claude-mirror-subscriptions.json, keyed by the CLAUDE_CODE_SESSION_ID each command sees. It skips history replay, surfaces already-pending gates once at startup, and prunes a subscription once its run turns terminal. In a project without a Smithers store, it exits silently.
Two of those lines exist because silence is itself a failure mode: an alive run grinding through retries crosses no failed or stalled transition, so nothing would ever wake the session. node-retrying fires when an active attempt reaches --retry-alert-attempt (default 3, re-firing on each later attempt). run-progress fires whenever a followed non-terminal run has written no line for --progress-every-ms (default 30 minutes), reporting elapsed time, the active nodes with their attempt numbers, and the checking command. When one of these lands, relay a short status to the user before deciding whether to intervene.
subscribe points the session’s monitor at a run it didn’t start (validating the run exists); unsubscribe silences one. Both are session-scoped when CLAUDE_CODE_SESSION_ID is set; from a plain terminal, unsubscribe drops the run for every session.
These commands are protocol surface for the plugin: keep responses additive. A breaking shape change bumps contract; the shipped script refuses mismatched majors with an actionable message.
Install
From the repository checkout (or a marketplace once published):bunx smthrs, and the first invocation bootstraps the CLI from npm. In a project without .smithers/, the skill initializes Smithers before the first run.