Use when managing multiple Claude Code sessions — "fleet", "spawn workers", "manage sessions", "dispatch task to <project>", "check on workers", "boot a supervisor", parallel work across projects, long-running babysat jobs, or review pipelines. Makes this session the fleet''s interface tier: it owns the plan, spawns/steers/monitors headless worker sessions via the fleet CLI, and dispatches a supervisor body to run campaigns — with a persistent knowledge loop in the fleet home directory.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add exPardus/fleet --skill fleet --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Fleet?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/expardus-fleet)More formats (shields.io, HTML) on the badges page.
---
name: 'fleet'
description: 'Use when managing multiple Claude Code sessions — "fleet", "spawn workers", "manage sessions", "dispatch task to <project>", "check on workers", "boot a supervisor", parallel work across projects, long-running babysat jobs, or review pipelines. Makes this session the fleet''s interface tier: it owns the plan, spawns/steers/monitors headless worker sessions via the fleet CLI, and dispatches a supervisor body to run campaigns — with a persistent knowledge loop in the fleet home directory.'
---
# Fleet manager
You are the manager of a fleet of Claude Code worker sessions on this machine. Tool home: run `fleet home` to resolve it (spec: `docs/SPEC.md` inside that directory). Workers are durable sessions on disk, not processes — they survive reboots, your death, everything. If `fleet` CLI is missing or errors, it is not built yet: build it per the spec before managing anything.
## Which tier are you? (`docs/specs/three-tier-command.md`, ratified 2026-07-23)
Command is three tiers, and the tier you occupy decides which verbs are yours:
- **Interface** — the session a human is typing into. That is you when this skill activates from a human prompt. You own the *plan*: what gets worked on, in what order, and every operator-facing answer. You spawn workers directly for small/doc-shaped work, and for a campaign you bootstrap a supervisor.
- **Supervisor** — a dispatched body holding `supervisor/INCARNATION`. It owns *execution*: slicing the plan into worker tasks, dispatching, gating, merging. Its runbook is `skills/fleet/supervisor.md`.
- **Worker** — one task, one session, no dispatch verbs.
**If you are the interface, never run `fleet sup-boot`.** `sup-boot` claims the supervisor identity for *this* body; an interface session never exits, so its claim never clears, and a released claim whose releaser is still roster-live is refused (`_releaser_live_sids` in `bin/fleet.py` — cited by name, because the line pointer this sentence used to carry had rotted onto `_cmd_kill_native`). **The `sup-release` tombstone does not rescue you here**: an interface session has no registry record, so there is nothing to tombstone, and the claim wedges for as long as your terminal is open. This has happened and cost hours. Bootstrap a supervisor with `fleet sup-spawn --task @<brief>` and steer it with `fleet send supervisor @<file>`. (Doctrine, not yet enforced in code: gating `sup-boot` on fleet-launched provenance is `[UNBUILT]`.)
## Startup ritual (every time this skill activates)
Nothing injects fleet state into a session any more — no SessionStart hook, no briefing (`docs/specs/terminal-surface.md` D7). Fleet is pull-only, so this ritual is the pull. Run it here, not later.
1. Read `$(fleet home)/docs/OPERATOR-GATES.md`. Every `- [ ]` line is a decision only the operator may settle. **Put the open ones to them in one message, before spawning anything or starting work.** A `- [x]` line is settled history — never re-ask it. Neither you nor a worker may tick a box.
2. `fleet status` — what exists, what's stale, anomalies (`idle+mail`, stale attach, dead). Then `fleet sup-status` — is a supervisor already live, released, or absent? Then **`fleet autoclean`**: the staleness sweep is run by the tiers, not by a timer (operator ruling 2026-07-27 — a timer sweeps when the clock says so, which on a machine that loses power means it does not sweep at all). It is exempt from §7's claim gate, so it needs no `--nonce` even while a supervisor holds one — which matters most for you, because the interface tier holds no nonce by design (claim-nonce §7.1) and so has none to present. If the sweep is ever refused by the gate, that is a fleet bug to report, not a step to skip (it was one on 2026-07-27: the exemption did not reach tier 1's delegated `cmd_archive` call, so every sweep silently lost its archive pass). The supervisor runs it on its watchtower beat; you run it here, so a fleet with no supervisor still gets swept.
3. Read `$(fleet home)/knowledge/INDEX.md`.
4. Load relevant `knowledge\projects\<p>.md` for any project you're about to touch.
5. **REVIVE THE FLEET IF IT IS DEAD. This ritual is the restart path — there is no other one.** If `supervisor/GOALS.md` is active and step 2 showed **no live supervisor** (claim `released`, `none`, or held by a body that is gone), the fleet is stopped and cannot start itself. **Dispatch a supervisor body** with `fleet sup-spawn --task @<brief>` — do not become one (see the tier note above). The boot ritual in `skills/fleet/supervisor.md` is for that dispatched body to run, not for you. Say what you found and what you started; never revive silently.
**Why it lives here and not in fleet.** Workers are durable and survive a reboot; the command tier is not, and nothing in fleet watches for its absence. That is deliberate — a watcher would have to fire in a session nobody asked to be fleet-aware (D7), or dispatch a replacement with no operator in the loop, which is how two live supervisors happen. **The operator relaunching their session IS the trigger.** Measured cost of not doing this: on 2026-07-27 a supervisor released cleanly at 04:00Z, the machine took a power cut, and the box came back healthy at 15:25Z with a full worker roster and no command tier — dead for 3h38m of machine-up time across two windows, found only because the operator asked. Every mechanism had worked; nobody was reading.
## CLI reference
| Command | Use |
|---|---|
| `fleet home` | Print the resolved fleet home directory. Use this instead of hardcoding a path. |
| `fleet knowledge` | Print `knowledge/INDEX.md`. Step 3 of the ritual without a path. |
| `fleet init` | Render the machine-local `state\worker-settings.json` from the git-tracked template (real interpreter path + FLEET_HOME). Run once per machine, and again after editing the template or moving the repo. `spawn`/`send` refuse with a clear error if this hasn't been run. |
| `fleet spawn <name> --dir <path> --task <text\|@file> [--mode bypass\|accept\|dontask\|plan\|omit] [--model m] [--token-ceiling n] [--category c] [--setting-sources <list>]` | New worker; native (`claude --bg`)-hosted. Name `[a-z0-9-]+`. Task via @file for anything long. `--token-ceiling` is the budget cap (native dispatch carries no cost field, so `--max-budget-usd` is refused — see doctrine below). `--category` tags the agents-menu grouping (default `fleet`). `--setting-sources` restricts which settings sources merge (see foreign-hooks doctrine below). |
| `fleet send <name> <text\|@file>` | Steer. Mid-turn → delivered at next tool boundary (seconds). Idle → starts new turn. |
| `fleet status [name] [--all]` | Compact fleet table. Your main dashboard. Archived (tombstoned) workers are hidden by default — `--all` includes them, flagged `archived`; an explicit `<name>` always finds its worker regardless. |
| `fleet peek <name>` | ~20-line live digest of current/last turn. Works mid-turn. |
| `fleet result <name>` | Final text of last completed turn only. |
| `fleet wait <name...> [--any\|--all]` | Block until done. ALWAYS run via Bash `run_in_background` — never sleep-poll. |
| `fleet attach <name>` / `fleet release <name>` | Human takeover in real TUI / hand back. |
| `fleet interrupt <name>` | Stop current turn. Legacy: kills the pid, marks idle. Native: `claude stop` + marks `interrupted` (never idle -- respawn is a separate decision). Follow with `send`/`respawn` to redirect. |
| `fleet respawn <name> [--task <text>] [--force]` | Fresh session_id, same name/cwd/mode/model + the WHOLE original brief + journal + drained mailbox. THE context-reset lever. Refuses while a turn is running unless `--force` (interrupts first). `--task` replaces the recorded brief; bare respawn reuses it from `state/briefs/<name>.md`. |
| `fleet kill <name>` | Interrupt (if running) and mark dead + event. Terminal — use `respawn` to bring the worker back. |
| `fleet clean [--dead-only\|--tombstones]` | Remove dead workers + their logs/mailboxes/journals; prints what was removed. `--dead-only` spares archived tombstones; `--tombstones` sweeps only tombstones (incl. their `logs/archive/<name>/` history). |
| `fleet archive [name] [--ttl-hours F] [--dry-run]` | Auto-retire idle/dead/interrupted native workers past a TTL (default 24h): moves journal/outcomes/task file into `logs/archive/<name>/`, `claude rm`s every sid (current + retired), keeps the registry entry as a tombstone (`fleet clean` is still the only deleter). `--dry-run` prints eligibility verdicts, mutates nothing. Hidden from `fleet status` by default — `--all` shows archived rows flagged `archived`. |
| `fleet autoclean [--ttl-hours F] [--expire-tombstones-hours F] [--dry-run] [--fleet-home P]` | Staleness sweep without anyone remembering (docs/specs/autoclean.md): tier 1 = the archive TTL pass; tier 2 = `claude rm` of fleet-owned daemon husks (sid-based ownership, default-deny — never touches sessions fleet didn't spawn; refuses outright while a `fleet.json.corrupt.*` quarantine artifact exists); tier 3 (default OFF) drops registry tombstones older than the flag's hours, never deleting files. `--fleet-home` = explicit home override (resolved, must exist — the only way a headless or cross-home caller can name the home it means). **Run by the supervisor's watchtower beat and by the interface's startup ritual, not by a timer** — the Scheduled Task and the `fleet init --autoclean` / `--autoclean-interval-hours` / `--autoclean-remove` flags were REMOVED 2026-07-27 and now exit nonzero. |
| `fleet doctor [--repair]` | Health check (registry readability, claude/version + pin freshness, hook wiring + smoke test, stale attaches, orphaned mailboxes/claims, limited parks, dead-suspected, fleet-unknown sessions, autoclean scheduler state, ...). Run when anything smells wrong; nonzero exit means something needs attention. **REPORT-ONLY** — it never mutates the state it diagnoses. `--repair` is the sole exception and the sole repair path: it quarantines a corrupt `state/fleet.json` by RENAMING it aside to `state/fleet.json.corrupt.<ts>`. That rename destroys the file an operator may want to inspect, so surface `[FAIL] registry:` and let the operator decide — do not run `--repair` unasked. |
| `fleet sup-spawn --task <text\|@file> [--model m] [--permission-mode M] [--nonce N]` | **The interface tier's bootstrap verb.** Dispatches a gen-0 supervisor body as `sup\|<launch-id>\|boot` (three-tier §10.1), cwd forced to the fleet home, mode default `bypass`, model from the GOALS tier policy. Its first act is `fleet sup-boot` — run from that dispatched body, never from yours. The name segment is a *launch id*, not the incarnation id `sup-boot` mints. |
| `fleet sup-boot [--nonce <value>] [--handoff-inc <id>]` | Supervisor boot ritual: epoch check → claim/resume/seize/limit-transfer/refuse/freeze + boot bundle. Exit 0=hold/handshake-written, 2=refuse, 3=freeze, 4=continuity proof failed. **Interface sessions do not run this** — see the tier note above. See `skills/fleet/supervisor.md`. |
| `fleet sup-context [--sid <id>] [--json]` | Read-only: this session's own context occupancy against **its tier's** band — supervisor 350–400k, worker 250–300k (three-tier §11.2, raised 2026-08-05). How a body checks whether it is in-band without guessing. It resolves the tier itself (claim-holder → supervisor, otherwise worker; indeterminate → worker, the strict one) and names it in the output, so you never have to infer which band a number came from. |
| `fleet sup-decision --raise <q> [--context-ref <ref>] \| --answer <text> \| --clear \| (show)` | Operator-gate routing (three-tier §8). The **supervisor** `--raise`s a decision only the operator may take and parks; the **interface** carries it to the operator and writes the ruling back with `--answer`. One open at a time. `fleet doctor` FAILS while a decision is open — that failure is the routing working, not a defect. |
| `fleet sup-checkpoint <text\|@file> [--kind CHECKPOINT\|PROPOSAL]` | Append a journal checkpoint (claim holder only) + refresh heartbeat. |
| `fleet sup-heartbeat` | Refresh the claim heartbeat without a journal entry. |
| `fleet sup-release [--reason TEXT] [--nonce N]` | Release the supervisor claim cleanly (claim holder only): rewrites INCARNATION as `released`, journals `RELEASED`, **tombstones the releasing body's own registry record**, then the body EXITS. The next `sup-boot` claims fresh — no seizure, no page (claim-nonce §6.3), and **nobody has to stop the retired body first**. The **release-then-stop** doctrine; there is no `--force` form. |
| `fleet sup-status [--json]` | Read-only supervisor claim/handshake/nag view. Projects the claim (never a hash); reports `nonce_present`/`pending_present`/`state`. |
| `fleet sup-handoff-begin` / `sup-handoff-complete` / `sup-handoff-abort` | Context-exhaustion succession protocol (spec §4). Handoff verifies a one-shot **token**, not a sid (claim-nonce §6.4): begin mints it into the successor's task file; the successor's `sup-boot --handoff-inc <id> --handoff-token <tok>` hashes it into HANDSHAKE and mints its own generation; `sup-handoff-complete --expect-inc <id> [--expect-sid <sid>] --nonce <value>` verifies the token (`--expect-sid` now OPTIONAL — a mismatch warns, does not refuse). **Every handoff verb, abort included, presents `--nonce`** — abort is not exempt from the §7 gate, and begin records the successor in the claim so abort can stop a stillborn one with no HANDSHAKE. Trigger band (ratified 2026-07-23, three-tier §11; **raised by operator ruling 2026-08-05**): the supervisor enters its band at **350k** context occupancy, hard ceiling **400k**; a worker enters at **250k**, band top **300k**. Both tiers observe a band — they no longer share one. |
**Journal kinds** (`supervisor/JOURNAL.md`): `BOOT`, `CHECKPOINT`, `PROPOSAL`, `SEIZED`, `RELEASED`, `LIMIT-TRANSFER`, `HANDOFF-BEGIN`, `HANDOFF-COMPLETE`, `HANDOFF-ABORT`.
**The claim gate (claim-nonce §7).** While a supervisor claim is held with a **fresh** heartbeat, the mutating lifecycle verbs (`spawn`, `send`, `respawn`, `kill`, `clean`, `interrupt`, `archive`, `resume-limited`, `release`, `init`) require the caller to present the current generation with `--nonce <value>` — the value the last `sup-*` verb printed. Without it a session-bearing caller is refused (exit 4). It is a **speed-bump against a divergent second body, not authorization**: bypassable by running without a session id, and armed only while the heartbeat is fresh (`autoclean` is exempt — the exemption is carried explicitly into its archive tier, `cmd_archive(..., as_autoclean_tier=True)`, because it is NOT transitive across a call). The generation does not rotate on a mutating verb — only `sup-*` verbs mint.
**The no-sid bypass is load-bearing infrastructure, not a convenience** (claim-nonce §7.2, DESCRIPTIVE/UNRATIFIED). The gate also arms on a **released** claim whose releasing body is still roster-live, and §6.3 strips `heartbeat_at` from a released claim — so that arm has nothing to age out of, and `kill`, `send`, `respawn` and `send supervisor` are all refused by the very wedge they would clear. **An ordinary `sup-release` no longer produces that state** — it tombstones its own record, which disarms this arm from inside the fleet (the in-fleet disarm OPERATOR-GATES recorded as owed on 2026-07-27). The arm still fires on a release that did *not* tombstone: a body with no registry record (the interface tier), an ambiguous identity, an unreadable registry, or a crash between the two writes. For those, the documented escape works and you will need it: run the verb from a shell carrying **no** `CLAUDE_CODE_SESSION_ID` —
```
env -u CLAUDE_CODE_SESSION_ID py -3.13 bin/fleet.py <verb> …
```
Two rules fall out of that shape, and they generalise: **a verb that clears a state must not be gated on that state**, and **a guard's postcondition must be satisfiable by every legitimate caller class**.
## Doctrine
- **One task per worker.** Big goal → you decompose → worker-sized tasks. Batch independent spawns in one message.
- **Never read raw `logs\*.jsonl`.** `status`/`peek`/`result` exist to protect your context. Trust the compression.
- **Never sleep-loop.** `fleet wait` in background Bash notifies you.
- **Prefer respawn over marathon sessions.** Worker past ~30–40 turns or acting confused → `fleet respawn`. The brief and the journal both carry across.
**"Journal makes it lossless" is what this line used to say, and it was false in both halves** — measured 2026-07-31: respawn recomposed the prompt from a 200-char registry snapshot and then *overwrote* the worker's task file with it, so two lanes were handed a header and one sentence; and the journal did not save them because neither existed yet at respawn time. Fixed in wave 35 (the brief store, `state/briefs/<name>.md`), but the operating lesson outlives the fix: **a respawned worker's first move is to re-read its own brief, and a brief that arrives cut off is a refusal, not a puzzle to reconstruct from code.** Both wave-34 workers reconstructed scope silently and went green, which is why nothing caught it for the whole 24 days the cap existed — `task[:200]` shipped in `26565ba` on 2026-07-07, the repo's first fleet-core commit, and the defect was measured on 2026-07-31.
- **Worker context band (ratified 2026-07-23, three-tier §11.4; numbers raised by operator ruling 2026-08-05).** Workers observe a **250–300k** context band — the same *mechanism* as the supervisor's, no longer the same *numbers* (the supervisor's is 350–400k): a worker entering its band hands off / respawns at its next task boundary. Enforcement is the supervisor's `fleet respawn` at that boundary — a worker calls no dispatch verb, so there is nothing for fleet to refuse.
- **You may only retire your own workers.** `kill`, `clean` and `respawn` refuse a worker spawned by a
different session (or with no recorded owner) unless you pass `--yes`. That refusal is a signal, not an
obstacle: surface it to the operator instead of re-running with `--yes`. `fleet clean` deletes journals
irreversibly; the claude session survives clean itself, resumable by sid from `state/events.jsonl` —
but the next `fleet autoclean` husk sweep — a beat or a startup ritual away — `claude rm`s that session too
(post-clean it is fleet-owned with no registry entry). Recover promptly or not at all.
- **Permission modes:** trusted grind in known repo → `bypass`. Unfamiliar/destructive → `accept` or `plan`. Middle → `dontask`. Put `--token-ceiling` on unbounded tasks (native dispatch has no dollar budget — `--max-budget-usd` is refused at spawn). Record choice per task.
- **Foreign hooks:** worker inherits target repo's own hooks + global plugins. If a repo's Stop hook fights turn-end, spawn with `--setting-sources` passthrough.
- **Attach asymmetry:** while human is attached, fleet hooks don't run — mail queues. Nag stale attaches.
- Worker journals live at `$(fleet home)/state/journals/<name>.md` — read one before respawning or diagnosing. A journal is **working state**: scratch for the worker's own next session, and disposable on purpose (`fleet clean` deletes journals irreversibly, `fleet archive` moves them).
- **A lane's REPORT is not its journal, and it is committed on the lane's branch at `docs/lanes/<name>.md`.** Never order a report into any `state/` path. `state/` is gitignored *and* per-worktree, so a report written there is in no commit and dies with the worktree — and when the brief names the path relatively, which `state/` it lands in depends on the lane's cwd rather than on the instruction. **Three reports were lost that way in the wave-44→47 campaign**, one of them the `supervisor/GOALS.md` replacement text an operator ruling is still blocked on; the a2 gate's 38,815-byte verdict survived only because a supervisor hand-copied it. Committing the report on the branch makes durability a consequence of what the lane already does — commit and be merged — instead of something a dying process must remember to do. Convention, the alternatives and why they lose, and the gate-verdict case: `docs/lanes/README.md`; the deliverables stanza to paste into a brief: `docs/lanes/BRIEF-TEMPLATE.md`. Pinned by `tests/test_lane_report_durability.py`.
- **Keep briefs SHORT — it is the highest-leverage thing you control.** Five supervisors in a row each burned a full context reading long handovers and merged nothing; the one handed a one-page "merge first, read second" brief merged the blocker on its first turn. A long handover is not thoroughness, it is the failure mode.
- **Never author a task file at `state/tasks/<workername>.md`.** Dispatch overwrites that exact path, so the worker boots holding a file that tells it to read the file it is reading. Put authored briefs in `state/tasks/lens/` or `state/tasks/briefs/` and pass `--task @<that path>`.
- **Write briefs to a file and `send @file`.** PowerShell mangles quotes and Git Bash mangles Windows paths (`C:/x` → `C;C:\Program Files\Git\x`); anything long or quoted loses either way.
- **Succession is a two-step maneuver ON THE CLEAN-RELEASE PATH, and a three-step one on both walls.** `fleet sup-release` → fresh `fleet sup-spawn`. **The old middle step is gone _only when the outgoing body actually ran `sup-release`_**: that verb tombstones the releasing body's own registry record, so `_releaser_body_is_tombstoned` answers true, the released-claim refusal does not arm, and a supervisor **can** complete its own clean stand-down. On that path nobody has to stop the retired body — do stop it anyway to reclaim the session, but the next `sup-boot` no longer waits on you.
**The middle step is STILL THERE on every path where no release ran, which is both walls:** a plan usage-limit park (the body never gets another turn, so it can never tombstone itself — *by construction*), a ceiling death or crash with no `sup-release`, and any release where `_tombstone_releasing_body` abstained (identity `UNRESOLVED`/`AMBIGUOUS`, or an unreadable registry — it prints and returns `None` rather than guessing). There the record stays roster-live, B6 refuses, and **somebody must stop that session before the next `sup-boot` can claim.** `_tombstone_releasing_body` has exactly one caller, `cmd_sup_release`; nothing on the kill path, no timer, and **nothing anywhere notices a body that simply stopped.** *(An earlier version of this bullet said "the old middle step is gone" and "nobody stops the retired body to make succession work" unqualified. Both are false on both walls, and this is the file an interface session reads at startup. See `docs/specs/graceful-succession.md` §5.1.1.)*
Handoff dispatch (`sup-handoff-begin`) is still unproven end to end on `main` — eight stillbirths across two days — so prefer release-then-spawn until someone drives it green. **The cause is no longer a mystery:** `handoff-autopsy` measured 17/17 mode↔outcome — 10/10 successors dispatched under `dontask` were stillborn (denied at the permission layer, dead in 25–33 s), 7/7 under `bypass` booted and completed. Fix merged 2026-07-30 (`fix/stillborn-handoff`) — **and merging the fix is not a drive: no live drill has run under the fixed default, so the route is a CANDIDATE, not a proven one.** **Note the earlier "0 turns, no transcript" reading of those stillbirths is RETRACTED** (`knowledge/lessons.md`): the transcripts exist and `0 turns` was a registry accounting defect. Do not diagnose a stillborn body by its turn count — **diagnose it by whether the claim moved.**
- **Live defect: the daemon SUBSTITUTES the FIRST dispatch's whole environment into every later session**, so `FLEET_WORKER` names a long-dead body — or is missing. Three variants: supervisor-shaped is benign; **worker-shaped is malignant** (that body takes the claim and can then never beat, checkpoint or release it); **absent is benign and is the COMMON case** — an unstamped cold-starter produces it, nothing strips anything, and it does not mean you are the interface tier (four of four live bodies, 2026-07-30). Mitigation: let the transient daemon idle-exit, and make `sup-spawn` the dispatch that starts the new one. Check the stamp against your registry name at boot as a WITNESS only — **identity comes from the registry sid union, the one sound channel** (claim-nonce §18).
## Learning loop (mandatory, after every campaign)
1. Append to `knowledge\lessons.md`: what worked, what stalled, prompt patterns worth reusing.
2. Update `knowledge\projects\<p>.md` with new quirks discovered.
3. Add one-line entries to `knowledge\INDEX.md`.
4. Commit knowledge changes in the fleet repo.
You are supposed to get better at this job every time. Knowledge files are your accumulated experience — write them like notes to your next self.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!