docs/adr/0005-clanky-ships-its-stage.md

ADR 0005: Clanky Ships Its Stage — the clanky-herdr Binary

  • Status: Proposed
  • Date: 2026-07-05

Context

Clanky's terminal stage runs on clanky-herdr. The fork sources live in the monorepo (ADR 0003) as a patch stack on upstream (clanky-agent ADR-0017), but the running stage is still an environment assumption: Clanky attaches to whatever herdr binary the user has installed and to a named session inside it. That assumption costs real things:

  • Version drift. The running stage server can lag the installed binary, and both can lag the fork sources the agent was developed against. Behavior that depends on fork-carried capabilities becomes probabilistic.
  • Capability uncertainty. Fork capabilities (pane/session metadata, multi-client retained-render gating, watch/presence eventing) cannot be assumed present, so every consumer carries compatibility checks that exist only to tolerate a vanilla binary Clanky never actually targets.
  • Incomplete containment. As a guest in the user's mux, Clanky must discover which panes are his. The roster, presence mirroring, and orchestration graph are best-effort reconstructions rather than facts.
  • Wording tax. Docs and skills speak in "the active mux adapter" circumlocutions to preserve a tmux/Zellij adapter option no one is building.

The iOS window makes the assumption untenable: native attach, mirror mode, and relay pane read/steer all depend on fork behavior, so the app already hard-requires a binary the product does not control.

Decision

Clanky ships its stage. The herdr/ package builds a Clanky-branded stage binary, clanky-herdr, installed and version-locked by Clanky's setup wizard and supervised like any other Clanky component. It coexists with — and never replaces or attaches to — a user-installed herdr.

The same pattern already governs ClankVox: a Rust plane the Node brain drives over IPC, built idempotently at setup, resolved by explicit location, faulting legibly when missing. clanky-herdr follows it:

  • Full runtime isolation. clanky-herdr uses its own socket paths and state directory under Clanky's resolved data root ($CLANKY_HOME/stage/), never Herdr's defaults. A user's own herdr CLI cannot reach Clanky's stage and Clanky's tools cannot reach the user's sessions. Binary and source locations are overridable (CLANKY_STAGE_BIN / CLANKY_STAGE_DIR, mirroring CLANKY_CLANKVOX_BIN / CLANKY_CLANKVOX_DIR).
  • One default session, supervisor-owned. The supervisor starts clanky-herdr with the brain on cold start and owns the default session. Single-session is Clanky's usage default, not a capability removed from the binary.
  • Complete containment by construction. Everything on the stage is in Clanky's domain: his workers, and anything the user opens there. Clanky is the lead agent over the whole stage; the roster, presence mirror, and orchestration graph are complete because the stage boundary is the ownership boundary.
  • Stage marker. Panes inside Clanky's stage carry CLANKY_STAGE=1 in addition to the standard Herdr environment. Clanky surfaces (worker skill, spawn seam, coordination protocol) key on the Clanky marker, so they never misfire inside a user's personal Herdr session.
  • Fork capabilities are baseline. With the installed binary always Clanky's own, fork-carried capabilities are assumed present and may be hard-required — including relay-facing event subscriptions and pane/session metadata shaped for the mobile mirror. The StageProvider capability checks remain as a testing seam, not a user-facing compatibility mechanism.
  • iOS glue. The relay attaches to the known stage socket; the app pairs to a stage whose version ships with the brain it talks to.

Mux-agnosticism as a shipping feature is dropped. The StageProvider seam survives as design discipline with an honest justification: it keeps the AGPL patch stack thin and rebasable against upstream, and keeps the agent testable without a live stage. Product semantics stay in clanky-agent, per ADR-0017's boundary — that part is unchanged.

flowchart TB
  subgraph mac["Always-on Mac"]
    sup["supervisor (launchd)"]
    brain["clanky-agent brain"]
    subgraph stage["clanky-herdr — default session ($CLANKY_HOME/stage/)"]
      lead["Clanky face pane"]
      w1["worker panes<br/>clanky:&lt;slug&gt;"]
      up["user-opened panes"]
    end
    userherdr["user's own herdr<br/>(untouched, separate sockets)"]
  end
  ios["clanky-ios window"]

  sup --> brain
  sup --> stage
  brain <-->|stage sockets| stage
  ios <-->|relay WS| brain
  ios -.->|attach / mirror| stage

Distribution

Shipping the binary is AGPL distribution of the clanky-herdr fork. That is compatible with the existing posture — the clanky-herdr fork source remains public and is vendored here — but it becomes a release checklist, not a background fact:

  • License text ships with the binary; the published fork stays in sync with every shipped build.
  • The brain remains a separate process speaking to clanky-herdr over sockets; clanky-agent's own licensing is unaffected.
  • Distributed macOS builds are signed and notarized.
  • Keeping "herdr" in the binary name is deliberate: it carries attribution and provenance rather than hiding them.

Consequences

    • Brain and stage version-lock on the same monorepo commit; the installed-binary drift class of bugs disappears.
    • Fork capabilities are assumable, so relay/iOS features build against real eventing instead of polling-shaped workarounds.
    • Setup is one wizard flow: build/verify clanky-herdr, start the supervisor, pair the app.
    • Docs and skills name Herdr plainly; the adapter circumlocutions go away.
    • clanky-herdr joins ClankVox in the compatibility surface: CI builds it, releases version it, and stage bugs are Clanky's to fix or carry as patches.
    • The patch-stack discipline (herdr-fork-rebase) stays mandatory. Owning the binary must not become an excuse to diverge from upstream and lose its terminal-emulation and performance stream.
    • AGPL source-sync and notarization join the release checklist.

SPEC.md §2/§4.1 (clanky-agent) and the root package map drop the mux-agnostic framing in the implementing change; clanky-agent ADR-0017's capability-check boundary is reinterpreted as a testing seam by this decision. The skill-layer consequences (naming, consolidation, config-rendered skills) are clanky-agent ADR-0020.