I run several agent sessions in parallel, each in its own project, all on the same machine. For a while I believed that was safe because every session had its own repo. It wasn't. Sessions reach outside their home tree more often than you think, a memory file here, a shared config there, a quick fix in a sibling project because the finding happened to surface elsewhere.
The collisions, five of them when I sat down and audited the record, all had the same shape. The second session was never blind. It saw the signs of the first one working, a fresh claim in a status file, a half-written directory, and it proceeded anyway. Claims existed then; each project noted them its own way, in its own status file, and nothing read them back. I want to be clear that this is not a model being careless. Under context pressure, an agent treats another agent's presence as noise.
- Politeness is not a mechanism, so it doesn't survive.
Also worth telling, my staging rule failed the same week, broken again 67 minutes after I recorded it, by a session that had never lived the incident. That story owns its own piece, because the fix turned out to be nothing like better prose.
So the claim went from a courtesy to a protocol. Not a new idea, a standardized one: one format, one place, one mechanical refusal, instead of five projects each writing notes nobody was bound to honour. Before any session writes outside its own tree, it takes a claim, a small JSON file named after the target, holding who, when, and what for. The whole file is five fields:
{
"target": "shared-config",
"holder": "session-a41",
"purpose": "align the lint rule with the new preset",
"taken_at": "2026-08-14T21:02:11Z",
"heartbeat_at": "2026-08-14T21:09:40Z"
}
The protocol around it is four verbs. Take before the first foreign write, renew the heartbeat every writing turn, release at the end, and if another session already holds the claim and its heartbeat is fresh, the answer is REFUSED, and REFUSED is an answer, not an obstacle. The refused session queues a handover note instead, and the work happens later, in order.
foreign write wanted
│
▼
take <target> ── held, heartbeat fresh? ──yes──▶ REFUSED
│ no, or stale │
▼ ▼
claim written, read back queue a handover,
│ the work runs later
▼
beat every writing turn ──▶ release at the end
The part I did not expect to matter, and it mattered most, was verifying the guard itself. Early on, the claim script printed "claimed" even when writing the claim file had silently failed (a slash in a target name broke the path). A guard that reports success on failure is worse than no guard, because sessions proceed believing they hold a lock that doesn't exist. The fix was small, slugified names and a checked write, but the lesson was bigger. When you build a safety mechanism for agents, test the mechanism with the same suspicion you had for the agents.
A healthy machine, at rest, has an empty claims directory. Claims that outlive their session are the first thing I look at when something seems half-written. The whole mechanism is about sixty lines of shell. The value was never the code though. It's the moment this shows up on screen and the wrong move becomes loud instead of silent:
$ claim take shared-config "align the lint rule with the new preset"
REFUSED: shared-config is claimed by session-a41 (heartbeat 41s ago).
Top comments (1)
The checked write detail is the bit I would steal first. I have had agent guardrails fail in that exact boring way, where the script prints success and the next step trusts the lie. Making REFUSED a normal terminal state also matters. Otherwise every agent learns that the coordination layer is just another obstacle to route around.