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
Workflows are callable agent tools
Pass a workflow to an agent as a tool. The schema is derived from the workflow’s ZodinputSchema, 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 GBsmithers.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.
createSmithershad no reachableclose(), 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-idcould be used to claim a different app’s half of the tenant key. - Isolated clones were not byte-faithful.
createIsolatedCloneinherited the host’score.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
continueOnFailnow reports a distinctsucceeded-with-failuresstate while keeping thefinishedlifecycle 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/sdkis 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
parentRunIdstay detached and independent. SMITHERS_HOMEinheritance is split. Ordinary workflows inherit it;inherit: falseandclone.runscrub it unless supplied explicitly.Task.repairandcontinueOnFailare mutually exclusive, rejected at graph-extraction time rather than resolved by precedence at runtime.- A fresh
bunx smthrs initno 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.