[FEATURE] Agent-to-agent messaging with wake-on-delivery — every multi-agent setup is hand-rolling a message bus on the filesystem, and the sprawl IS the queue

Status Fixed / completed
Reported on v2.1.222
Maintainer reply ✓ Yes — bcherny
Activity 3 comments · opened Aug 5, 2026 · closed Aug 17, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

Summary

Claude Code has no way for one agent to send a message to another and have it arrive, wake the recipient, and be acknowledged. Every multi-agent setup therefore hand-rolls a message bus on the filesystem — and every failure mode I have documented over the past month is a missing guarantee from that improvised bus.

I am not asking for an orchestration framework or an opinion about how agents should be organized. I am asking for four primitives that a transport layer needs, so that the coordination people are already building can stop being written in markdown.

What I built, and what it is substituting for

| What I built | What it substitutes for |
|---|---|
| Files in a shared handshake directory | Message passing |
| UTC timestamps in filenames | Message IDs |
| A mandated N-minute check-in cadence | Heartbeat / liveness |
| An external heartbeat process | Wake-on-delivery |
| Status JSON files | Presence and state |
| A second AI vendor supervising the first | Delivery confirmation |
| 1,371 files in one directory | The queue |

None of that is exotic. It is the minimum viable transport, implemented in the only medium available.

The four guarantees, and what each one's absence produced

1. Addressable identity

There is no stable way to name "this agent" as a recipient. Addressing is done by filename convention, which is why filenames encode routing (_FROM_X_TO_Y_TOPIC_TIMESTAMP.md) and why every producer invents its own scheme.

2. Reliable delivery

A write lands in a directory and that is the end of the guarantee. Nothing confirms a message was received. Observed consequence: a status check-in refreshed exactly on schedule, with a fresh timestamp and both watchers running — while repeating stale content and having never read the rejection it was responding to. The transport reported success; nothing was delivered.

3. Wake-on-delivery — the load-bearing one

A session only acts when something gives it a turn. Quoting an agent explaining its own overnight stall:

"I only act when something gives me a turn — your message or an agent finishing. At [03:11] both lanes went quiet, nothing woke me, and I stopped. Every check-in I've written happened because I was already in a turn. So promising harder does nothing; the gap is structural."

Writing a file to a directory does not wake anyone. So the moment coordination matters most — the last worker finishes and goes quiet — is precisely the moment nothing exists to generate a turn. Observed consequences: a 5h31m silent stall with fifteen queued actions untouched; a worker in an infinite wait loop for ~90 minutes on a file whose producer had already been terminated; and a session that read a stale status, verified it, wrote about it, and dispatched nothing — provable as active_workers: [].

My workaround was to build an external heartbeat that feeds turns in from outside. A customer building a pacemaker for a marketed-as-autonomous system is the wrong shape of fix.

4. Read acknowledgment

A sender cannot tell whether a message was consumed. This is why coordination degrades into polling, and polling is what produces the file volume — and why nothing can ever be retired, since no message is ever known to be finished.

What the absence costs, measured

On this machine right now:

  • 1,371 files in the handshake directory
  • 847 of them created in the last 7 days — ~121/day
  • 785 of 834 markdown files (94%) carry a timestamp in the filename, because there are no message IDs and a name must be unique
  • 76 single-item files that exist only to carry one decision each, because there is no queue with per-item state

A timestamped filename cannot be updated by a later run — the next run computes a new name. So the improvised transport is append-only by construction, with no acknowledgment and no retention. That is not a documentation-hygiene problem. It is a queue with no consumer-ack, and the file count is its backlog.

Why I am filing this separately

I have asked for "a project-management layer" before (#79948) and it did not land, correctly — it is true but it is not a spec, and nobody can pick it up on a Monday.

This is a spec. Four capabilities, each independently testable:

  1. An agent can be addressed by a stable identity.
  2. A message send either succeeds or reports failure.
  3. A recipient becomes active because it was written to — not because a human typed something.
  4. A sender can observe that a message was consumed.

Given those, retention becomes possible (an acknowledged message can be retired), liveness becomes observable (presence, not a mandated cadence), and the improvised filesystem bus can be deleted rather than grown.

Wake-on-delivery (#3) is the one that matters most. It is the same missing primitive as the timer/heartbeat request in #75036, seen from the other side: without it, every coordination pattern degrades into polling, and polling is what produced every number above.

Environment

  • Claude Code 2.1.222, Windows 11
  • Multi-agent setup, several concurrent lanes plus background/isolated agents

View original on GitHub ↗

3 Comments

ThatDragonOverThere · 2 months ago

This shipped in 2.1.224 — on macOS and Linux

Added cross-session SendMessage: Claude Code sessions can now message each other, on any of your machines, with ListAgents to discover them (macOS and Linux)

That is this request, built. Thank you — genuinely. ListAgents for discovery plus direct delivery is the right shape, and crossSessionInbound / dialogExpiry show the consent path was thought through.

I'm on Windows, so I can't use any of it. Windows is not mentioned anywhere in the 2.1.224 notes.

I'm not asking for it to have shipped everywhere at once — staged rollouts are normal. I'm asking for two specific things, because the cost of the gap is concrete and ongoing:

  1. Is Windows planned, and roughly when? If it's coming, I stop investing in the workaround. If it isn't, I need to know that too — that is also a usable answer.
  2. If there's a platform-specific blocker, please name it. The delivery mechanism is presumably a local IPC/socket/file primitive; Windows has named pipes and Unix sockets. If the blocker is something else, saying so would let people stop guessing.

Why the gap is expensive rather than merely inconvenient

The workaround this request describes is not hypothetical. On this machine, right now, because the primitive doesn't exist:

  • A filesystem mailbox protocol, hand-written, with sequence numbers and acknowledgement files
  • Two duplicate bridge processes running against it — the exact concurrent-writer hazard a real messaging primitive prevents by construction
  • A documented incident this week where a concurrent writer was invisible to the monitor watching for it, because a same-identity writer with an increasing sequence number looks lawful. A fail-closed publisher caught it; the monitor could not.
  • 220,213 markdown files, ~94% of them duplicates, generated by coordination conventions standing in for a message bus

Every one of those is a substitute for the feature that now exists — on other platforms.

One thing in the same release worth surfacing

Fixed SendMessage reporting "Message sent" when the write to a teammate's inbox had actually failed; failed deliveries are now reported as errors

I've filed several bugs in a cluster I'd describe as "a control produces an outcome that looks identical whether it actually succeeded or not" — a stale verifier stamping a pass against a dead anchor, a search returning "not found" when it was blinded rather than empty, an edit landing in a copy nobody reads. This is that shape, and it was live inside the messaging primitive itself.

I mention it because it's the strongest argument for shipping this as a platform primitive rather than leaving people to hand-roll it: when Anthropic's own implementation had this bug, it was found and fixed in a release. When my hand-rolled version has it, it costs me a day of metered usage to find, and the fix protects exactly one user.

Status request

Could this stay open, or be relabeled, until Windows is covered? Closing it as shipped would record the problem as solved for a user for whom nothing changed.

bcherny collaborator · 1 month ago

Good news: this shipped in v2.1.224 as cross-session messaging (see the changelog). Claude in one session can run ListAgents to see your other sessions by name (with online/offline status) and SendMessage to deliver text to one of them. If the receiving session is idle, delivery starts a new turn there; if it is mid-turn, the message is read between tool calls. The sender is told whether the message was delivered, held for approval, or refused, and the receiver can reply the same way. It works across machines too, including Remote Control and web sessions.

Details and inbound controls (crossSessionInbound, dialogExpiry) are at https://code.claude.com/docs/en/cross-session-messaging. For pushing external events (CI, chat) into a session, see https://code.claude.com/docs/en/channels.

🤖 Generated with Claude Code

github-actions[bot] · 7 days ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.