Skip to main content
Smithers 0.35.0 is about what happens when things go wrong. Retry was the only recovery primitive, so a terminal failure meant hand-authoring a repair workflow. A task can now declare repair: on terminal failure it receives the terminal error, the attempt history, and the worktree context, with a bounded budget and a TASK_REPAIR_FAILED terminal state. It is deliberately neither a fallback (which would substitute a different result) nor an escalation (which would move the failure elsewhere), because a recovery primitive that hides the failure is worse than none. Cancellation now reaches all the way down. cancelRun cascades the whole descendant subtree as one transition: a cycle- and depth-guarded descendant CTE, single-winner CAS so concurrent cancellers cannot half-apply, and a transactional continue-as-new fence. Detached owners and agent process trees are terminated through the driver’s existing killProcessTree, so no work outlives the subtree that owned it, and the response reports every run the cascade reached with its depth and action. The store is multi-tenant. Runs carry a persisted, indexed (owner, app) pair. Enforcement is real isolation at the Gateway boundary, and a foreign run id answers RunNotFound rather than a permission error, so ownership cannot be probed by id. Child, continue-as-new, and fork runs inherit; legacy rows stay unowned and keep shared behavior. The rest is volume: 127 commits since 0.34.0, 68 of them fixes. (0.33.0 and 0.34.0 were tagged but never published to npm; everything they contained ships here.)

Upgrading

Behavior changes are collected in the upgrade notes. This release has more of them than usual, and two will turn a previously silent condition into a hard error.

Workflows are callable agent tools

Pass a workflow to an agent as a tool. The schema is derived from the workflow’s Zod inputSchema, and a schema-less workflow gets a strict empty object rather than a permissive one, so an agent cannot invent arguments. Calls are not a side channel: they run as real child runs through the existing executeChildWorkflow machinery, carrying parentRunId lineage and a startedBy harness of smithers-workflow-tool, so a tool-invoked run is as visible and attributable as any other. A child that parks on an approval, event, or timer returns WORKFLOW_TOOL_SUSPENDED instead of blocking, depth is bounded by a durable counter, and per-attempt serialization preserves the parent’s concurrency slot.

Declarative external-event listeners

A registry file declares which GitHub issue and pull-request events start which workflows, and Smithers registers, reconciles, and deregisters the real webhooks to match. A pure planner produces an inspectable plan before anything is mutated, smithers listeners drives it from the CLI, and the Gateway grows a github-source webhook branch. Ownership state is written 0600 via atomic rename and holds only a sha256 digest of the secret. No secret or token reaches plans, state, errors, or logs.

Grok (xAI) is a supported provider

GrokAgent runs through BaseCliAgent like the other CLI providers, with accounts (bunx smthrs agents add), quota-aware ordering in fallbackAgents() so a rate-limited account sinks rather than blocking, and dead-session classification so a stale Grok session cannot burn a retry budget. xAI exposes no live usage endpoint. bunx smthrs usage says exactly that rather than inventing an estimate.

The engine embeds in plain Node

packages/smithers/src/external exposes a reusable engine instance usable without bun: PGlite by default (or real Postgres via connectionString, connection, SMITHERS_POSTGRES_URL, or DATABASE_URL), a host-supplied logger, one instance running many runs, and error.cause preserved across the tagged-error boundary. Capabilities that genuinely do not exist off-bun fail with named errors rather than degrading silently: Node SQLite raises DB_REQUIRES_BUN_SQLITE, and PTY stays a Gateway/UI capability.

The control-plane database is bounded

A 100 GB smithers.db was possible because nothing ever reclaimed anything. smithers gc now compacts duplicate snapshot rows (behavior-preserving, resumable, batched) and can delete history for terminal runs older than a window, protecting lineage and live runs. --dry-run reports what each would reclaim. Retention is opt-in behind --db-retention-days or SMITHERS_DB_RETENTION_DAYS, because an upgrade must never silently delete run history. Compaction and retention free pages for reuse inside the file; they do not shrink it on disk, and Smithers never VACUUMs a live database.

The monitor is a product surface

The monitor now composes from the shared UI design system end to end: runs overview and rail with intentional density, an execution tree with legible node kinds, iteration and attempt/retry metadata, failure counts on collapsed ancestors and auto-expansion of paths needing attention, complete keyboard navigation with APG tree semantics, and loading, empty, error, and offline states that do not look broken. Verified across both themes and mobile, tablet, and desktop widths.

Fixes worth calling out

  • A failed run launch could lose a delivery permanently. The integration delivery hook ran after completion, and claim release only rewinds pending rows, so a failure marked the delivery complete and every provider redelivery was deduped away. The workflow start vanished with no error.
  • Stale resume pointers poisoned every retry. Resume ids persisted on failed and cancelled attempts, so the next attempt resumed a conversation that no longer existed and died immediately, burning a retry without doing work. Pointers from unsuccessful attempts are now ignored at resolution (which repairs existing rows) and cleared at terminal transition.
  • CI shard 3 wedged on SQLite. createSmithers had no reachable close(), so callers could never release a handle and one test process peaked at 189 open databases. A tracked-fixture teardown brings that to 53.
  • Trusted-proxy auth honoured an app header outside its allow-list. A proxy that never stripped x-app-id could be used to claim a different app’s half of the tenant key.
  • Isolated clones were not byte-faithful. createIsolatedClone inherited the host’s core.autocrlf, so on Windows a patch or bundle produced from a capsule reported every text file as wholly changed.
  • A degraded run looked identical to a clean one. A run whose children failed under continueOnFail now reports a distinct succeeded-with-failures state while keeping the finished lifecycle status, so terminal polling is unchanged.

Upgrade notes

  • CLI agent constructors reject unknown options. A mistyped adapter option used to be ignored silently, forever. It is now a TypeError. This will surface real typos in existing code as hard failures.
  • @modelcontextprotocol/sdk is an optional peer you install explicitly. It was a hard dependency in every install. The MCP toolset lazy-loads it and fails with an install hint if absent.
  • Lifecycle children share the parent’s concurrency budget. A parent capped at N previously bounded only its own tasks, so a subtree could run arbitrarily wide. Workflows relying on that will now run subtrees more slowly. Runs linked only by parentRunId stay detached and independent.
  • SMITHERS_HOME inheritance is split. Ordinary workflows inherit it; inherit: false and clone.run scrub it unless supplied explicitly.
  • Task.repair and continueOnFail are mutually exclusive, rejected at graph-extraction time rather than resolved by precedence at runtime.
  • A fresh bunx smthrs init no longer ships the docs-driven-development workflow and starter spec. They are opt-in; the docs describe how to add them.
  • The Gateway requires a trusted-proxy peer boundary before honouring identity headers.
  • Migration 0044 adds the run ownership columns and index.