Open many Claude Code windows on one project and have each work independently
but collaboratively. A small committed .fleet/ folder turns every window into a
self-registering agent — and edits to a file another live agent has claimed are
hard-blocked by the harness, so windows never silently clobber each other.
Open a second Claude Code window on the same repo and the two will happily edit the same file seconds apart — silently overwriting each other's work.
Fleet makes that impossible: a claimed file is denied at the
PreToolUsehook withexit 2— a real block, not a warning — with no daemon, no server, no ports.
- Hard block, not a warning — editing a file a live foreign agent claimed returns
exit 2; the edit never lands. - Atomic claims — a claim is an atomic
mkdirof a lock dir (the one POSIX op that's both atomic and fail-if-exists), so two windows opening in the same second can't both win. - Self-healing — dead windows are reaped by heartbeat timeout (
stale_after_s, default 180 s); nothing stays locked forever.
- Self-registering agents — each window becomes
agent-NonSessionStart, silently coexisting with your other hooks. - Live roster in context — every turn injects who's live and what they hold, so the model claims before editing on its own.
- Agent-to-agent messaging —
msg/inboxto negotiate handoffs between windows.
- Just files + 4 hooks — no daemon, no server, no ports, no dependencies beyond Bash + Claude Code.
- Drop-in —
bash install.sh /path/to/projectcopies.fleet/and wires the hooks.
- Shared tree (default) — all windows edit one working copy; claims + the guard prevent same-file collisions.
- Worktree mode — each window gets its own git worktree (true file isolation) with Fleet coordinating intent across them.
- A sibling window's uncommitted work can't false-red your push. A pre-push gate that lints/tests the whole repo
reads the filesystem — i.e. every live window's uncommitted work at once — so one window's mid-build files
(an untested new component, a half-edited config) could block another window's push even when its commit was
perfectly green.
.fleet/bin/isolated-gate.shfixes it: when the shared tree is dirty, it validates the pushed commit in a clean ephemeral git worktree (with the project's heavy deps symlinked in) — bypassing the sibling's work, never touching the primary tree or stashing anyone's changes. Clean tree → runs in place (fast path). - One line to adopt — wrap your existing gate in your pre-push hook:
exec .fleet/bin/isolated-gate.sh -- bash <your gate>.
- Each window keeps its OWN anchor goal. The
goalstackanti-drift anchor keyed only by project, so parallel windows shared one stack — Window B's goal leaked into Window A's re-injected "active goal" line. Nowgoalstackkeys per window (by the Claude Code session id): each window has its own goal + parent stack, so an interjection in one window never rewrites another's focus. A brand-new window inherits the last shared goal as a baseline (single-window continuity across session turnover is preserved); live windows each read their own — never cross-contaminated. Ships as.fleet/bin/goalstack;fleet.sh goal set|show|note|popdrives it.
- A
msgcan actually WAKE the recipient, instead of sitting unseen until they next happen to run. A file append can't wake an idle agent (it's blocked on its stdin pipe, not polling the inbox), so Fleet closes the loop two ways. Live windows:fleet.sh wake-cmdprints a one-line watcher to hand to the Monitor tool — a new DM then re-invokes that window to act (the SessionStart nudge reminds each agent to arm it once). Closed windows: the optional, opt-inwake-dispatcher.shheadless-resumes an offline session to process its inbox — with a process-liveness alive-guard (never races a live transcript), rate-limit + cooldown + kill-switch, and dry-run by default (--selftestproves every guard bites). AndSessionEndnow preserves UNREAD DMs so a handoff to a window that closes first is never lost. The dispatcher is the one part that steps beyond "just files + hooks" — it is never installed or started byinit.
/coordinatoronboards a window as the dedicated git push/merge/integration coordinator: it orients on the repo + worktrees + live agents, arms the monitor, then integrates every lane's verified work — merge the frozen branch tip, run the pre-push gate, verify the push actually landed (authoritativels-remote, never trust exit-0), de-drift every worktree, and DM the lane./armis the one-command re-arm of the wake-on-message monitor after a restart./brainonboards a window as the fleet's knowledge-oracle + research seat: peers offload questions withfleet.sh brain ask "<q>"instead of pausing their own work; the Brain answers from any available memory/knowledge tooling + live web research, cites + honesty-bands, and captures durable findings./task-manageronboards a window as the fleet's goals/tasks/status coordination seat: it keeps every agent lane's active goal current, surfaces stalls + reachability gaps + blockers fromroster/monitors, routes work withfleet.sh msg, and gives the operator a clean "who's on what / what's next / what's stuck" readout (Fleet's own primitives are the substrate; any richer project board is optional). All four ship under.fleet/skills/andinstall.shdrops them into your project's.claude/skills/. Full coordinator playbook: docs/COORDINATOR.md.fleet.sh retire [<agent>] [--force]safely unloads an agent from the fleet on demand — the sibling of the automaticSessionEndderegister. It works for any agent (agent-N/ short / session-id; no arg = yourself), releases its claims, removes its record + wake breadcrumb, vacates the Brain seat if it held it, and announces aretireboard event. Safe-by-default: it refuses to drop a claim over uncommitted (dirty) work, or to strand a foreign agent's unread DMs, unless you pass--force.
The hero above is a real session — three live agents, and a claimed file hard-blocking a foreign edit. A couple more, straight from the CLI:
doctor — health, config, and hook wiring at a glance
$ .fleet/bin/fleet.sh doctor
== fleet doctor ==
Fleet v0.1.0 (schema 1)
config: claim_mode=prefix block_mode=block stale_after_s=180 state_location=local
live agents: 3 active claims: 1
tools: jq ✓ python3 ✓ git ✓
hooks: wired in .claude/settings.json (OK)
scripts: all executable (OK)
stale agent files (will be reaped): 0Worktree mode — true file isolation per window
$ .fleet/bin/fleet.sh worktree enable
$ .fleet/bin/fleet.sh worktree new checkout-ui # this window gets its own git worktree| Prerequisite | Notes |
|---|---|
| Claude Code | the windows you open become agents |
Bash + git |
POSIX shell; git only for worktree mode |
# drop Fleet into any existing project
bash install.sh /path/to/your/project # copies .fleet/ + wires the hooks
# …or, after copying .fleet/ in yourself:
bash .fleet/bin/fleet.sh initThen reopen your project windows (hooks load at startup) and open as many as you like — each is now a coordinating agent.
.fleet/bin/fleet.sh roster # who's live, what they hold
.fleet/bin/fleet.sh claim src/api "auth" # reserve an area before editing
.fleet/bin/fleet.sh release src/api # done
.fleet/bin/fleet.sh msg agent-1 "ping" # message another window
.fleet/bin/fleet.sh inbox # read messages
.fleet/bin/fleet.sh wake-cmd # print the Monitor-tool watcher that WAKES you on a new DM
.fleet/bin/fleet.sh worktree enable # opt into per-window git worktreesOne window drives integration — run /coordinator in it to become the git
push/merge coordinator (it orients, arms the monitor, and lands every lane's verified
work); /arm re-arms your wake-on-message monitor after a restart. Both are
installed into .claude/skills/ by install.sh — see docs/COORDINATOR.md.
Four Claude Code hooks over a plain-file state dir — no process to run:
Claude window ─┐
(SessionStart)│ register.sh ──▶ ┌───────────────────────────┐
(UserPrompt) │ awareness.sh ──▶│ .fleet/state/ (gitignored)
(PreToolUse) │ guard.sh ──────▶│ ├── agents/<id>.json (roster + ♥ heartbeat)
(SessionEnd) │ deregister.sh ─▶│ ├── claims/<path>/ (atomic mkdir locks)
│ │ ├── board.jsonl (intent log)
edit blocked ◀┘ exit 2 ◀──────│ └── inbox/<agent>/ (messages)
└───────────────────────────┘
register.sh(SessionStart) — registers the window asagent-N, silently.guard.sh(PreToolUse(Edit|Write|…)) — denies edits to files a live foreign agent claimed (exit 2).awareness.sh(UserPromptSubmit) — heartbeat + injects the live roster each turn.deregister.sh(SessionEnd) — releases this agent's claims, drops it from the roster.
Full config, recovery, and caveats (notably: avoid iCloud/Dropbox-synced folders): .fleet/README.md.
install.sh # one-shot installer (copies .fleet/ + wires hooks into a target repo)
.fleet/
bin/fleet.sh # the CLI (roster · claim · release · msg · inbox · board · worktree · doctor)
bin/{register,guard,awareness,deregister}.sh # the four hook handlers
bin/lib.sh # shared helpers (atomic claim, heartbeat, JSONL)
config.json # stale_after_s · claim_mode · block_mode · state_location
state/ # runtime coordination state (gitignored — per machine)
.claude/settings.json # registers the four hooks
CLAUDE.md # the Fleet protocol stanza injected into each agent
No secrets, no network, no ports — Fleet is plain local files + shell hooks. Runtime
state (.fleet/state/) is gitignored (per-machine). Report issues: SECURITY.md.
MIT © 2026 convenientlymike
