Skip to main content
Smithers 0.34.0 puts you in the cockpit of a running workflow. Mirror a run into a herdr terminal workspace and supervise it from the same terminal as your coding agent: a pane per agent node, a cockpit outline of the whole graph, and a dual-control dock that lets you steer a running agent with one keystroke or hijack its session and drive it yourself. Smithers keeps owning execution, isolation, and durability; herdr only renders and relays, and everything degrades to a silent no-op when no herdr server is running. 0.34.0 also makes agent edits legible. Every CLI harness reported file edits differently, so the run UI could only show a flat “Edited src/foo.ts” note. Agents now normalize a file-mutating tool call into one AgentFileChange shape, the node chat stream renders each confirmed change as an expandable diff, and the Diff tab works while the run is still executing instead of only after it finishes. The rest of the release is volume: 254 commits since 0.33.0, 97 of them fixes for the places daily production use exposed cracks, notably resuming and retrying runs, agent fallback chains, steer delivery, Gateway workflow-UI discovery, and a run status that stops overstating how healthy a run is. (0.33.1 was tagged but never published to npm; everything it contained ships here.) Disk use is now bounded and inspectable end to end. Active detached logs cap themselves at 100 MiB with a preserved tail and truncation notice, while the new bunx smthrs gc reports filesystem capacity and reclaims terminal-run logs (including workflow-adjacent legacy logs), sandbox roots, and worktrees behind age and ownership guards. Legacy hidden campaign worktrees and known temp scratch shapes are surfaced but need an explicit --include-unmanaged; live process directories and unpublished worktree changes remain protected.

Upgrading

Behavior changes are collected in the upgrade notes.

Live workflow supervision: steer, hijack, and a terminal cockpit

A long-running workflow used to be observable but not steerable: you could watch the logs, or kill the run. 0.34.0 adds a supervision layer with three levels of control. Steer. bunx smthrs steer RUN_ID "focus on the failing test first" queues a durable instruction that lands as a user turn at the target node’s next agent step; the run never stops. --node picks a specific node (the default is the run’s current agent node), bare bunx smthrs steer auto-picks the single active run, and delivery is tracked through SteerQueued and SteerConsumed events with expiry at terminal states. Steers survive restarts because they live in the store (_smithers_steers), not in process memory. Take over. bunx smthrs steer --takeover parks the run resumably and hands you the agent’s live session to drive yourself. It is run-wide, so it warns and asks before aborting in-flight sibling agents. Cockpit. bunx smthrs supervisor (alias top) opens a live outline of phases and agents: j/k to select, Enter to open a node detail tab (in herdr when available), an approve/deny surface for a waiting gate, and [ ] to switch runs. It sources through the workspace Gateway by default and silently falls back to direct smithers.db reads when none is reachable. With a herdr terminal server running, bunx smthrs herdr attach mirrors the run into a terminal workspace: a workspace per run, a cockpit tab, and a tab or pane per agent node with attention promotion and terminal outcome markers. bunx smthrs herdr status|attach|open|clean manage the mirror. The client speaks herdr wire protocol 19 (herdr 0.8.0); a protocol mismatch fails closed before any mutating call, and the optional mirror paths degrade to silent no-ops, so a missing or older herdr never affects the run itself. Reasoning effort is now first class along the way: it is derived at spawn, persisted per attempt (_smithers_attempts.effort), and rendered on both the direct-db and gateway display paths.

Agent file edits are one contract, and the UI renders them as diffs

A CLI agent subclass may now implement parseFileChanges, which normalizes a file_change action into AgentFileChange records. The same normalization runs live inside createOutputInterpreter, which attaches the result to the streamed action as detail.fileChanges.
Claude Code and Kimi carry before and after text, so their edits reconstruct a real unified diff. Codex, OpenCode, Cursor, and Amp report paths and kinds only. Each engine declares what it can do in AgentCapabilityRegistry.fileChanges (supportsFileChanges and supportsUnifiedDiff); the per-engine table is in the agents reference. In the node chat stream that means:
  • A change with a diff is an expandable row that opens the hunks inline.
  • A paths-only change renders as a plain row labelled diff unavailable rather than a focusable button that does nothing when you click it.
  • A started edit stays hidden until the tool result confirms it, so a denied or failed edit no longer looks applied.
  • Claude’s Write reconstructs a full-creation diff only once the result confirms the file was new. Over an existing file it stays paths-only instead of fabricating an empty old side, and NotebookEdit reconstructs inserts only.
  • Kimi’s WriteFile keeps its file-change kind through tool-result completion, so the pending change finalizes instead of collapsing into a generic tool row.
Recorded CLI transcripts for Claude Code, Codex, and OpenCode are checked in as fixtures and replayed through the real adapters in tests, from raw wire lines all the way to finalized chat items, so a vendor stream-shape change breaks a test instead of a UI.

Diffs while the run is still executing

getRunDiff and getNodeDiff refused until a run reached a terminal status, so the Diff tab stayed empty for exactly as long as you cared about it. While a run is live, both now diff the run base against the current working copy of every checkout an attempt recorded, read-only, never cached, under the same 50MB oversized cap that produces DiffTooLarge. The bundle is flagged live: true and surfaces as a (live) marker in the summary. The terminal base-to-terminal diff stays the authoritative final snapshot. The oneshot Diff tab and the TUI tree-mode diff tab refresh off run events with a trailing debounce, because agent edits arrive in bursts, and take one final refetch on the terminal transition. A refresh no longer flickers the existing diff out for a spinner.

One account’s rate limit no longer stalls a run

The new fallbackAgents() turns the account registry into a ready-made failover chain: one agent rung per registered Claude Code or Codex subscription, shuffled per call so load spreads across seats, with your normal agent appended as the last rung. On a machine with no registered seats it returns just that agent, so single-account setups behave exactly as before.
Seed the shuffle with ctx.runId and a run keeps the same chain across every render and retry, which keeps the engine’s per-rung quota skipping precise, while different runs still start from different seats. providers: "all" extends the pool to Kimi, Antigravity, and raw API-key accounts, and models/agentOptions apply per-provider overrides without ever letting a rung be repointed at another subscription. Oneshot builds the same shape from the same registry, in registration order rather than shuffled: bunx smthrs agents add regenerates .smithers/agents.ts so every usable registered account is its own chain rung inside its engine’s pool, and the run walks those rungs before it moves on to the next engine. Adding a second registration for a subscription you already registered is called out at add time now, because a duplicate seat adds no capacity, only confusion; the check compares within one provider, so a Claude seat and a Codex seat that happen to share a sign-in email stay separate rungs with separate rate limits.

Register every subscription you own, browser login included

bunx smthrs agents add --tmux handles the providers that only offer a browser login. It launches the provider CLI in a detached tmux session with the account’s config dir set (Claude Code via claude auth login --claudeai, skipping REPL onboarding), prints the attach command so you can complete the login in the browser, polls until the credentials land, and registers the seat. Re-running after a timeout detects already-finished credentials and just registers. The interactive agents add wizard offers the same flow whenever tmux is available.
agents add also records who was signed in, and agents list shows that account identity next to each label, so two labels backed by the same subscription (and therefore the same rate limit) are visible at a glance instead of looking like extra capacity. Registered agent ids are stable now: registeredAgentId/registeredAgentLabel live in @smthrs/accounts as the canonical home, and the CLI and runtime helpers stamp identical account-backed ids.

Subscription headroom across every registered seat

The new private apps/quota-dashboard workspace app is a compact Electrobun desktop view over the real bunx smthrs usage --format json output. It charts the weekly and session headroom for every registered Claude Code and Codex seat, sorts the pool by available capacity, and puts expired or broken sessions first so the account to re-authenticate is obvious. agents list --format json now includes each seat’s non-secret signedInAs identity, which lets the dashboard pair a quota window with the subscription behind a local label. The compiled .app bundle stays ignored; only the source, configuration, and both refreshed lockfiles are committed.

ForkFanOut: end-of-run chores on a fork of the finished session

<ForkFanOut> fans independent chores out over forks of one task’s final agent session. Every entry waits for the source task, starts from a copy of its conversation snapshot in a fresh session (the source is never mutated), and runs its own prompt. It is built for the end of a run: linters, named commits, memory writes, and logging that need full context of the work just done without depending on each other.
Entries accept per-task agent/output/label overrides over the component-level defaults, an allowlisted set of Task props passes through, and duplicate ids or a missing agent/output/fork fail at render time instead of mid-run. Details are in the ForkFanOut reference.

Resume and retry work again

retry-task ran the engine in the foreground with no detach, so a closed pipe (SIGPIPE) killed the run and orphaned its agent children. The invocation is now detached (#1481).
  • An approved detached run with a null workflowHash can resume again (#1489).
  • up --resume RUN_ID no longer demands a workflow path, matching its help text: the run’s stored workflow is relaunched (#1475).

Fallback chains recover instead of failing the run

A session-loss error from a provider (including Kimi’s “session is broken” banner) defeated the agent fallback chain: it silently skipped its Codex leads instead of retrying or falling through. A mixed-reason chain failure hard-failed the run instead of parking it.
  • Session-loss errors are now classified, so the chain retries or falls through to the next seat (#1480).
  • A mixed agent-chain failure parks the run as waiting-quota with preflight context attached, resuming when capacity returns (#1482).
  • Codex preflight accepts valid ChatGPT auth even when auth.json also contains an OPENAI_API_KEY (#1447).
  • Both behaviors are pinned by new quota-chain-failover and session-loss-chain-failover e2e suites.

The Gateway finds your UI, and stays responsive while it does

bunx smthrs ui could fail three ways. The Gateway never registered workflows launched by explicit path outside .smithers (#1474). Registration rendered every workflow synchronously, pegging the event loop so /health went unreachable for minutes (GATEWAY_UNREACHABLE). And a custom-path <UI path="/custom"> could 404 until background discovery reached it. All three are fixed: explicit-path workflows register, <UI>/<TUI> discovery is queued and drains one render per macrotask so /health answers throughout, and an unmatched UI request drains pending discovery once before resolving the mount.
The Smithers Monitor following a live release-canary run from launch to completed

The Monitor overview starts at All clear, a release-canary run is launched live, its running status pill appears in the sidebar, and the run detail shows the Healthy banner while the execution tree ticks through preflight, build, tests, and report to end on Completed with 4/4 tasks done.

Inspecting a completed run in the Monitor across its inspector, timeline, and XML views

Touring the finished run: the Completed banner, per-node durations, the node inspector with transcript and output, the Timeline, Debug, Frames, and XML views with a frame scrubber, and the Notable, All, and Activity event tabs.

A finished run tells the truth, and no agent outlives its engine

Three ways a run could look healthier than it was (#1464). A <Loop> or <ReviewLoop> that exits through the default onMaxReached: "return-last" with its until predicate still false never converged, yet the run reported done. The scheduler now records the loop as exhausted (persisted on _smithers_ralph.exhausted, migration 0035), the finished RunResult and the RunFinished event carry exhaustedLoops, and the CLI stops calling it success:
A killed engine could still read running-healthy. When the full liveness probe failed, status, ps, and inspect fell back to a path that ignored the heartbeat entirely. The fallback now classifies from the run row alone using heartbeat_at_ms and runtime_owner_id, and a continued run whose segment never finished is treated as running so its stale heartbeat downgrades the verdict instead of resolving to succeeded. Agent subprocesses no longer survive their engine (#1332). Agent CLIs spawn detached as their own process-group leaders, the engine registers every agent pid in _smithers_agent_processes (migration 0036), and any CLI invocation sweeps that registry and group-kills entries whose engine is verifiably gone, taking subagents, MCP servers, and tool children with it. cancel and down sweep the runs they fence. Set SMITHERS_NO_ORPHAN_REAPER=1 to opt out of the boot sweep.

ctx.outputs stops returning a plausible empty array

The callable form ctx.outputs(outputs.probe) silently returned [] because it indexed the snapshot with the raw argument. It now resolves through the same table-ref resolution as every other accessor, and an argument that resolves to no declared output table throws (#1486).
  • Regression tests pin the callable form to the same rows as the string name.

upgrade-dependents: open rename PRs downstream

The 0.33.0 rename left dependent repos importing a package that will never see another release. The new upgrade-dependents pack workflow discovers open-source dependents (awesome-smithers, GitHub code search, and any extraRepos you pass), then runs one lane per repo: an agent clones, upgrades to smthrs, and must prove git grep returns zero hits before an independent reviewer re-checks the diff and a draft PR is opened.

Per-run token usage in one query

“How many tokens did this run spend” used to mean replaying the event log and parsing TokenUsageReported payloads out of JSON. The engine now persists a usage row per attempt (_smithers_run_usage, keyed by run, node, iteration, and attempt) alongside each usage event it already emits, on both success and failure paths. bunx smthrs usage --run RUN_ID prints the total, and SmithersDb.getRunTokenUsage(runId) is a single SUM. Rows are upserts keyed by attempt identity, so a provider that re-reports a cumulative running total replaces its row instead of inflating the total, while genuine retries stay separate attempts.

Durable agent checkpoints

Agents can now declare checkpointCapabilities and persist a final AgentCheckpoint per attempt. <Task id={B} fork={A}> starts B from a copy of A’s final checkpoint when the target declares an exact codec, version, and "fork" match, and falls back to the source attempt’s recorded forkable conversation messages otherwise. Forking never mutates the source: multiple tasks can fork the same source, and a forked task can itself be forked.

Nanocodex: a pinned-source sandboxed agent backend

NanocodexAgent connects Smithers to the external smithers-nanocodex bridge: a pinned stock Nanocodex agent run as a short-lived, checkpoint-aware worker inside a Bubblewrap sandbox. The v0.0.1 integration is deliberately fixed (one fresh serve worker and one stock Nanocodex turn per generate() call, no daemon or worker pool) and fails closed when the sandbox requirements are not met. Linux x64 only; see the Nanocodex integration page for the requirements.

A theme registry for the UI kit

@smthrs/ui-styleguide now ships a generated theme registry with eight palettes: Catppuccin, Fucory, GitHub, Gruvbox, Night Owl (new in this release), One, Rosé Pine, and Solarized. A generator script keeps every palette’s light and dark variants, contrast ratios, and CSS custom properties in sync, so run UIs built on smthrs/ui pick up a coherent theme with one setting.

stereOS in the browser, and three new sites

  • custom-sandbox.smithers.sh, published at the time as stereos.smithers.sh, ran the stereOS demo entirely in your browser inside a WebContainer, so the sandbox-provider demo needed no local checkout.
  • patterns.smithers.sh is a field guide to orchestration patterns.
  • research.smithers.sh hosts the persuasion-gap paper.
  • status.smithers.sh gained Smithers Code and Smithers Cloud API components, each verified live before listing, and a missing feed now answers a real 404 instead of an HTML page labelled as status.

Other improvements

  • The bug-triage-train workflow ships in the pack with a live UI: file a batch of bug reports through triage, reproduction, and fix lanes.
  • CodexAgent emits --sandbox workspace-write now that codex-cli 0.147 removed --full-auto, so full-auto-configured workflows keep launching on current codex installs.
  • Queued steers expire inside the terminal cancel transaction, and steer enqueue is fenced against terminal runs, so an instruction can no longer land on a run that is already ending.
  • Detached built-in runs resume correctly after approval decisions.
  • A Codex agent configured through the array form keeps its reasoning effort instead of silently dropping it.
  • bunx smthrs tail --format jsonl emits raw event JSON per line and nothing else, so scripted consumers stop parsing banner noise.
  • bunx smthrs bug --run RUN_ID attaches the run’s lifecycle events instead of a ~25 second window of TaskHeartbeat noise, and generates a correct report title (#1484).
  • Panel moderators inside a Ralph loop no longer deadlock over Ralph’s iteration-suffixed dependency names (#1487).
  • bunx smthrs hijack re-emits the agent’s launch flags. --resume restores conversation state only, so a hijacked session lost the model, permission mode, and config dir the workflow agent ran with. Those are recorded on the hand-off now and replayed onto the CLI invocation.
  • reconstructUnifiedDiff bounds its LCS grid by bytes instead of cells. The old n * m cell guard allocated one typed array per old line, so a lopsided payload passed the check and still allocated about a gigabyte; one flat Uint32Array makes the cell bound a real byte bound.
  • The live node diff path enforces the payload cap it previously bypassed, so an oversized working-copy diff raises DiffTooLarge instead of serializing uncapped.
  • The gateway UI keys its live diff refresh on the last event’s seq rather than the event ring’s length, which stops changing once the 200-event ring fills and froze the live diff on long runs.
  • The review workflow UI renders reviewer feedback, synthesis, and issue descriptions as formatted Markdown through the shared Markdown primitive instead of one raw paragraph, and a mid-run rejection no longer shows a final Blocked badge next to RUNNING: verdicts only go final once the run is terminal.
  • EmptyState and SectionHeader accept element titles again. Both extended ComponentProps<"div">, whose intrinsic title?: string intersected with their own title?: ReactNode slot and collapsed it to string & ReactNode.
  • scheduler and devtools re-emitted their stale committed declarations from tsup --dts-only, which made check:dts false-green on type changes. Both configs now clean the emitted index.d.ts first.
  • Hermes and OpenClaw are listed in the README harness table and the npm package description alongside the other CLI harnesses.
  • The detached launch admission wait is configurable, and a live engine child is no longer killed at the deadline. bunx smthrs up -d waited a fixed 30 seconds for the engine child to prove it persisted the run row, which a heavily loaded machine can miss on module parse alone; the launcher then terminated the healthy child and reported an empty log. A child that is still alive at the deadline now gets a grace window of four times the timeout, with a progress note on stderr, and the final failure names the live pid instead of “(empty)”. Set SMITHERS_DETACHED_ADMISSION_TIMEOUT_MS to raise or lower the base window. The default is still 30 seconds, and a value below 1000ms falls back to it.
  • The Trellis benchmark lab pin now carries the validated v2 authoring ladder: kit-only scripts, static validation, fuel accounting, evidence-bound settlement, mission CLI, and hardened batched ARC evaluation planning. It is internal benchmarking infrastructure, not part of any published package.
  • Test scratch directories now go through a tracked cleanup helper, preventing abandoned SQLite, PGlite, sandbox, and workflow workspaces from accumulating under $TMPDIR after suites finish or throw.
  • The TUI node inspector’s empty Logs tab again renders the parenthesized (no log events for this node) placeholder, with a headless regression test that runs even when the optional zmux PTY suite is unavailable.
  • Claude Code OAuth credentials stored in the macOS Keychain are found everywhere they matter. bunx smthrs usage and the agent availability probe now read the per-config-dir Keychain item for isolated accounts (Claude Code-credentials-<hash of the config dir>), with no fallback to the default install’s item, so a validly logged-in account stops reporting NO_USABLE_AGENTS when the claude auth status probe times out, and one account’s token is never attributed to another.
  • <Task fork> authored inside a Subflow or Sandbox child workflow keeps its fork edge; the dom extraction variant used for child workflows dropped the prop and the fork was silently lost.
  • Asking oneshot for a codex agent while the codex-paused marker is set now names the pause itself, its until/reason, and how to clear it (SMITHERS_CODEX_PAUSED=0 or delete the marker) instead of the generic “not enough availability signals” that pointed at healthy auth.
  • effect and the @effect/* packages are aligned at 4.0.0-beta.105. Keeping the family on one beta prevents npm from satisfying caret peer ranges with multiple effect runtimes in an end-user install.
  • The smthrs umbrella is guarded against type-vs-runtime export drift: a new test imports every documented agent export and asserts it exists at runtime, the exact shape of the bug where fallbackAgents typechecked but threw “Export named ‘fallbackAgents’ not found” on import. The facade’s committed index.d.ts was regenerated against the release base.
  • The gateway PTY resize test polls for the new terminal geometry instead of reading it once after a fixed half-second sleep, so a loaded CI runner no longer reds the packages/server shard over a resize frame that merely arrived late.

Upgrade notes

  • No breaking changes at runtime. All 0.33.0 rename guidance still applies.
  • If a package manifest overrides Effect prereleases, update the whole effect and @effect/* family to beta.105 together.
  • AgentCapabilityRegistry gains a fileChanges: { supportsFileChanges, supportsUnifiedDiff } field. Registries are normalized with both flags false when absent, so existing agents keep working; a custom agent typed against the exported TypeScript type needs the field added.
  • getRunDiff and getNodeDiff return a diff for a non-terminal run now instead of refusing. Bundles computed from a live working copy carry live: true; treat the terminal bundle as the authoritative snapshot.
  • A mixed agent-chain failure now parks the run as waiting-quota instead of failing it. Check bunx smthrs ps for parked runs before concluding a run died.
  • bunx smthrs up --resume RUN_ID without a workflow path is now valid; the stored workflow is relaunched.
  • This release adds several store migrations: the _smithers_attempts.effort column, the _smithers_steers table, _smithers_ralph.exhausted, the _smithers_agent_processes table, agent checkpoints, the _smithers_run_usage table, and the Herdr supervision tables. They apply on first open, so no manual step is needed, but an older CLI pointed at a migrated database will not read the new columns.
  • The herdr client pins wire protocol 19 (herdr 0.8.0). Explicit bunx smthrs herdr commands against another protocol fail closed with HERDR_PROTOCOL_MISMATCH before any mutating call; the optional mirror paths log and continue. herdr remains fully optional.
  • CodexAgent maps full-auto to --sandbox workspace-write instead of the removed --full-auto flag. codex-cli releases that predate --sandbox need an upgrade before Codex tasks will launch.
  • 0.33.1 was tagged but never published to npm. If you tracked its changelog, every entry ships in 0.34.0; there is no npm artifact to migrate through.
  • bunx smthrs status can now return the verdict degraded for a finished run whose loop never satisfied its until condition. The run row status stays finished and RunState stays succeeded, so scripts that key off the run status are unaffected; scripts that assert verdict === "done" should accept degraded or treat it as the non-convergence signal it is.
  • Every CLI invocation now sweeps _smithers_agent_processes and group-kills agent processes whose engine is gone. Set SMITHERS_NO_ORPHAN_REAPER=1 if you deliberately keep agent processes alive past their engine.

The full changelog

254 commits since 0.33.0, plus this changelog update, touching 901 files: 127,655 insertions, 6,718 deletions. Much of that insertion count is regenerated llms-*.txt docs bundles; excluding those, the lockfiles, and declaration bundles it is 839 files, 91,632 insertions, 3,524 deletions. The complete commit-level list is in CHANGELOG.md. Hit a bug? bunx smthrs bug files a report in seconds, and --run RUN_ID attaches the run’s events for you.