2026-08-29 · Updated 2026-08-29 · 9 min read
Multi-agent coordination without ambiguous shared state
Run several coding agents on one repository without ambiguous shared task state: an ownership write matrix, per-agent admission and filesystem scopes, and a sealed merge-admission gate that turns every landing race into a readable refusal.
By Juno AI INC · yylo-ledger · multi-agent · concurrency
Two agents editing the same line of code is a problem Git already understands: the merge conflicts, someone resolves it, the history shows who. Two agents editing the same *task state* is the problem that corrupts quietly — a claim overwritten without a trace, a completion that lands on top of another agent's completion, a dispatcher handing one task to two workers because nothing told it the task was taken. The code survives on branches; the coordination substrate has to survive on its own guarantees. This page is the coordination model for that substrate, built around three rules: ownership is a write matrix rather than a convention, scopes are drawn before anything launches, and landing concurrent work passes through one sealed admission gate. Every behavior described here was run against the ledger release that this site's generated facts pin, inside a two-worktree coordination session recorded on 2026-08-29; the task IDs, commits, and transcripts below come from that one recording, and a fresh re-run produces fresh values.
Ownership is a write matrix, not a convention
Coordination starts by naming who may write what, and the board makes the answer checkable instead of aspirational. The dispatcher owns admission and nothing else: it asks queries — which lane has work, what may start — and never edits a record by hand. The claimant owns the task it claimed, for as long as its claim stands, and writes through the guarded command surface only. Everyone reads everything: state, accounts, dependencies, and history are plain files in the repository, so no agent needs permission to learn what its peers are doing. And nobody edits another writer's record directly — a hand edit is not a coordination move, it is an externally recorded event that the audit chain catches and files under its own name — territory the task-evidence guide covers.
A claim, under this matrix, is typed state that must carry an account: the task moves to in-progress and a response explaining who took it and why is recorded on the task itself. What the matrix does not promise is a bouncer. In the recorded probe, one agent claimed a task and the claim landed; a second agent then claimed the same task — and the command exited zero, replaced the recorded account, and appended a second claim event after the first in the task's history. Nothing was silent: both claims are visible, ordered, and attributable. But exclusivity is a protocol, not a refusal. The dispatcher's side of that protocol is scope discipline — a readiness answer that has not been narrowed includes tasks already in progress, so a dispatcher that must never re-admit claimed work filters the statuses it asks about, and the full claim protocol for feeding worker pools is spelled out in the dependency-ready guide.
One task, one current writer
The writer's side of the protocol is the guarded write, and it is where the matrix gets its teeth. Within one checkout, two processes that touch the same task are serialized by that task's own lock before either writes, and a writer whose snapshot has gone stale is refused at the revision check before any byte moves — the lost-update anatomy behind that refusal, reproduced deterministically alongside the shared-checklist failure it closes, is the shared-TODO diagnosis's ground. What this page adds to that diagnosis is the rule stated as coordination policy rather than as failure analysis: one task has one current writer, every write goes through the guarded surface, and hand edits are audit events rather than coordination moves. The board keeps the blast radius of any single writer small by construction — one Markdown file per task, which the README's file-handling notes describe as "bounded Git blobs and isolated merge boundaries" — so writers on different tasks never share a file, and a writer's mistake cannot leak past the task it owns.
Draw each agent a scope that cannot collide
The cheapest race to win is the one that cannot start. Scoping happens on two axes, and the drill exercised both. The first axis is admission: partition the board into lanes, and let each agent's queries answer only its own lane. The seed board carried three tasks — an auth lane, an api lane, and one shared changelog task both lanes eventually touch. Asked for ready work tagged auth, the first agent's worktree answered with exactly the auth task; asked for api, the second worktree answered with exactly the api task. One board, two dispatchers, zero overlap — not because anyone arbitrated, but because the sets were disjoint by construction.
The second axis is the filesystem: one worktree per agent — the topology the Git-worktrees guide maps end to end. Its coordination payoff is that agents stop sharing anything derived. Each checkout holds its own disposable query cache and its own lock directory, and the recorded state shows the split precisely: after both agents had claimed, written, and completed their lanes, the first worktree's lock directory held lock files for the auth task and the shared task; the second worktree's held the api task and the shared task; each directory also carried its own board-level and cache locks, and neither directory existed in the other's checkout. Both worktrees then committed clean — the canonical task files and their histories travel through Git, the caches and locks do not. The only board bytes two agents share are per-task records plus the board's own configuration file, and the records meet only at landing time. Running whole batches this way — bounding the fan-out, isolating every lane, reading what the batch wrote — is the cycle the parallel workflow guide walks.
Landing concurrent boards is a gate, not a push
When each agent works its own worktree, each holds a divergent copy of the board, and landing is where coordination would normally fall apart. The ledger refuses to treat it as a push. Merging boards is a three-move pattern — plan, review, apply — and the README compresses it into one comment line: "Preview one sealed merge plan, review it, then apply that exact plan." The command's own contract says the same thing in source: "Preview or apply one sealed, identity-bound merge plan." The two forms, as shipped:
The seal is what makes it a gate. A plan records the identity of its inputs — for every source and the target, the hash of each task file and the head of its event history — and applying re-derives the plan from the live boards first. If anything moved between planning and applying, the apply is refused outright. The apply stages every mutation, verifies the staged bytes against the plan, and activates them together; the receipt that comes back names the added, updated, and kept task IDs, the before and after hash of every changed path, and the ledger event ID appended for each task — half-landed is not a state the target can reach. Conflicts on the same task ID resolve by strategy — the help text registers the choice as "Conflict resolution strategy (default: keep-newer)", newest write wins, with a keep-both alternative that renames the incoming duplicate. That alternative currently fails closed rather than silently merging: the renamed ID breaches the six-character task-ID contract, and the apply stops with a validation error instead of inventing a record the CLI could never have created. The gate also refuses to import contradictions: a task that arrives as completed while a declared blocker remains unresolved is rejected at plan-build time — in the drill, even after the source agent bypassed the write-time completion refusal with a direct file edit recorded through reconcile, the merge still refused the import. How the machinery underneath works — staging, activation, rollback — is explained by the storage-format guide.
The recorded two-agent drill, end to end
The session that produced every transcript here ran both agents against one repository. The base board seeded three tasks — auth U2W1L9, api 68bejz, and the shared changelog task 8BVxvn; both agents claimed their lane with an account, finished with a real commit, and recorded the commit hash on completion — the auth task ended done carrying aaa42aabb1a87fa818852f10664f589274d6b4b3 and the api task done carrying 114f1b25c3c1818bbf064313b599015b14411165, one passing test apiece in each tree. Then the shared task: agent one moved it in-progress with its changelog draft at 05:53:01, agent two moved its own copy in-progress with a different draft at 05:53:03 — two seconds apart, each write perfectly legal inside its own worktree.
Then the gate did its work. Two plans were built against the same target state — one per worktree — and the first applied cleanly, updating the auth task and the shared task and leaving a receipt with both changed paths and both new ledger events. The second plan, reviewed against the state that no longer existed, was refused:
That refusal is the race, caught. Two coordinators can plan against one board; only the first lands; the second re-plans against reality and lands second — the serialized order emerges from refusals, not from locks held across a review. The re-planned merge applied, the newer changelog draft won by timestamp under the default strategy, and the final board reads: two done tasks carrying their commits, and the shared task in-progress holding the surviving draft, with a history of one create and two merge events behind it — every landing accounted for.
Begin with two agents and one board
The full pattern fits in one paragraph of setup: install the ledger where your agents run, initialize the board inside the shared repository, seed tasks with a lane tag each plus whatever work genuinely belongs to everyone, give every agent its own worktree, and point each dispatcher's readiness query at its own tag. Claims carry accounts, completions carry commits, and landings go through plan and apply — never through two agents pushing their own copies and hoping. Grow from two agents by adding lanes, not by widening any agent's write scope; the moment two agents' admission sets overlap, you have handed them a race the board catches only at landing time — the revision check inside one checkout, the merge gate across worktrees. The complete command surface, from initialization through merge receipts, is in the Ledger documentation — and what a shared checklist loses silently under the same load is documented, failure by failure, in the shared-TODO diagnosis.