tower — run the control tower
herdr (Apache-2.0, herdrdev/herdr) gives every coding agent a real terminal pane on a background server, and exposes the whole session over a CLI and socket API. A control tower is one agent that uses that API to run the others: it writes specs, launches panes, watches for completion, verifies the result, and reports. This skill is the operating protocol for that seat.
herdr itself teaches the CLI — see herdr's own skill. This skill teaches the part herdr cannot: how not to be lied to by your own fleet — as a session loop, as dispatch discipline, and as machine-checked acceptance gates.
Official vs house. Install official herdr and nvk wiki-manager separately. This skill does not replace them. Folded here is house herdr-fleet (dispatch/supervise), not the official CLI skill. docs/herdr-skill-upstream.md is a synced pointer, not a skill this marketplace maintains.
Paths
${CLAUDE_PLUGIN_ROOT} is the plugin root: it contains scripts/, commands/, and templates/. This file lives at ${CLAUDE_PLUGIN_ROOT}/skills/tower/SKILL.md. Bare scripts/<file> in this skill is plugin-root-relative. docs/ and LICENSE.unlazy sit next to this file.
Overview
- Delegate by default. Project coding, audits, censuses, log reads, judgement against a repo — all of it goes to a pane. Work done inline burns the tower's context, is invisible in the fleet UI, and dies with the session.
- A spec is a file, not a prompt. Prompts get truncated and echoed; files do not. The prompt is one line pointing at the spec.
- Idle is not done. Done is a named marker file on disk plus an independent re-check of the deliverable's central claim. Agent prose is say-so.
- Watch the marker, then poke. A background poller waits on the marker path and prompts the tower when it lands. Never hook agent
idle.
- Worktree is the default implement seat. Home stays on
main. For a write, branch, or PR: herdr worktree list, then open or create. Start the seats the task requires on that worktree workspace. A worktree workspace with no live named seats is not staffed. See layout.
- Thread in the name. Agent
<slug>-<N>-<role> (must match [a-z][a-z0-9_-]{0,31}; shorten the slug, not the thread id, if it would overflow). Pane #<N> · <role> · <task>. The # is the label, never the agent name. See layout.
- Start only the seats the task requires. The role menu is not a standing orchestra.
- A gate that cannot fail is not a gate. Every spec names a gates file; done means the checker exits 0.
- Planes. herdr is the default for project work. Inline is one cheap fact to fill the spec. Other runners are instance config, not this skill. See dispatch.
- Auto-wiki. A hook on
main calls scripts/auto-wiki.py. It rewrites high-level wiki pages from the git diff. It does not paste the source. See auto-wiki.
Quick Start
test "${HERDR_ENV:-}" = 1 # tower must live inside herdr
# 1. spec on disk — it names the gates file for the job
$EDITOR scratchpad/specs/2026-08-17-thing.md
# 2. pane, labelled before the first prompt (cwd is the worktree; <N> is the thread id)
herdr pane split --pane "$HERDR_PANE_ID" --direction right --ratio 0.42 --cwd ~/code/thing --no-focus
herdr pane rename <new_pane_id> "#<N> · worker · thing"
herdr agent start <slug>-<N>-worker --kind claude --pane <new_pane_id> -- --model opus
# 3. dispatch, then land-check that it actually arrived
herdr agent prompt builder "read the spec at /abs/specs/2026-08-17-thing.md and follow it exactly. \
Write the deliverable to /abs/deliverables/thing.md and the marker to /abs/markers/thing.done when fully done."
herdr agent get builder | jq -r .result.agent.agent_status # must be working, not idle
# 4. leave the chat — the marker wakes you (cwd: ${CLAUDE_PLUGIN_ROOT})
scripts/tower-watch.sh start --marker /abs/markers/thing.done --prefix builder
# 5. accept only through the gates (cwd: ${CLAUDE_PLUGIN_ROOT})
node scripts/gate-check.mjs --status /abs/gates/thing.md # exit 0 or it is not done
Core Concepts
- The loop. Write spec → prompt a pane → start the watch → stop analysing in this chat → marker lands → verify → reconvene with the fleet table of every agent, tab, and run. The tower's jobs are specs, launch, land-check, watch, verify, report. It does not do the work.
- Three grades, preserved upward. Every load-bearing claim is VERIFIED / INFERRED / NOT DETERMINED. Collapsing the last two is how a gap ships as a conclusion. A report where nothing is ever NOT DETERMINED had a spec that was too loose.
- "Can't tell" is not "dead." Lost herdr contact is
unverifiable, never exited, and never an invitation to re-staff. See patterns.
- Live work is a graph, not a chat. A new owner ask may add a node or defer one in writing; it never drops a live one. See work graph.
Daily OS
The outer loop. Dispatch is the inner loop. Details: operating loop.
- Session start. Load the work graph. Read the live map and the status board. Report fleet, queue head, entitled kinds/models, pin file, dispatch blockers. Do not start agents. Do not edit product repos. Completion: one compact decision summary.
- Unpaid ask. Before the reply that starts a new job, settle every ask from the previous owner message: answer it in this reply, or name it parked and why. A pane start does not pay a question. See work graph.
- catalog. Same board, entitlement focus. Name entitled vs missing kinds/models. Name pin slots whose kind is missing. Do not go outside the board unless the board cannot answer.
- reconvene. After a marker: verify, then one owner table (Project, Status, What is true, Wrong/gap, You). That is the conversation. Not a running commentary while panes work.
On demand (not session-start steps):
- Escalate. An item the tower cannot decide goes to the owner queue after re-verify, or is declined because it is already fixed. Details: operating loop.
- Handoff. A handoff note is an output rewritten from live herdr and markers on disk. It is never a source of liveness. Details: operating loop.
- Closeout. Route the deliverable into the project's knowledge base, or mark it ephemeral with a reason. Nothing auto-promotes. Details: operating loop.
Acceptance gates
Reuse unlazy v2 enforcement via the vendored checker. Do not rebuild it. Do not run unlazy orchestrated mode inside a herdr fleet — the space PM owns dispatch.
Every spec names a gates file. Each gate is one observable outcome with CHECK / EXPECT / EVIDENCE. Format: gate format. Parent workflow: gates workflow.
CHECKER="${CLAUDE_PLUGIN_ROOT}/scripts/gate-check.mjs"
node "$CHECKER" gates/<name>.md # run unmet checks, flip boxes, write evidence
node "$CHECKER" --status gates/<name>.md # report only, change nothing
Done means gate-check.mjs --status <gates> exits 0. An empty touch marker is not done. A checked box with EVIDENCE: pending is unmet. A gate you decided not to meet is ABANDON: <id> <reason>, not deleted.
- The implementer may run the checker; the parent re-runs
--status before calling anything done.
- A gates file with zero
- [ ] Gn: checkboxes exits 2, never ALL MET.
- Do not write a second checker. This one is vendored for that reason.
Documentation
House leftovers folded here (not six plugins): herdr-fleet (HOUSE dispatch, not official herdr), work-graph, space-loop, reconvene-table, verify-deliverable, model-router, plus auto-wiki. Official herdr and nvk wiki-manager stay separate installs.
- Operating loop — Session start, catalog, unpaid ask, reconvene, Escalate, Handoff, closeout, and the reconvene-table PR verb list
- Dispatch — planes, the spec file, the eight-step delegation protocol, and the three spec-writing failures
- Watch & poke — why idle ≠ done, marker polling, the land-check, and how to verify a deliverable independently
- verify-deliverable — seven-step re-check; verdict pass / partial / fail
- Staffing — the role graph, required vs optional seats, kinds/models/effort, chain of command, and time-boxing an autonomous loop
- space-loop — Loop block in every PM spec; tower never prompts scout/mentor when a PM is live
- herdr-fleet — fleet index,
agent read, pane-record-before-close
- model-router — board-first RoutingDecision; pin ∩ entitled
- Layout — worktree as the default implement seat, thread in the agent name, pane labels, and why an extra tab means an extra worktree
- Closing — retiring agents, closing one-shot panes, when a workspace may close, and reloading a plugin without killing a space
- Work graph — work graph file, unpaid ask, node states, and typed edges
- auto-wiki — rewrite wiki pages from a git diff; hook calls this package's script
- Patterns — adopted loops (catalog). Auto-wiki is the named adopt.
- fusion-opinion — read-only N opinions + one architect when the spec says
when=fusion-opinion
- Pitfalls — the failure catalogue, each with what it actually cost
- CLI reference — the herdr surface a tower uses, verified against v0.7.5
- Gate format — the file shape,
CHECK / EXPECT / EVIDENCE, ABANDON, and how to write a gate that means something
- Checker reference — CLI flags, exit codes, matching, file discovery, timeouts
- Gates workflow — spec names the gates, implementer fills them, parent re-runs
--status
- Boundaries and attribution — what the gates take from unlazy, what they refuse, and the licence
- herdr's own skill · herdr README — upstream, synced
Common Workflows
- Session start. Work graph + live map + status board → one compact decision summary. Do not start agents. Then catalog, or dispatch.
- Dispatch one task. Spec file (naming its gates) → worktree workspace (not home
main) → split + rename pane → agent start → agent prompt → land-check working → tower-watch.sh start → on MARKER_OK, read the deliverable, re-check its central claim against gh, the disk, or the DB yourself, and re-run gate-check.mjs --status for exit 0.
- Stand up a project workspace. Start the required seats only (usually PM + mentor + one worker), one tab, roles as splits, every pane labelled
#<N> · <role> · <task>. Add reviewer when there is a mergeable diff, QA when a human will click it.
- Split investigate-and-change. Phase A is read-only and ends in
STOP; the owner decides; Phase B is released separately. Discover-and-fix in one uninterrupted run is how an agent fixes the wrong thing confidently.
Scripts
scripts/tower-watch.sh, scripts/tower-poke.sh — the watch/poke loop. Point at a tower root with TOWER_ROOT; both are read-only against product repos.
scripts/gate-check.mjs — the acceptance checker. Vendored from Leonxlnx/unlazy (MIT), zero dependencies, Node 16+. Keep the attribution header and LICENSE.unlazy together; re-vendor by hand per boundaries.
scripts/auto-wiki.py — rewrite high-level wiki pages from a git diff. Consumer hook calls this file. Not a copier.
Upstream Sources
Sync & Update
When the user runs sync: re-fetch the upstream README and skills/herdr/SKILL.md listed in sync.json. The protocol docs are authored, not synced — see sync.json notes. When the user runs diff: compare cached docs/ against upstream, re-check docs/cli-reference.md against the installed herdr --help, and re-read docs/boundaries.md before changing the checker.
1---2name: tower3description: Run a control tower over a fleet of herdr agents — delegate every project task to a pane, dispatch from a spec file, watch a completion marker, and verify independently before calling anything done. Includes acceptance gates — a gates file of CHECK/EXPECT/EVIDENCE outcomes verified by a vendored checker, so a completion marker carries proof and "done" is an exit code instead of a claim. Use when operating or staffing a herdr fleet, writing a delegation spec, deciding whether an agent is finished, writing acceptance criteria, or verifying a delivered job. Use at session start; when the user asks for status, catalog, an unpaid ask, or to reconvene; when choosing a plane; when escalating, handing off, or seating a write on a worktree. Triggers on herdr, control tower, tower, fleet, agent pane, marker, land-check, idle vs done, definition of done, gates file, gate-check, GATES.md, MARKER_OK, CHECK/EXPECT, unlazy, session start, catalog, unpaid ask, reconvene, planes, escalate, handoff, worktree, auto-wiki.4---56# tower — run the control tower78[herdr](https://github.com/herdrdev/herdr) (Apache-2.0, `herdrdev/herdr`) gives every coding agent a real terminal pane on a background server, and exposes the whole session over a CLI and socket API. A **control tower** is one agent that uses that API to run the others: it writes specs, launches panes, watches for completion, verifies the result, and reports. This skill is the operating protocol for that seat.910herdr itself teaches the CLI — see [herdr's own skill](docs/herdr-skill-upstream.md). This skill teaches the part herdr cannot: **how not to be lied to by your own fleet** — as a session loop, as dispatch discipline, *and* as machine-checked acceptance gates.1112**Official vs house.** Install official `herdr` and nvk `wiki-manager` separately. This skill does not replace them. Folded here is house **herdr-fleet** (dispatch/supervise), not the official CLI skill. `docs/herdr-skill-upstream.md` is a synced pointer, not a skill this marketplace maintains.1314## Paths1516`${CLAUDE_PLUGIN_ROOT}` is the plugin root: it contains `scripts/`, `commands/`, and `templates/`. This file lives at `${CLAUDE_PLUGIN_ROOT}/skills/tower/SKILL.md`. Bare `scripts/<file>` in this skill is plugin-root-relative. `docs/` and `LICENSE.unlazy` sit next to this file.1718## Overview1920- **Delegate by default.** Project coding, audits, censuses, log reads, judgement against a repo — all of it goes to a pane. Work done inline burns the tower's context, is invisible in the fleet UI, and dies with the session.21- **A spec is a file, not a prompt.** Prompts get truncated and echoed; files do not. The prompt is one line pointing at the spec.22- **Idle is not done.** Done is a named marker file on disk *plus* an independent re-check of the deliverable's central claim. Agent prose is say-so.23- **Watch the marker, then poke.** A background poller waits on the marker path and prompts the tower when it lands. Never hook agent `idle`.24- **Worktree is the default implement seat.** Home stays on `main`. For a write, branch, or PR: `herdr worktree list`, then `open` or `create`. Start the seats the task requires on that worktree workspace. A worktree workspace with no live named seats is not staffed. See [layout](docs/layout.md).25- **Thread in the name.** Agent `<slug>-<N>-<role>` (must match `[a-z][a-z0-9_-]{0,31}`; shorten the slug, not the thread id, if it would overflow). Pane `#<N> · <role> · <task>`. The `#` is the label, never the agent name. See [layout](docs/layout.md).26- **Start only the seats the task requires.** The role menu is not a standing orchestra.27- **A gate that cannot fail is not a gate.** Every spec names a gates file; done means the checker exits 0.28- **Planes.** herdr is the default for project work. Inline is one cheap fact to fill the spec. Other runners are instance config, not this skill. See [dispatch](docs/dispatch.md).29- **Auto-wiki.** A hook on `main` calls `scripts/auto-wiki.py`. It rewrites high-level wiki pages from the git diff. It does not paste the source. See [auto-wiki](docs/auto-wiki.md).3031## Quick Start3233```bash34test "${HERDR_ENV:-}" = 1 # tower must live inside herdr3536# 1. spec on disk — it names the gates file for the job37$EDITOR scratchpad/specs/2026-08-17-thing.md3839# 2. pane, labelled before the first prompt (cwd is the worktree; <N> is the thread id)40herdr pane split --pane "$HERDR_PANE_ID" --direction right --ratio 0.42 --cwd ~/code/thing --no-focus41herdr pane rename <new_pane_id> "#<N> · worker · thing"42herdr agent start <slug>-<N>-worker --kind claude --pane <new_pane_id> -- --model opus4344# 3. dispatch, then land-check that it actually arrived45herdr agent prompt builder "read the spec at /abs/specs/2026-08-17-thing.md and follow it exactly. \46Write the deliverable to /abs/deliverables/thing.md and the marker to /abs/markers/thing.done when fully done."47herdr agent get builder | jq -r .result.agent.agent_status # must be working, not idle4849# 4. leave the chat — the marker wakes you (cwd: ${CLAUDE_PLUGIN_ROOT})50scripts/tower-watch.sh start --marker /abs/markers/thing.done --prefix builder5152# 5. accept only through the gates (cwd: ${CLAUDE_PLUGIN_ROOT})53node scripts/gate-check.mjs --status /abs/gates/thing.md # exit 0 or it is not done54```5556## Core Concepts5758- **The loop.** Write spec → prompt a pane → start the watch → **stop analysing in this chat** → marker lands → verify → reconvene with the fleet table of every agent, tab, and run. The tower's jobs are specs, launch, land-check, watch, verify, report. It does not do the work.59- **Three grades, preserved upward.** Every load-bearing claim is VERIFIED / INFERRED / NOT DETERMINED. Collapsing the last two is how a gap ships as a conclusion. A report where nothing is ever NOT DETERMINED had a spec that was too loose.60- **"Can't tell" is not "dead."** Lost herdr contact is `unverifiable`, never `exited`, and never an invitation to re-staff. See [patterns](docs/patterns/README.md).61- **Live work is a graph, not a chat.** A new owner ask may add a node or defer one in writing; it never drops a live one. See [work graph](docs/work-graph.md).6263## Daily OS6465The outer loop. Dispatch is the inner loop. Details: [operating loop](docs/operating-loop.md).66671. **Session start.** Load the work graph. Read the live map and the status board. Report fleet, queue head, entitled kinds/models, pin file, dispatch blockers. Do not start agents. Do not edit product repos. Completion: one compact decision summary.682. **Unpaid ask.** Before the reply that starts a new job, settle every ask from the previous owner message: answer it in this reply, or name it parked and why. A pane start does not pay a question. See [work graph](docs/work-graph.md).693. **catalog.** Same board, entitlement focus. Name entitled vs missing kinds/models. Name pin slots whose kind is missing. Do not go outside the board unless the board cannot answer.704. **reconvene.** After a marker: verify, then one owner table (Project, Status, What is true, Wrong/gap, You). That is the conversation. Not a running commentary while panes work.7172On demand (not session-start steps):7374- **Escalate.** An item the tower cannot decide goes to the owner queue after re-verify, or is declined because it is already fixed. Details: [operating loop](docs/operating-loop.md).75- **Handoff.** A handoff note is an output rewritten from live herdr and markers on disk. It is never a source of liveness. Details: [operating loop](docs/operating-loop.md).76- **Closeout.** Route the deliverable into the project's knowledge base, or mark it ephemeral with a reason. Nothing auto-promotes. Details: [operating loop](docs/operating-loop.md).7778## Acceptance gates7980Reuse **unlazy v2** enforcement via the vendored checker. Do not rebuild it. Do not run unlazy **orchestrated** mode inside a herdr fleet — the space PM owns dispatch.8182Every spec names a gates file. Each gate is one observable outcome with `CHECK` / `EXPECT` / `EVIDENCE`. Format: [gate format](docs/gate-format.md). Parent workflow: [gates workflow](docs/workflow.md).8384```bash85CHECKER="${CLAUDE_PLUGIN_ROOT}/scripts/gate-check.mjs"86node "$CHECKER" gates/<name>.md # run unmet checks, flip boxes, write evidence87node "$CHECKER" --status gates/<name>.md # report only, change nothing88```8990Done means `gate-check.mjs --status <gates>` exits **0**. An empty `touch` marker is not done. A checked box with `EVIDENCE: pending` is unmet. A gate you decided not to meet is `ABANDON: <id> <reason>`, not deleted.9192- The implementer may run the checker; **the parent re-runs `--status`** before calling anything done.93- A gates file with zero `- [ ] Gn:` checkboxes exits **2**, never ALL MET.94- Do not write a second checker. This one is vendored for that reason.9596## Documentation9798House leftovers folded here (not six plugins): `herdr-fleet` (HOUSE dispatch, not official herdr), `work-graph`, `space-loop`, `reconvene-table`, `verify-deliverable`, `model-router`, plus **auto-wiki**. Official `herdr` and nvk `wiki-manager` stay separate installs.99100- **[Operating loop](docs/operating-loop.md)** — Session start, catalog, unpaid ask, reconvene, Escalate, Handoff, closeout, and the reconvene-table PR verb list101- **[Dispatch](docs/dispatch.md)** — planes, the spec file, the eight-step delegation protocol, and the three spec-writing failures102- **[Watch & poke](docs/watch-and-poke.md)** — why idle ≠ done, marker polling, the land-check, and how to verify a deliverable independently103- **[verify-deliverable](docs/verify-deliverable.md)** — seven-step re-check; verdict pass / partial / fail104- **[Staffing](docs/staffing.md)** — the role graph, required vs optional seats, kinds/models/effort, chain of command, and time-boxing an autonomous loop105- **[space-loop](docs/space-loop.md)** — Loop block in every PM spec; tower never prompts scout/mentor when a PM is live106- **[herdr-fleet](docs/herdr-fleet.md)** — fleet index, `agent read`, pane-record-before-close107- **[model-router](docs/model-router.md)** — board-first RoutingDecision; pin ∩ entitled108- **[Layout](docs/layout.md)** — worktree as the default implement seat, thread in the agent name, pane labels, and why an extra tab means an extra worktree109- **[Closing](docs/closing.md)** — retiring agents, closing one-shot panes, when a workspace may close, and reloading a plugin without killing a space110- **[Work graph](docs/work-graph.md)** — work graph file, unpaid ask, node states, and typed edges111- **[auto-wiki](docs/auto-wiki.md)** — rewrite wiki pages from a git diff; hook calls this package's script112- **[Patterns](docs/patterns/README.md)** — adopted loops (catalog). Auto-wiki is the named adopt.113- **[fusion-opinion](docs/patterns/fusion-opinion.md)** — read-only N opinions + one architect when the spec says `when=fusion-opinion`114- **[Pitfalls](docs/pitfalls.md)** — the failure catalogue, each with what it actually cost115- **[CLI reference](docs/cli-reference.md)** — the herdr surface a tower uses, verified against v0.7.5116- **[Gate format](docs/gate-format.md)** — the file shape, `CHECK` / `EXPECT` / `EVIDENCE`, `ABANDON`, and how to write a gate that means something117- **[Checker reference](docs/checker.md)** — CLI flags, exit codes, matching, file discovery, timeouts118- **[Gates workflow](docs/workflow.md)** — spec names the gates, implementer fills them, parent re-runs `--status`119- **[Boundaries and attribution](docs/boundaries.md)** — what the gates take from unlazy, what they refuse, and the licence120- **[herdr's own skill](docs/herdr-skill-upstream.md)** · **[herdr README](docs/readme-upstream.md)** — upstream, synced121122## Common Workflows123124- **Session start.** Work graph + live map + status board → one compact decision summary. Do not start agents. Then catalog, or dispatch.125- **Dispatch one task.** Spec file (naming its gates) → worktree workspace (not home `main`) → split + rename pane → `agent start` → `agent prompt` → land-check `working` → `tower-watch.sh start` → on `MARKER_OK`, read the deliverable, re-check its central claim against `gh`, the disk, or the DB yourself, and re-run `gate-check.mjs --status` for exit 0.126- **Stand up a project workspace.** Start the required seats only (usually PM + mentor + one worker), one tab, roles as splits, every pane labelled `#<N> · <role> · <task>`. Add reviewer when there is a mergeable diff, QA when a human will click it.127- **Split investigate-and-change.** Phase A is read-only and ends in `STOP`; the owner decides; Phase B is released separately. Discover-and-fix in one uninterrupted run is how an agent fixes the wrong thing confidently.128129## Scripts130131- `scripts/tower-watch.sh`, `scripts/tower-poke.sh` — the watch/poke loop. Point at a tower root with `TOWER_ROOT`; both are read-only against product repos.132- `scripts/gate-check.mjs` — the acceptance checker. Vendored from Leonxlnx/unlazy (MIT), zero dependencies, Node 16+. Keep the attribution header and [LICENSE.unlazy](LICENSE.unlazy) together; re-vendor by hand per [boundaries](docs/boundaries.md).133- `scripts/auto-wiki.py` — rewrite high-level wiki pages from a git diff. Consumer hook calls this file. Not a copier.134135## Upstream Sources136137- **Repository**: https://github.com/herdrdev/herdr138- **Documentation**: https://herdr.dev/docs/ · [socket API](https://herdr.dev/docs/socket-api/) · [agent skill](https://herdr.dev/docs/agent-skill/)139- **Checker**: [Leonxlnx/unlazy](https://github.com/Leonxlnx/unlazy/blob/main/scripts/gate-check.mjs), MIT140141## Sync & Update142143When the user runs `sync`: re-fetch the upstream README and `skills/herdr/SKILL.md` listed in `sync.json`. The protocol docs are authored, not synced — see `sync.json` notes. When the user runs `diff`: compare cached `docs/` against upstream, re-check `docs/cli-reference.md` against the installed `herdr --help`, and re-read `docs/boundaries.md` before changing the checker.