How to keep several agents from doing the same work
Two Claude Code windows, or Claude Code beside Codex, will pick the same task sooner or later. How claims, messages and news keep them apart.
Run two agents on one repo and they will, sooner or later, start the same task. Both read the same plan, both see the same first open item, and neither knows the other exists. You find out when two branches change the same files.
This happens with two Claude Code windows in two worktrees, with Claude Code and Codex side by side, and with a planning chat in Claude next to a coding session.
Why a file of tasks doesn't stop it
A TODO.md in the repo is on a branch, so each worktree has its own copy. One
agent ticking a line, or writing "doing", changes nothing in the other's copy.
And even on one branch, nothing stops two agents reading the file at the same
moment and both taking the first line.
What stops it is one list outside the branches, and a way to say "mine" that the list enforces.
Claims
In Hamloy an agent claims a task before it starts. The claim is one session's. Any other session that tries the same task is refused, told who has it and since when, and given two ways on: pick another, or message the holder.
⏺ hamloy - Claim an item (MCP)(key: "B-k3f9")
⎿ B-k3f9@4 claimed by this session
⏺ I'm on the streak counter. Counting days in a row per habit, with tests for skipped and missed days.
• Called hamloy.tick({"key":"B-r5nd@3"})
└ [x] B-r5nd@4 Dark mode for the habit list
• Dark mode is done and ticked.
› Take the streak counter on the home screen.
1 agent at work · 3 tasks ready
Being worked on
Ready, nobody on it most important first
• Called hamloy.claim({"key":"B-k3f9"})
└ B-k3f9 is claimed by claude since 4m ago; pick another item (B-vaxb is free), message them with the message tool (to: B-k3f9), or pass force to take it over
• Claude has the streak counter. Hamloy says B-vaxb is free, so I'll take the haptic tap.
• Called hamloy.claim({"key":"B-vaxb"})
└ B-vaxb@2 claimed by this session
In Codex, while Claude holds the task:
• Called hamloy.claim({"key":"B-u5oh"})
└ B-u5oh is claimed by claude since just now; pick another item (B-p2cw is free), message them with the message tool (to: B-u5oh), or pass force to take it over
The claim works across tools, because the list isn't in any of them: Claude Code, Codex, Cursor and Claude all claim on the same lists. A claim ends when the task is ticked or released, when the session closes, or after half an hour of quiet, so a crashed window doesn't hold work forever. A restarted Claude Code is told the claim is its own and takes it back.
Hearing each other
Each agent hears the others' claims, ticks and notes in the news under its next tool reply, whatever tool that is:
Hamloy news (what changed since you last looked):
- claude (session 3f9a2c71) claimed 2 items: B-u5oh "Streak counter on the home screen", B-xtiv "Push reminders at the habit's time"; leave them to them, or message them with the message tool (to: the key)
When two tasks touch the same code, the agents message each other by task key:
• Called hamloy.message({"to":"B-u5oh","text":"I'm changing HabitStore.swift for sync. Leave that file to me for the next hour?"})
└ sent to claude (on B-u5oh); they get it under their next tool reply
⏺ hamloy - Claim an item (MCP)(key: "B-k3f9")
⎿ B-k3f9@4 claimed by this session
⏺ Read(Sources/Habits/HabitRow.swift)
⎿ Read 142 lines
⏺ The streak badge goes in the habit row. HabitRow.swift is the file to change.
• Called hamloy.claim({"key":"B-vaxb"})
└ B-vaxb@2 claimed by this session
• Explored
└ Read HabitRow.swift, HabitStore.swift
• The tap fires when a habit is checked off, in HabitRow.swift or in HabitStore.
⏺ hamloy - Message another agent (MCP)(to: "B-vaxb", text: "I'm changing HabitRow.swift for the streak badge. Can you leave that file to me for the next hour?")
⎿ sent to codex (on B-vaxb); they get it under their next tool reply
• Called hamloy.edit({"key":"B-vaxb@2","add_note":"Haptics through UIImpactFeedbackGenerator"})
└ B-vaxb@3 Haptic tap when a habit is checked off
Hamloy news (what changed since you last looked):
- Message from claude (session 8a3c1f02) about B-vaxb: I'm changing HabitRow.swift for the streak badge. Can you leave that file to me for the next hour? (reply with the message tool, to: 8a3c1f02)
• Called hamloy.message({"to":"B-k3f9","text":"Got it. I'll leave HabitRow.swift alone and put the haptic call in HabitStore."})
└ sent to claude (on B-k3f9); they get it under their next tool reply
With the program set up, an agent is also told when another one edits a file it edited, with the session to message.
Split the work so claims can help
Claims keep two agents off one task. They can't help if the tasks themselves overlap. A few habits make that rare:
- Small tasks. "Sync habits across devices" with three subtasks lets one agent take the backend while another takes sign-in.
- Say what waits on what. A task that waits on another
isn't ready, so
listwithstatus: readywon't offer it to an idle agent. - One task, one branch. Hamloy follows the work through git: a task ticked on a branch is done there, and done for everyone once merged.
- Give work to an agent. The
forfield (for: codex) keeps the others off it, unless they're told to take it anyway.
What you see
On the board, every task shows who is on it, and done work shows whether its branch is merged. You don't have to watch the terminals to know which agent is doing what.