Mechanism 2 of 4 · The fleet
The absence this makes loud: nobody independent looked.
Git worktrees stop several agents clobbering each other at the byte level. They cannot stop two agents solving the same problem twice, and they have nothing at all to say about whether anyone checked the result.
Running one agent? Read the review handoff and skip the rest. Everything here still applies at N=1 — the collision check just returns a cluster of one, and the independence check resolves to "a human reviewed it" and records that instead of implying more.
Roles
There are four roles — planner, worker, reviewer, and all-in-one, which is the default un-enrolled posture and is deliberately unrestricted. Roles are enforced when a tool is called, not when an agent registers.
That timing is the design. A manifest can only fail to mention a tool; the gate refuses it. So narrowing an agent's authority takes effect on its very next call, with no session to revoke and no cached permission to expire. Refusals name the next move in a hint — a role change isn't an error, so it doesn't arrive as one.
Collisions
Work is not handed out one item at a time. It's partitioned into clusters that provably share no files, so N agents can run in N worktrees without coordinating — and the files are reserved in the same write that claims the work.
Two details that matter more than they look. First, an item that declares no touchpoints is not treated as touching nothing — its areas are predicted from a semantic search over its title and description plus the touchpoints of items already linked to it, and the result is flagged as predicted so you know the confidence is lower. An undeclared touchpoint is exactly the absence that would otherwise read as "safe to run in parallel".
Second, when a prediction is wrong and two live clusters genuinely do collide, Graphban lets it ride — it records the collision with both agent ids and the overlapping area, lets the actual touchpoints replace the predictions, and computes the next partition from ground truth. Aborting would throw away real work to prevent a merge conflict git is going to surface anyway.
Allocation
propose_allocation reads the live agents and the free clusters and returns a mapping with its reasoning. It writes nothing. Committing it is a separate call, and it's the same row whether a human clicks it or an orchestrating agent makes it.
The downlink
MCP is client-to-server. The server cannot wake an idle terminal, so rather than pretend there's a third channel, a role change rides back on whatever the agent polls next.
Review handoff
Completed work goes to review, not to done. A reviewer leases an item it did not build and gets the branch back with it — a reviewer in another terminal on another machine has nothing to review unless the handoff names where the work is.
The reasoning is that reviewer and adversary are different jobs and must not become one habit — a reviewer converges, an adversary diverges. Making adversarial checking a fourth role was considered and rejected; it's a precondition the reviewer satisfies however it likes. It's gated on effort so a one-line fix isn't taxed.
bounce sends work back with a required reason that travels with the item, and pins it to its author for one lease period — they still hold the worktree and the context — after which it opens to the fleet. A permanent author-only pin was rejected because it strands the item when the author never comes back, which is the common case.
Independence
Being a different agent is not the same as being an independent one. The check is an ordered ladder, and the last rung is the one that matters.
When choosing a reviewer, Graphban prefers one whose vendor differs from the author's, falling back to merely agent-distinct. A Claude reviewer approving Claude work is a different agent but not a different error distribution. This is a preference in ranking, not a refusal — but it's what upgrades the invariant from "no self-review" to "no monoculture review", and it's the payoff for running four heterogeneous terminals instead of four identical ones.
Enrolment seats
MCP gives a client exactly one static header per server entry — and that one string was carrying four things at once: identity, authorization ceiling, role, and wave membership. Two of those are long-lived and two are ephemeral. So they were split.
Identity, project scope, read/write ceiling, and which roles this key is eligible for. It never changes when the work does.
Single-use, 30-minute TTL, consumed at registration. Pasting it into a prompt leaks a role for half an hour, not an API key — which makes the thing humans will actually do the safe thing to do.
A field note that cost a day: Cursor does not interpolate ${env:VAR} in mcp.json at all — probed at 3.16.2. It doesn't send them as literals either; it silently drops them, so the server logs no failed auth. Nothing looks wrong. That's most of why seats exist as a separate concept rather than "just use a different env var per agent".
Delegation
This is the difference between delegating and supervising, and it is the reason one person can run twelve agents: your context does not grow when the fleet does.
A parent that wants one item built somewhere cheaper does it in two calls and carries on working. delegate(id, lane, tier, seat=true) mints a worker seat bound to that item and returns its code; spawn starts a child on it. Registering on a bound seat claims the item server-side, so the child holds it from its first call and never touches the collision board — and if somebody else already holds it, the reply says who, and the child exits rather than doing the work twice.
Nothing about the outcome returns to the parent's session. It lands on the board, with its evidence, its branch and its reviewer. A fleet that reports into one context window is not a fleet; it is one context window doing twelve times the reading, and it stops working at about three.
cheap and frontier are the vocabulary. Which harness and which model that resolves to comes from a committed table of rows that have actually been verified for a given lane and role, narrowed by the project's policy, then by the key owner's preferences, then by what is installed on the machine — and every spawn records what it chose, how many rows each step dropped, and the runner-up. An empty resolution is an error naming the step that emptied it; there is no silent default.
Measured quality and latency feed back into that ranking per lane, and only once a cell has five samples. Below that it says unmeasured rather than guessing, because a ranking built on one result is a ranking that recommends whatever ran first.
The supervisor measures each child's diff against the branch it was cut from — one worker, one worktree, one branch makes that boundary exact — and reports three things nothing else in the system can see: files two workers both changed, files no touchpoint declared (the partition's input was wrong), and how many commits the trunk moved under a branch while its child worked. It reports them; it decides nothing. Rebasing somebody's work is not a supervisor's call.
The supervisor
Somebody has to open the terminals. That job kept landing on a human pasting seat codes into four windows, so it became a program — and the interesting constraint is what that program is not allowed to do.
A child per seat, each in its own git worktree, launched against a named vendor binary whose version is checked before anything starts. Then it waits, and it cleans up — including the orphans left by a previous run that died badly. until is a planner loop around that wave, not a thicker supervisor: keep going while there is work, spawn reviewers when review appears, and on restart adopt the children already running.
It can only launch a process holding a seat the server issued, to do work the server arbitrates. It cannot mint a seat, grant a role, or approve anything. Delete it and every invariant still holds; the fleet just needs a human to open terminals again.
gban fleet up … is the same command with the server and project you are already logged into filled in, and the supervisor's exit code returned unchanged — 75 is stuck, 69 an unreachable model endpoint, 55 a spent budget, and folding those into one "the wrapper failed" code would destroy a taxonomy worth having. It is a subprocess, never an import: that is what keeps gban free of the supervisor's dependencies and leaves gbfleet --help authoritative about its own commands. If the supervisor is not installed, it offers to install it and asks first.
What the walk found: sixteen steps passed, one is blocked on a decision rather than a budget, and it found nothing in the supervisor. The walk refuses to run unless pointed at a real server and refuses any project it did not create itself — a walk that quietly passed by not running would be the worst possible version of it. The one thing it stands in for is the model: the child is a real MCP client with the reasoning removed, so what is proven is the plumbing, not the judgement.