Three agents fixed the same bug
What happens when several agent sessions work one codebase and none can see the others' uncommitted work – and the process I wrote so it never happens again.
I run several agent sessions against the Atlas repo at once, in separate worktrees. On 2026-08-10 there were five to eight of them working simultaneously, and that day one defect – chat titles falling back to the directory name – was implemented three times. Ingest-side description seeding, a channel-side extractPromptTitle, and the fix that landed, naming a chat from its first prompt the way the TUI does. Two implementations were dead on arrival, and nobody knew that until all three were read side by side.
This is the story I keep having to tell plainly, because the temptation is to soften it: none of the three fixes was wrong. Each one worked. The failure was that none of the sessions could see the others’ work – two had committed nothing, and a branch name is an area, not a claim. It was a process failure, not a technical one, and it was mine.
The chat-title day was the worst of it, not the first of it. The same stretch produced the relay-eviction fix written twice, fifteen hours apart, with algebraically identical predicates (highest >= droppedBefore - 1 versus droppedBefore <= highest + 1). master sat 45 commits behind because unrelated work had been committed onto a feature branch. A wedged session was killed with kill -TERM while holding 102 uncommitted lines, and survived only because someone happened to look. A merge was deployed to Cloudflare at 17:02Z and was still absent from GitHub eight minutes later. And one root cause was derived from zero twice, twenty minutes apart, because the first session’s reasoning lived only in a session log nothing else reads.
The galling part: the search discipline already existed. The docs already said grep the tree, then git log --all --grep=, before implementing anything. It found none of the three chat-title fixes, because git indexes only work that exists.
The conventions
I wrote the conventions down in two passes: a doc on 04 August, after one session spent most of its length untangling four days of parallel work; and the tooling on 10 August, the day of the chat-title incident. The load-bearing rule is that a claim is made before any code, and it claims the problem, in a sentence:
bun run feature start web-chat-title "chat titles fall back to the directory name" That writes an empty commit to refs/claims/web-chat-title, and the comment in the script says better than I can why it is shaped that way:
/**
* A claim is an empty commit on `refs/claims/<name>`, outside `refs/heads`.
*
* "Search before implementing – grep, then `git log --all --grep=`" predates
* this and did not stop three chats writing the same chat-title fix on
* 2026-08-10, because none of the three was findable. Two had committed
* nothing, and git indexes only work that exists; the third's branch was named
* `chat-name-seed`, which is an area, not the defect. So the claim is written
* at `start`, before a line of code, and it names the problem in prose.
*
* `git log --all` walks every ref under `refs/`, so the search the docs already
* prescribe finds a claim with no code behind it. Living outside `refs/heads`
* it never merges into master and never pollutes history, and being a ref it is
* never garbage collected.
*
* Liveness is the branch, not the claim. `finish` deletes the branch, so a
* landed claim stops reading as live while its findings stay greppable.
*/Around it, rules paid for by specific defects. Uncommitted work does not exist – nobody can see it, nothing will merge it, and another session may snapshot it into a commit of its own, which is exactly how one feature became three. Findings get recorded in git as they are worked out, because a session killed mid-investigation otherwise leaves its reasoning where no search reaches. Merge conflicts are decided by provenance, not date – a commit that snapshotted someone’s uncommitted work is newer by date and older by content. And finishing is a checklist that ends in proof: containment via git cherry, a push, and reading the remote back.
What the rules are not
None of this is a lock, a queue or turn-taking. The same stretch that produced the chat-title incident also produced two halves of one bug – a web fix and a channel fix, written by two sessions, disjoint files, both merged, no conflict and no duplication. Parallelism works when the slices are genuinely disjoint and each session checked what the others owned first. Every rule is about being visible to the session beside you, and none of them should make two disjoint slices any slower.
Even the tooling stops short of judgement. feature status prints every worktree’s facts – commits, dirty files, age, what it claims – and no verdict, because a worktree nobody cleaned up is indistinguishable from one somebody is using. The destructive half of that call stays mine.
Every rule in the doc is a defect that has already been paid for. The conventions did not prevent the chat-title day. They exist because of it.
Projects
what this writeup is about