Captain
IS: conducting a fleet via the captain CLI — fan out tickets, poll captain status,
batch plan approvals and off-script questions into human decisions, nudge stalled agents,
distill fleet memory. IS NOT: typing low-level cmux verbs by hand (use
cmux) or running any pipeline step yourself — the agent self-drives
plan → implement → /pr-reviewer → /tidy → the conditional UI steps → /pr-creator →
/pr-babysitter → verdict.
The driver is a long-lived Claude Code session, not a human at a keyboard; Captain keeps
no state (status derives live from cmux + each worktree's .captain/).
/pr-reviewer and /tidy are separate configured stages in that order, and never a
replacement for one another: the reviewer is read-only and writes committable Fix:
lines, and /tidy applies that report's confirmed findings alongside its own sweep.
References
Load only when the condition applies:
| Reference |
Read when |
| references/heartbeat.md |
A fleet is running and you need to poll — the rung ladder and the --since snapshot protocol |
| references/auto-pickup.md |
The user has explicitly armed /captain loop / "run the dev loop" / "drain the queue" |
Mental model
- Agents self-drive the whole pipeline; do not send routine "continue" prompts. Answer
a real gate, deliver rejection feedback, or nudge only after
status plus a screen read
provides evidence of a stall.
- Status is stateless, derived fresh each call: membership = a cmux workspace whose
cwd has a
.captain/ dir; busy/idle = cmux top run-state tags; gates = the newest
unresolved feed item per cwd; done = a hash-checked .captain/verdict.json. Re-run
any time — nothing desyncs.
- Human gates: Claude plan approval (mandatory — implementation never starts
un-approved), questions/blocked agents, and the merge. Everything else flows on its own.
A codex agent has no plan mode, so its fleet has no plan gate —
approve/reject
have nothing to reply to; its brief tells it to plan then proceed.
- Verdict gate: fan-out writes the definition of done to
.captain/rubric.md; the
agent's fresh-context verifier writes .captain/verdict.json citing the rubric's hash.
A valid pass shows READY with ✓ verified; a fail → NEEDS YOU and its summary; no
verdict → in flight. A criterion may also be na (cannot apply to this diff) — neither
a pass nor a failure.
- Fleet memory:
~/.claude/captain/memory/<repo>/learnings.md (shared per repo) —
fan-out injects ## Rules + the recent ## Inbox; agents append verified learnings at
end of run.
Setup
- Prereqs:
captain install adds the pipeline skills (/pr-reviewer, /pr-creator,
/pr-babysitter from mblode/agent-skills; /tidy lives there too but isn't fetched
by captain install) and checks node, git, claude, cmux, LINEAR_API_KEY. If the CLI
is missing: npm i -g cmux-captain, or npm run build && npm link from a checkout.
- Route each ticket semantically. Your cwd is almost never the ticket's repo, and
nothing picks it for you — a team spans repos, a project spans repos (Pulse v0
lives in linkiq, chat and frontyard), some tickets carry no project. Read the ticket
(description, the code paths/symbols it names, its linked PRs) to decide which repo the
work touches — grep candidates when thin — then pass
--repo-path <repo>.
- Fan out: group tickets by repo, one
captain start <ids…> --repo-path <repo> per
repo (one worktree + workspace + self-driving agent each). start is implicit, so bare
captain TIG-430 works — though a single non-issue word is treated as a typo'd
subcommand and errors. A non-issue arg starts a free-form task in the current checkout.
--base <ref> stacks on a prerequisite branch. Confirm each started[].cwd
(--json) before approving any plan — a worktree in the wrong repo can never pass its
rubric.
--print prepares and writes/prints the brief without launching. Not a dry-run, and
multiple issue ids are rejected.
- Each agent launches on a pinned model + effort (default
default / high), so
it never inherits your driver's tier — override per fleet with CAPTAIN_MODEL /
CAPTAIN_EFFORT (or config .model / .effort).
--agent codex (or CAPTAIN_AGENT / config .agent) is best-effort: full
autonomy, an adapted brief (plan then proceed), no plan gate.
- Arm the heartbeat — see references/heartbeat.md. Never
hand polling back to the human.
The loop
Poll by default once a fleet is running — never ask the human whether to poll, never
offer "ping me when you want an update". Batching gates into one AskUserQuestion keeps
them in control.
| You say |
Run |
| "status" / "what's blocked" / "what's ready" |
For a known run, captain status <ticket-or-workspace…> --json; add --summary for polling. Use unfiltered captain status only when the request is fleet-wide. --repo, --needs, and --ready also narrow — never fetch the full fleet merely to post-filter it. |
| "show me the plans" |
Send up to the bounded batch below to one read-only reviewer per heartbeat; it returns one compact {ticket, summary, scopeDrift, risk, recommendation} decision card per plan. Deep-read only plans it flags high-risk or ambiguous — never spend your window on --scrollback |
| "approve all plans" |
captain approve <ticket> --note "<the card's recommendation>", one call per gate so each carries its own card. Bare captain approve all only on an explicit blanket instruction — it records no reasoning |
| "send 404 back: don't touch auth" |
captain reject tig-404 --note "…" — replies to the gate and types it into the workspace |
| "what's verified" |
captain status — READY rows carry ✓ verified; spot-read verdict.json's criteria before merging |
| "what's been done" / "what's left" / "what are these PRs" |
captain gain --json, read roster — one entry per launch, newest first. verdict: "pass" + a prUrl is done; group: "needs-you" is on the human; live: false is a worktree already merged and removed (it keeps its launch and decision, loses title/verdict/PR). Then /eli5 it. roster.dropped > 0 means narrow with --since 24h. Do not reconstruct this from scrollback or read-screen |
| "this one's gone quiet" |
cmux read-screen --workspace <id>, then cmux send --workspace <id> "continue with your workflow\n" to nudge |
| "distill the learnings" |
Edit ~/.claude/captain/memory/<repo>/learnings.md — promote held-up Inbox bullets to ## Rules, cut slop; ~/.claude/captain/log.jsonl has approve/reject notes |
Escalating NEEDS YOU: once per heartbeat, give pending plan gates to one read-only
batch reviewer with the ticket, repo, and captured plan for each gate. Bound a batch to
at most 8 plans, 6,000 input characters per plan, and 24,000 total, in status order; leave
overflow pending for the next heartbeat and mark a truncated plan ambiguous. It returns
one decision card of at most 80 words per gate. Spend a deeper review only on a card
marked high-risk or ambiguous, or when the human selects read-more. Then batch the
cards into one AskUserQuestion — one decision per gate, options approve
(captain approve <ticket> --note "<the card's recommendation, one line>"),
reject-with-note (captain reject <ticket> --note "…"),
read-more (deeper subagent, re-ask). The card always rides along: --note is the only
thing that puts the reasoning into log.jsonl, and an approve without one is counted by
captain gain as an unexplained approval. Off-script questions surface in that same ask and
are answered verbatim with cmux send --workspace <id> "…\n". One reviewer and one ask
per wake, not per gate.
Gotchas
- Wrong dir is the #1 silent failure. No
--repo-path fans the worktree into your cwd
— a repo with none of the ticket's code, whose rubric never passes. Reroute: close the
workspace (never a group anchor), git worktree remove --force, delete the branch,
relaunch.
- Never approve a plan with no decision card behind it — the read-only batch reviewer
reads it first; high-risk or ambiguous cards get a deeper second read. Pass the card
through:
captain approve <ticket> --note "…". This is now measurable —
captain gain reports decisions.unexplainedApprovals, so a skipped review shows up in
the ledger instead of vanishing.
- Never guess off-script questions — answer verbatim in the workspace, or
reject if
it's a plan.
- Stops at PR-ready — merging and deploying stay with you.
- Never trust a one-line verdict — it gates the label, not the merge. Spot-read the
criteria array: a thin one means the verifier was skipped, and a criterion whose
name
doesn't match the rubric's wording means the bar was softened.
cmux send can silently no-op (text parked unsubmitted while status still reads
"working") — follow every send with cmux send-key --workspace <id> enter and re-read
the screen.
- Verify an unknown run-state before retrying.
run=unknown/— means no live cmux
tag was observed, regardless of agent. Read the screen first. If it's an empty shell,
rerun the same original captain start command in the foreground, including its
repo/base/agent options — Captain's idempotent retry path handles the prepared worktree.
Never reconstruct a hard-coded launch from prompt.txt or blind-relaunch.
- Workspace ids, not names —
status prints the right cmux command per row; copy it.
- Never close an apparent duplicate workspace — it's likely a group anchor (closing
ungroups the fleet); a real duplicate means a stale binary, so rebuild instead.
- Fleet-scale test runs can exhaust the machine. N agents each spawning an uncapped
jest/vitest worker pool (default = cores − 1, ts-jest workers reach 2–3.6GB each) pushed
memory past 100GB on a 48GB machine and triggered kernel jetsam kills of the whole fleet
(Jul 6 2026: three concurrent
yarn test runs ≈ 40 workers). Three layers of defence:
every agent launches with VITEST_MAX_THREADS/FORKS=2 in its env (extend via config
.agentEnv, e.g. {"NODE_OPTIONS": "--max-old-space-size=3072"}), briefs tell agents
to pass --maxWorkers=2, and fan-out prints a note when the target repo's jest config
has no maxWorkers cap. Jest ignores env, so an uncapped repo config is the
remaining hole: cap it in the repo (maxWorkers + workerIdleMemoryLimit).
Reference
- CLI:
captain --help. Source: ~/Code/mblode/captain/src/captain/ (pure core:
view.ts grouping, verdict.ts).
- Low-level cmux verbs: the
cmux skill.
1---2name: captain3description: Conduct a fleet of cmux worktrees — fan out Linear/donebear tickets as self-driving agents, then surface what needs you. Use when asked to "conduct my fleet", "fan out these tickets", "start this ticket", "run these on codex", "what's blocked across my agents", "approve all the plans", "show me the plans", "what's ready to merge", "start the captain", "run the dev loop", or "drain the queue".4---56# Captain78**IS:** conducting a fleet via the `captain` CLI — fan out tickets, poll `captain status`,9batch plan approvals and off-script questions into human decisions, nudge stalled agents,10distill fleet memory. **IS NOT:** typing low-level cmux verbs by hand (use11[`cmux`](../cmux/SKILL.md)) or running any pipeline step yourself — the agent self-drives12plan → implement → `/pr-reviewer` → `/tidy` → the conditional UI steps → `/pr-creator` →13`/pr-babysitter` → verdict.14The driver is a long-lived Claude Code session, not a human at a keyboard; Captain keeps15**no state** (`status` derives live from cmux + each worktree's `.captain/`).1617`/pr-reviewer` and `/tidy` are separate configured stages in that order, and never a18replacement for one another: the reviewer is read-only and writes committable `Fix:`19lines, and `/tidy` applies that report's confirmed findings alongside its own sweep.2021## References2223Load only when the condition applies:2425| Reference | Read when |26|---|---|27| [references/heartbeat.md](references/heartbeat.md) | A fleet is running and you need to poll — the rung ladder and the `--since` snapshot protocol |28| [references/auto-pickup.md](references/auto-pickup.md) | The user has **explicitly armed** `/captain loop` / "run the dev loop" / "drain the queue" |2930## Mental model3132- **Agents self-drive** the whole pipeline; do not send routine "continue" prompts. Answer33 a real gate, deliver rejection feedback, or nudge only after `status` plus a screen read34 provides evidence of a stall.35- **Status is stateless**, derived fresh each call: membership = a cmux workspace whose36 cwd has a `.captain/` dir; busy/idle = `cmux top` run-state tags; gates = the newest37 _unresolved_ feed item per cwd; done = a hash-checked `.captain/verdict.json`. Re-run38 any time — nothing desyncs.39- **Human gates**: Claude plan approval (mandatory — implementation never starts40 un-approved), questions/blocked agents, and the merge. Everything else flows on its own.41 A codex agent has no plan mode, so its fleet has **no plan gate** — `approve`/`reject`42 have nothing to reply to; its brief tells it to plan then proceed.43- **Verdict gate**: fan-out writes the definition of done to `.captain/rubric.md`; the44 agent's fresh-context verifier writes `.captain/verdict.json` citing the rubric's hash.45 A valid pass shows READY with `✓ verified`; a fail → NEEDS YOU and its summary; no46 verdict → in flight. A criterion may also be `na` (cannot apply to this diff) — neither47 a pass nor a failure.48- **Fleet memory**: `~/.claude/captain/memory/<repo>/learnings.md` (shared per repo) —49 fan-out injects `## Rules` + the recent `## Inbox`; agents append verified learnings at50 end of run.5152## Setup53541. **Prereqs:** `captain install` adds the pipeline skills (`/pr-reviewer`, `/pr-creator`,55 `/pr-babysitter` from `mblode/agent-skills`; `/tidy` lives there too but isn't fetched56 by `captain install`) and checks node, git, claude, cmux, `LINEAR_API_KEY`. If the CLI57 is missing: `npm i -g cmux-captain`, or `npm run build && npm link` from a checkout.582. **Route each ticket semantically.** Your cwd is almost never the ticket's repo, and59 **nothing picks it for you** — a team spans repos, a project spans repos (Pulse v060 lives in linkiq, chat _and_ frontyard), some tickets carry no project. Read the ticket61 (description, the code paths/symbols it names, its linked PRs) to decide which repo the62 work touches — grep candidates when thin — then pass `--repo-path <repo>`.633. **Fan out:** group tickets by repo, one `captain start <ids…> --repo-path <repo>` per64 repo (one worktree + workspace + self-driving agent each). `start` is implicit, so bare65 `captain TIG-430` works — though a single non-issue word is treated as a typo'd66 subcommand and errors. A non-issue arg starts a free-form task in the current checkout.67 `--base <ref>` stacks on a prerequisite branch. **Confirm each `started[].cwd`**68 (`--json`) before approving any plan — a worktree in the wrong repo can never pass its69 rubric.70 - `--print` prepares and writes/prints the brief without launching. Not a dry-run, and71 multiple issue ids are rejected.72 - Each agent launches on a **pinned model + effort** (default `default` / `high`), so73 it never inherits your driver's tier — override per fleet with `CAPTAIN_MODEL` /74 `CAPTAIN_EFFORT` (or config `.model` / `.effort`).75 - `--agent codex` (or `CAPTAIN_AGENT` / config `.agent`) is **best-effort**: full76 autonomy, an adapted brief (plan then proceed), no plan gate.774. **Arm the heartbeat** — see [references/heartbeat.md](references/heartbeat.md). Never78 hand polling back to the human.7980## The loop8182**Poll by default** once a fleet is running — never ask the human whether to poll, never83offer "ping me when you want an update". Batching gates into one AskUserQuestion keeps84them in control.8586| You say | Run |87| --- | --- |88| "status" / "what's blocked" / "what's ready" | For a known run, `captain status <ticket-or-workspace…> --json`; add `--summary` for polling. Use unfiltered `captain status` only when the request is fleet-wide. `--repo`, `--needs`, and `--ready` also narrow — never fetch the full fleet merely to post-filter it. |89| "show me the plans" | Send up to the bounded batch below to **one read-only reviewer** per heartbeat; it returns one compact `{ticket, summary, scopeDrift, risk, recommendation}` decision card per plan. Deep-read only plans it flags high-risk or ambiguous — never spend your window on `--scrollback` |90| "approve all plans" | `captain approve <ticket> --note "<the card's recommendation>"`, one call per gate so each carries its own card. Bare `captain approve all` only on an explicit blanket instruction — it records no reasoning |91| "send 404 back: don't touch auth" | `captain reject tig-404 --note "…"` — replies to the gate _and_ types it into the workspace |92| "what's verified" | `captain status` — READY rows carry `✓ verified`; spot-read `verdict.json`'s criteria before merging |93| "what's been done" / "what's left" / "what are these PRs" | `captain gain --json`, read `roster` — one entry per launch, newest first. `verdict: "pass"` + a `prUrl` is done; `group: "needs-you"` is on the human; `live: false` is a worktree already merged and removed (it keeps its launch and decision, loses title/verdict/PR). Then `/eli5` it. `roster.dropped > 0` means narrow with `--since 24h`. Do **not** reconstruct this from scrollback or `read-screen` |94| "this one's gone quiet" | `cmux read-screen --workspace <id>`, then `cmux send --workspace <id> "continue with your workflow\n"` to nudge |95| "distill the learnings" | Edit `~/.claude/captain/memory/<repo>/learnings.md` — promote held-up Inbox bullets to `## Rules`, cut slop; `~/.claude/captain/log.jsonl` has approve/reject notes |9697**Escalating NEEDS YOU:** once per heartbeat, give pending plan gates to **one read-only98batch reviewer** with the ticket, repo, and captured plan for each gate. Bound a batch to99at most 8 plans, 6,000 input characters per plan, and 24,000 total, in status order; leave100overflow pending for the next heartbeat and mark a truncated plan ambiguous. It returns101one decision card of at most 80 words per gate. Spend a deeper review only on a card102marked high-risk or ambiguous, or when the human selects **read-more**. Then batch the103cards into **one** AskUserQuestion — one decision per gate, options **approve**104(`captain approve <ticket> --note "<the card's recommendation, one line>"`),105**reject-with-note** (`captain reject <ticket> --note "…"`),106**read-more** (deeper subagent, re-ask). The card always rides along: `--note` is the only107thing that puts the reasoning into `log.jsonl`, and an approve without one is counted by108`captain gain` as an unexplained approval. Off-script questions surface in that same ask and109are answered verbatim with `cmux send --workspace <id> "…\n"`. One reviewer and one ask110per wake, not per gate.111112## Gotchas113114- **Wrong dir is the #1 silent failure.** No `--repo-path` fans the worktree into your cwd115 — a repo with none of the ticket's code, whose rubric never passes. Reroute: close the116 workspace (never a group anchor), `git worktree remove --force`, delete the branch,117 relaunch.118- **Never approve a plan with no decision card behind it** — the read-only batch reviewer119 reads it first; high-risk or ambiguous cards get a deeper second read. Pass the card120 through: `captain approve <ticket> --note "…"`. This is now measurable —121 `captain gain` reports `decisions.unexplainedApprovals`, so a skipped review shows up in122 the ledger instead of vanishing.123- **Never guess off-script questions** — answer verbatim in the workspace, or `reject` if124 it's a plan.125- **Stops at PR-ready** — merging and deploying stay with you.126- **Never trust a one-line verdict** — it gates the _label_, not the merge. Spot-read the127 criteria array: a thin one means the verifier was skipped, and a criterion whose `name`128 doesn't match the rubric's wording means the bar was softened.129- **`cmux send` can silently no-op** (text parked unsubmitted while `status` still reads130 "working") — follow every send with `cmux send-key --workspace <id> enter` and re-read131 the screen.132- **Verify an unknown run-state before retrying.** `run=unknown`/`—` means no live cmux133 tag was observed, regardless of agent. Read the screen first. If it's an empty shell,134 rerun the same original `captain start` command in the foreground, including its135 repo/base/agent options — Captain's idempotent retry path handles the prepared worktree.136 Never reconstruct a hard-coded launch from `prompt.txt` or blind-relaunch.137- **Workspace ids, not names** — `status` prints the right `cmux` command per row; copy it.138- **Never close an apparent duplicate workspace** — it's likely a group anchor (closing139 ungroups the fleet); a real duplicate means a stale binary, so rebuild instead.140- **Fleet-scale test runs can exhaust the machine.** N agents each spawning an uncapped141 jest/vitest worker pool (default = cores − 1, ts-jest workers reach 2–3.6GB each) pushed142 memory past 100GB on a 48GB machine and triggered kernel jetsam kills of the whole fleet143 (Jul 6 2026: three concurrent `yarn test` runs ≈ 40 workers). Three layers of defence:144 every agent launches with `VITEST_MAX_THREADS/FORKS=2` in its env (extend via config145 `.agentEnv`, e.g. `{"NODE_OPTIONS": "--max-old-space-size=3072"}`), briefs tell agents146 to pass `--maxWorkers=2`, and fan-out prints a note when the target repo's jest config147 has no `maxWorkers` cap. **Jest ignores env**, so an uncapped repo config is the148 remaining hole: cap it in the repo (`maxWorkers` + `workerIdleMemoryLimit`).149150## Reference151152- CLI: `captain --help`. Source: `~/Code/mblode/captain/src/captain/` (pure core:153 `view.ts` grouping, `verdict.ts`).154- Low-level cmux verbs: the [`cmux`](../cmux/SKILL.md) skill.