Skip to content

feat(smithers): optional durable-orchestration integration (umbrella) - #28

Draft
pgebheim wants to merge 19 commits into
mainfrom
feature/smithers
Draft

feat(smithers): optional durable-orchestration integration (umbrella)#28
pgebheim wants to merge 19 commits into
mainfrom
feature/smithers

Conversation

@pgebheim

Copy link
Copy Markdown
Contributor

Long-running integration branch for optional Smithers durable-orchestration support in Rig. Children merge into feature/smithers; this PR is the review gate into main. Kept as a draft until the verification-hardening work is complete.

Tracking umbrella: #11

Landed so far

Design decision

Rig does not bundle or auto-install Smithers. The --with-smithers magic installer (old #9) is dropped — Rig stays lightweight and runtime-agnostic; users who want the durable layer run bunx smithers-orchestrator init themselves and copy in the smithers/ workflows. Only the README doc from #9 was kept.

Remaining work (into this branch)

Squash-to-main once the tracked set is green.

pgebheim added a commit that referenced this pull request Jul 28, 2026
…ness) (#34)

main had no CI at all — the 26 scope-reviewer tests plus check-review-p1.ts,
pr-review-labels.ts and the shell scripts were entirely ungated. This lands
the same dev-only harness introduced on feature/smithers (#33), scoped to
what main has today:

- test job: bun test scripts/ (26/26) — now gates every push/PR to main.
- typecheck job: skips gracefully here (no smithers/ layer yet) via the
  branch-universal guard; it activates automatically once the smithers/
  layer merges to main via the umbrella (#28).

Files are identical to feature/smithers' harness, so the umbrella merge
reconciles them with no conflict. Dev-only — never vendored (install.sh
copies only skills/, agents/, scripts/, smithers/ content).


Claude-Session: https://claude.ai/code/session_01GHxxuWxnyaNz3cZkcxmnRM

Co-authored-by: Paul Gebheim <86010+pgebheim@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pgebheim and others added 6 commits July 29, 2026 18:30
…rch gate

Seed rig's second execution surface: Smithers workflow equivalents of the
rig-epic/rig-task skills, so autonomous runs can enforce what skill prose only
instructs. First pass of the workflow-layer upstreaming (#12).

rig-epic gains an `advisor` input flag (#13): when set, the front-loaded
`spec-direction` gate renders as a Fable (`providers.claude`) Task instead of a
HumanTask — same node id + {proceed,direction} schema — so a kicked-off epic
runs unattended to child PRs, halting with a blocked report on proceed=false
instead of parking on a human. Trunk merge stays human.

Includes workflows/{rig-epic,rig-task}.tsx, ui/{rig-epic,rig-task}.tsx, a
reference agents.example.ts (machine-specific agents.ts is regenerated via
`smithers init`, not vendored), and a README documenting the dual-surface parity
contract and the remaining #12 work (install.sh vendoring adapter).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Es83TAgTGyhCG9z8rczB4z
Salvaged from the closed PR #9 (magic --with-smithers installer). Keeps the
manual, opt-in Smithers story and points at the vendored smithers/ workflow
layer (#26); drops the auto-scaffold that broke Rig's lightweight promise.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GHxxuWxnyaNz3cZkcxmnRM
…sOn (#27)

Once a child merged, its lane — including its merge-<prev> node — is skipped
via `continue`, but the next child still declared dependsOn:[merge-<prev>],
pointing at an unmounted node → DEPENDENCY_DEADLOCK at every child boundary.
The prior `break` guard only deferred the deadlock to the next re-render.

Gate the dependency on `prevUnmerged`: depend on the predecessor's merge gate
only while it is still in-flight; a merged predecessor means this child is
already free to start, so no dependency is emitted.

Fixes #27

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GHxxuWxnyaNz3cZkcxmnRM
…sition + rig-crank (#32)

* feat(smithers): composable TaskFlow/EpicFlow fragments + inline composition + rig-crank

Refactor the Smithers workflow layer from monolithic workflows + childRun Subflow
fan-out into composable React fragments the parent renders INLINE:

- flows/task-flow.tsx (TaskFlow) + flows/epic-flow.tsx (EpicFlow) — the graphs as
  fragments (no <Workflow>), taking a `tables` bag + `idPrefix` so they compose
  inline: one run, native deps, full time-travel, no childRun. Helpers
  taskSchemas/taskBag, epicSchemas/epicBag (optionally namespaced).
- rig-task.tsx / rig-epic.tsx are now thin <Workflow> wrappers over the fragments.
- EpicFlow runs each child as an INLINE TaskFlow (was a childRun Subflow). This
  supersedes the Subflow fan-out — and with it the dangling-`merge-<prev>`
  deadlock that #27/#31 patched, which the inline lanes sidestep structurally
  (render-gating, no cross-run dependsOn).
- The advisor spec gate is now a composed <GatherAndSynthesize> (Fable
  synthesizer) on the advisor path; same node ids + schema as the human path.
- rig-crank.tsx — NEW autonomous build loop: advisor-picks the next ready ticket,
  routes epic-vs-task to EpicFlow/TaskFlow inline, verifies via an evidence-based
  risk-probe gate, lands, loops (continueAsNewEvery) until the backlog is dry.
- rig-delegation-spike.tsx — a spike evaluating Smithers' DelegationChain suite.
- README: document the fragments, the tables-bag/idPrefix seams, rig-crank.

Validated graph-clean across all workflows in the trial project. Follow-up: the
prompt examples still carry the trial's domain flavor (pre-existing in the pack);
a genericization pass can land separately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ENct54EpFjBkVMecSGh4z

* feat(install): --smithers vendors the Smithers workflow pack

Opt-in flag copies smithers/{workflows,ui} + agents.example.ts + the pack README
into <target>/.smithers/ (no-clobber, agent-agnostic). Never vendors agents.ts
(machine-specific — regenerate via `smithers agents add`); the surrounding
package comes from `smithers init`. Ticks off the install.sh + agents.ts items
in the smithers/README TODO.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ENct54EpFjBkVMecSGh4z

---------

Co-authored-by: Paul Gebheim <86010+pgebheim@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolves every error the new typecheck job surfaced. All fixes preserve
runtime behavior; none weaken the check.

- rig-crank.tsx: remove duplicate `result: resultSchema` key (TS1117 — a
  copy-paste leftover; `result` is already registered with `input` above).
  Narrow `pick` before use (Boolean(pick) && … doesn't narrow → guard inside
  Boolean(…)).
- rig-epic.tsx: read the dynamic-registry keys childRun/epicResult through a
  `Record<string, any>` alias. These are registered at runtime via
  epicSchemas()/taskSchemas() (typed Record<string, any>), so they're spread-
  only and not statically visible on `outputs` — same treatment epicBag()/
  taskBag() already give their `outputs` param. NOT a runtime bug.
- epic-flow.tsx: annotate the 5 render-prop params `(d)` -> `(d: any)`,
  matching task-flow.tsx's existing convention (smithers' <Task> doesn't infer
  `d` from `deps`).

Verified locally: tsc --noEmit exits 0; scripts/ suite 26/26.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GHxxuWxnyaNz3cZkcxmnRM
'crank' was obscure; 'loop' names what it is — the autonomous backlog-draining
build loop (advisor-picks the next ready ticket, builds via rig-task, verifies
with backpressure, lands, repeats until the backlog is dry). Chosen over
rig-run (collides with smithers' up/run verbs) and rig-watch (implies passive
monitoring; this actively builds/lands).

- smithers/workflows/rig-crank.tsx -> rig-loop.tsx (git rename)
- <Workflow name>, Loop node id, header/display-name/description updated
- all cross-references in README + sibling workflows/flows updated
- metaphor uses of 'the crank' reworded so nothing orphaned remains

install.sh needs no change (vendors via the rig-*.tsx glob). Verified: tsc
--noEmit exits 0; scripts/ suite 26/26.


Claude-Session: https://claude.ai/code/session_01GHxxuWxnyaNz3cZkcxmnRM

Co-authored-by: Paul Gebheim <86010+pgebheim@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pgebheim and others added 13 commits July 31, 2026 18:05
A design-draft skill: treat the spec as desired state and code as actual
state, compute bidirectional drift (plan, read-only), then reconcile via a
pluggable sink — an ephemeral Smithers workflow (default), a tracked
milestone of tickets, or report-only. Never edits product code directly;
work lands through gated rig-task/rig-plan or a Smithers workflow lane.

Modeled on rig-plan; extractor is a project-supplied adapter (rig-tracker
pattern). Not wired into DEFAULT_SKILLS — proposal for discussion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GHxxuWxnyaNz3cZkcxmnRM
demos/ is a home for try-it sandboxes. notes-api ships a spec, Express-style
code with three planted drifts (missing DELETE, PUT<->PATCH divergence,
undocumented GET /health), a real grep-based extractor adapter, and the
rig-sync skill + rig-architect agent copied in so /rig-sync plan runs in place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GHxxuWxnyaNz3cZkcxmnRM
- SPEC.md for rig-sync (plan/apply, durable Smithers workflow sink, extractor
  adapter) — the backlog on agent-rig board #1.
- T1: sync.* config block in schema + example.
- T3: scripts/rig-sync.ts computeDrift — directional bidirectional diff by
  (kind,id): missing / undocumented / diverged / aligned + carried invariants.
  16 bun tests; reproduces the notes-api demo drift from the real extractor.
- T4: renderReport + 'report' verb -> terraform-plan-style .rig/DRIFT.md.
- Dogfood: .rig/config.json points rig at agent-rig board #1.

Refs #46 #48 #49

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GHxxuWxnyaNz3cZkcxmnRM
… (M0 T2,T5)

- T2: validateSurfaceDoc + 'validate-extractor' verb (rejects malformed
  adapter output with exit 1); docs/rig-sync.md contract + reference extractor;
  validated against the real notes-api extractor. 23 bun tests.
- T5: SKILL plan verb wired to scripts/rig-sync.ts (extractor resolver ->
  validate -> desired surface -> diff/report -> STOP). Corrected the apply/
  workflow wording to the durable, parameterized Smithers workflow model
  (run once, drift as input; never make-workflow; engine-agnostic).

M0 complete (T1-T5). Refs #47 #50

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GHxxuWxnyaNz3cZkcxmnRM
- T6: driftToUnits — routes each drift item by truth (missing=work always;
  diverged/undocumented -> work|doc|decision by spec|code|ask).
- T7: renderDriftSpec + 'spec' verb -> the report sink's .rig/plan.md; also the
  input /rig-plan decomposes for the backlog sink (T8, SKILL-wired).
28 bun tests. Refs #51 #52 #53

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GHxxuWxnyaNz3cZkcxmnRM
…10,T11)

- T9: smithers/workflows/rig-sync.tsx — parameterized reconcile workflow
  (driftReport units as INPUT, not regenerated): verify-drift -> plan gate ->
  per-unit Worktree + coder/reviewer Ralph lanes -> merge gate -> MergeQueue ->
  final-verify. Renders under `smithers graph` (verified with real notes-api
  drift input). Authored once, run many; engine-agnostic (agents from the
  target project's smithers/agents.ts).
- T10: 'workflow-input' verb -> deterministic drift -> the workflow's --input.
- T11: engine-agnostic docs (docs/rig-sync.md).

M2 complete. Refs #54 #55 #56 #57

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GHxxuWxnyaNz3cZkcxmnRM
…hildren

Running the reconcile workflow end-to-end surfaced a real bug: nesting the
per-unit lanes + merge-gate + final-verify as CHILDREN of <Approval> did not
schedule them — the run finished right after plan-gate approval (only
verify-drift + plan-gate executed). Switched to rig EpicFlow's convention:
Approval is a decision node, and subsequent steps render conditionally on
ctx.outputMaybe(gate)?.approved. Verified: after approval the coder/reviewer
lanes now dispatch (impl-* + review-* nodes go live).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GHxxuWxnyaNz3cZkcxmnRM
Second run-only bug: unit ids like 'DELETE /notes/{id}' were used verbatim as
the git branch (rig-sync/DELETE /notes/{id}) and worktree path — invalid ref
(spaces, slashes, braces), so every lane failed at worktree creation (12
retries, ~18min, zero coding). Added slug() and applied to branch/path + node
ids. Verified: the worktree now creates and the coder agent runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GHxxuWxnyaNz3cZkcxmnRM
…eam)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GHxxuWxnyaNz3cZkcxmnRM
…ts dependency

Downstream testing surfaced two real problems: (1) smithers init generates
Codex/Fable-first agent pools that most Claude users don't have, and rig-sync
inherited them by importing ../agents; (2) editing agents.ts to fix that fights
Claude Code's bg-isolation. Both vanish if the workflow doesn't touch agents.ts:
the two seats (coder, reviewer) now default to ClaudeCodeAgent inline, so it
runs out of the box with no agents.ts to configure. Still multi-modal — swap the
seats for your own agents.ts pools. Renders under smithers graph with no
agents.ts present. Docs/SKILL wording corrected (Claude default, overridable).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GHxxuWxnyaNz3cZkcxmnRM
feat(rig-sync): spec ⇄ code reconciler — M0–M2 (plan + drift engine + durable Smithers sink)
* fix(workflows): guard ctx.input arrays for 0.32 UI discovery

Smithers 0.32's gateway renders each workflow's <UI> at startup to
discover its views, calling the component with an empty `ctx.input`
(schema defaults are NOT applied during discovery). rig-sync and
rig-loop both call an array method on an input field at module top —
`ctx.input.units.filter(...)` and `ctx.input.built.join(...)` — so
discovery throws `TypeError: undefined is not an object`, the UI fails
to register, and the run's custom UI never renders in the monitor.

Guard both with `?? []`, which restores exactly the schema's own
`.default([])` during discovery and is a no-op at run time. Verified
against a 0.32 gateway: the two "workflow UI discovery render failed"
warnings are gone and both UIs register.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMUG7KT32tVitdDTjjyrgE

* fix(rig-sync): type the `key` prop on the Unit lane component

`<Unit key={u.id} …>` is rendered in `work.map(...)`, but Unit is a plain
function component whose props type didn't declare `key`. Under the smithers
JSX types, `key` isn't auto-injected for function components (built-ins like
<Task>/<Sequence> carry it), so it was rejected as an excess prop — TS2322 at
the call site. Declaring `key?: string` on Unit's props keeps the list-identity
key and makes `typecheck smithers/` pass. Pre-existing failure on
feature/smithers, surfaced independently of the UI-discovery guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMUG7KT32tVitdDTjjyrgE

---------

Co-authored-by: Paul Gebheim <86010+pgebheim@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
)

* feat(gateway): robust Smithers Gateway manager + Tailscale exposure

Add scripts/smithers-gateway.sh — an idempotent manager for the Smithers
workspace Gateway that publishes it to a tailnet for token-free browser
access — plus a `gateway` config block in rig.schema.json and the example.

Why: the Gateway's token auth is Authorization-header only, so a token-gated
UI is unreachable from a browser (no query-param / cookie / Basic fallback).
The manager runs the Gateway on loopback (no token needed) and publishes it
over HTTPS via `tailscale serve`, making Tailscale device identity the auth
boundary while the Gateway stays unauthenticated on 127.0.0.1. It exports
SMITHERS_GATEWAY_TRUST_ANY_HOST=1 so the tailnet DNS name in the Host header
isn't rejected as a DNS-rebinding attempt.

Commands: up (default) / down / restart / status / url / discover.
- Auto-discovers Tailscale (ip, MagicDNS name, HTTPS certs) and picks the mode
  (auto -> tailscale-serve when available, else loopback).
- Liveness is an HTTP console probe, not `smithers gateway status` (which can
  report running:false for a serving, manually-started Gateway).
- Idempotent; graceful fallback to loopback when Tailscale is absent; explicit
  `--insecure` opt-in for direct tailnet-IP binding.

Config: gateway.{port,mode,servePort,trustAnyHost}. install.sh already vendors
scripts/* into <target>/.claude/scripts/, so no installer change is needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMUG7KT32tVitdDTjjyrgE

* feat(rig-sync): bring up a reachable UI after launching the workflow sink

Wire scripts/smithers-gateway.sh into rig-sync apply's `workflow` sink: after
`smithers up ... --input`, run `smithers-gateway.sh up` so the durable run gets a
browser-reachable console (loopback Gateway + Tailscale HTTPS when present) and
report the console URL alongside the run id. Document the `gateway.*` config in
the skill's Configuration section.

rig-sync is the only skill that launches a durable Smithers run (rig-epic/rig-task
are agent-orchestrated and don't `smithers up`), so this is the one apply flow
that needs the wiring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMUG7KT32tVitdDTjjyrgE

---------

Co-authored-by: Paul Gebheim <86010+pgebheim@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant