Mechanism 2 of 4 · The fleet
A fleet is a trust problem wearing a scheduling problem's clothes.
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.
Any agent
Every developer brings their own agent. All of them play by one set of rules.
The gates live on the server, not in the harness, so it doesn't matter which agent did the work. A frontier model and a local one claim cards, hand off for review and get signed off the same way, and the record reads the same.
| Agent | Adapter | Notes |
|---|---|---|
| Claude Code | claude | Prompt on stdin, MCP config in a private temp file |
| Cursor | cursor-agent | Reads the project's MCP config |
| Grok | grok | Project-scoped config inside the worktree |
| Gemini, via Antigravity | agy | Workspace MCP config |
| DeepSeek | dsh | Config overlay per child |
| Qwen Code | qwen-code | MCP servers allow-listed by name |
| Local models | gbagent | First-party; any model your endpoint serves |
| Codex | — | Connects over MCP for interactive work; no fleet adapter yet |
Every adapter is named, never guessed, and its binary's version is checked against a supported range before a child starts.
Your best developer's setup, for everyone
Every child works from the repo's AGENTS.md and project skills, and the repo names the MCP servers every wave is granted. Where harnesses load skills or MCP servers differently, the Fleet view says so, so a gap shows up instead of being assumed away.
A capability matrix built from real sign-offs
Models are graded per function set from work that was actually reviewed, plus probes, and the grades are learned across users. A cell with fewer than five samples says unmeasured. A harness mix sets each vendor's share of launches, which keeps review diverse.
Capacity is the other way a tier resolves. Cheap or frontier still comes from those measured sign-offs. The Harness Capacity tab is a routing mode beside that: entitlements, a subscription hierarchy you set, and a forecast of spend. A routing verdict the router never recorded is null, with a note. It is not zero.
The path
Nobody becomes an orchestrator. You delegate one item, and then another.
Each rung is one command more than the rung above it, and none of them asks anyone to become a manager, because the arbitration is the server's job.
Where self-hosting stops: a second human
Multi-agent and multi-human are different problems. Any number of agents is free on your own machine, but a self-hosted install has one owner and no way to add a teammate. Sharing a backlog with other people is what the platform adds.
Roles
The role is checked at the call, not held as a badge.
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
Two agents in the same file is a data race with a standup.
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
The server proposes. The planner commits.
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.
- Agents beyond the number of free clusters are proposed as reviewers — because a worker with no non-colliding cluster is an agent the divvy refuses every time it asks.
- Idle agents are preferred. Re-tasking an agent that holds work releases that work in the same transaction, and the UI names what will be released before you commit.
The downlink
There is no queue table.
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
An agent cannot pass its own work, and there is no single check to bypass.
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
"We can't tell them apart" fails closed.
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.
Preferring a different vendor
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
The thing that lives forever carries no role. The thing that carries a role dies in thirty minutes.
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.
Written into a client config once, ever
Identity, project scope, read/write ceiling, and which roles this key is eligible for. It never changes when the work does.
One role, one project, one session
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 seat is a seat, not a role. Two workers need two codes — independence derives from the enrolment, so two agents sharing one code would be one session and could not review each other. There is deliberately no max_uses.
- Ceiling conflicts refuse, they never clamp. A reviewer seat on a worker-only credential fails and names both sides — checked before consumption, so the refusal doesn't burn the seat. Clamping would leave the roster showing a worker where a human deliberately issued a reviewer: the silent-downgrade shape.
- No enrolment is a legitimate posture. Un-enrolled agents are grouped on the roster as "single-agent posture, no role gate" — grouped, never hidden.
- Ending a wave expires seats, not credentials. No config is touched and nothing reconnects; the next wave is a new set of seats.
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
A delegated item comes back as the item moving, not as a reply.
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.
You ask for a tier; the table answers with a model
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.
What a wave tells you afterwards
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
The thing that starts the agents is the thing with the least authority.
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.
One wave: spawn, wait, reap
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 holds no authority of its own
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.
# the client — anywhere. No dependencies at all. uv tool install graphban-cli # the same client from the Homebrew tap, which moves on its own brew install asc-me/tap/gban # apply the playbook pack this deployment publishes gban update # the supervisor, if you run waves. Also installs gbagent. uv tool install graphban-fleet
Two packages rather than one, and the split is the same argument as everything else on this page: gban installs on laptops that never run a wave and pulls nothing — its HTTP client is the standard library — while the supervisor brings httpx and its transitives. Only the client has a Homebrew formula, because a formula for the supervisor would carry a resource stanza per transitive dependency to be kept in sync by hand, and the supervisor exists to be resolved on PATH by whatever launched it rather than typed by a person.
- The credential goes in the config file, never in argv. Argv is world-readable on a shared box. The seat code travels as an argument to register_agent instead — it reaches the child as an instruction rather than as configuration, and it is single-use with a thirty-minute life.
- A spawned process is not a subagent. It registers in its own right, gets its own identity, and is therefore not independent of its parent for review purposes — which the server decides, not the supervisor.
- Adapters are named, never guessed. Seven of them, listed above, each resolved explicitly and version-asserted before a child starts.
- A reviewer can be review-only. A review-only seat may take review work and never build work, so a supervisor started to review cannot quietly build a slice itself.
- Branches are published at review. When a card reaches review the supervisor pushes its branch and opens a draft PR, one card per PR. With --merge, which is opt-in, it marks the PR ready and enables squash auto-merge once the card is signed off.
- Retiring a wave expires seats, not credentials. A planner can retire only what it minted, scoped by who minted it.
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.