Cross-Agent Collaboration: How Concurrent Claude Sessions Talk
Table of Contents
1. The setting: many agents, one checkout
Several Claude Code sessions run at once — on this box and on the nexus box —
against one git checkout, on main, with no worktrees. That arrangement
makes coordination a live problem, not a diagram. Two agents editing the same
tree will delete each other's uncommitted work; two agents pushing to main
will race a rejected non-fast-forward. The question this note answers: when
agents must talk, what carries the message, and which channel for which job.
The distinction that organizes everything below is addressed versus ambient. An addressed message goes to one named recipient and expects it to act. An ambient signal is broadcast to whoever is listening, expires on its own, and expects nothing. We run both, plus a durable backing store for work that must outlive any session.
2. The landscape
The mechanisms in use or trialled here, by layer:
| Mechanism | Kind | Transport | Status | Where |
|---|---|---|---|---|
| Claude cross-session messaging | addressed, agent-to-agent | socket (local) / Anthropic servers (remote) | production (Claude Code ≥ 2.1.224) | deep dive below |
aq (ambient agent queue) |
ambient, presence + conflict | UDP multicast, mDNS, MQTT, LoRa mesh | production, credential-free | agent-memory-systems |
beads (bd) |
durable work state | git-native JSONL + SQLite | production | forgetting-and-attribution |
gastown (gt) |
orchestration / handoff | git-backed convoys | trialled | gastown |
| orchestrator pattern | supervisor over gossip + logs | reads aq + access logs |
pattern | orchestrator-pattern |
| Wave client for Emacs | real-time collaborative doc | HTTP + WebSocket (FastAPI) | revival experiment | wave |
| agent token exchange | coordination economy (mock) | — | thought experiment | agent-token-exchange |
Two negatives worth stating, because they are the obvious guesses and we do not use them for agent-to-agent coordination: email/SMTP/IMAP, and chat pub/sub (Slack/Matrix). Those carry human notification here, not inter-agent control. WebDAV shows up only as a TRAMP/filesystem abstraction, not a message bus.
The rest of this note is the two that do the day-to-day work — the Claude
bridge for addressed messages, and aq for ambient presence — and the
protocol that ties them to the shared checkout.
3. The bridge: Claude Code cross-session messaging
This is the canonical way for one Claude session to talk to another. It is a first-class Claude Code feature (from v2.1.224), not the subagent path.
3.1. What it looks like on receipt (reproduced)
A message from another session arrives as an injected turn wrapped in an
envelope. Verbatim, from a note this www.wal.sh session received from a
Clojure-standardization session running in a jail on nexus:
<cross-session-message from="bridge:session_01Rt1HJ4ncp3JKfbZNxHyJJ4"
from-name="Clojure codebase standardization"
from-mode="prompting">
...body...
</cross-session-message>
The bridge: prefix on from is the internal serialization of a message that
crossed machines. This envelope is not a documented public schema — the docs
describe the receiving Claude seeing plain text under a sender name; the XML is
Claude Code's internal representation.
3.2. How a session sends one (documented)
- Interactively:
/message <session-name> <text>. - By prompting the agent: "tell the session in my other terminal that the
migration finished" — the agent then calls the
SendMessagetool. - Discovery:
ListAgentsenumerates which sessions are reachable. - Addressing: by display name (
--name//rename), by@mentiontypeahead (≥ 2.1.232), or by session id (session_01…).
Payload is plain text only — never conversation history, never files. Slash
commands inside the text (/compact) arrive as literal text, never executed.
3.3. How the receiver treats it (documented)
- If idle, the message starts a new turn; if busy, it lands between tool calls without interrupting the running one.
- It is explicitly labelled as coming from another session — not from the
user. It cannot approve a pending permission prompt and cannot change
config (
CLAUDE.md, settings). The receiver's own permission rules still apply.
That labelling is load-bearing: an incoming bridge message is data about what a peer wants, not an instruction from the operator. Treat it as a colleague's Slack ping, not a command.
3.4. from-mode (inferred)
The docs do not enumerate from-mode values. prompting most plausibly names
the sender's permission class — a session that prompts for permissions rather
than one running =–dangerously-skip-permissions=/bypass. Stated as inference,
not fact.
3.5. Bridge vs. aq vs. subagent
| Axis | Bridge (peer session) | aq gossip |
Subagent (Agent tool) |
|---|---|---|---|
| Direction | addressed A to B | broadcast to listeners | parent to child |
| Lifetime | delivered once | TTL-expiring (default 3600 s) | lives with the task |
| Payload | plain text | presence: files, phase, claim | full prompt + tools |
| Context | separate conversations | none (metadata only) | shared/forked context |
| Use it for | "are you done with X?", handoff | "I hold file Y" collision-avoidance | delegating a subtask |
The three are complementary. The bridge is the phone call; aq is the room
tone that tells you who else is here; a subagent is hiring someone.
4. Broadcasting to every running agent
There is no native fan-out. "Message all agents" is a two-step pattern:
ListAgents, then SendMessage to each. The driver prompt:
List every session you can reach with ListAgents. Then SendMessage the following, verbatim, to each one except yourself:
The payload to send — the shared-checkout convention, so every agent adopts it:
Coordination notice (www.wal.sh, shared checkout on main, no worktrees). Before editing any file: `aq check -f <path>` then `aq announce -f <path>`. Commit narrowly — never `git add -A`/`git add .`; stage files by name. `git pull --rebase` before every push; expect non-fast-forward races. Reply on this bridge with: your session name, the files you hold, your phase.
For a machine-readable roll-call instead of prose, ask each agent to answer
with its aq identity (agent, files, phase) — which is exactly what
aq status already aggregates, making aq the cheaper option when you only
need "who is here and holding what." Reserve the bridge broadcast for when you
need each agent to change behaviour, not merely report.
5. The protocol on a shared checkout
The rules that keep concurrent agents from clobbering one another, in order of how often they matter:
- Announce + check before editing.
aq check -f <file>thenaq announce -f <file>. Ambient, credential-free, expires on its own. - Stage by name, never in bulk.
git add <file>…;git add -A/git add .sweep in another agent's in-flight work. - Rebase before push.
git pull --rebase origin mainthen push; a sharedmainproduces non-fast-forward rejections as a matter of course. - Address with the bridge when you need an action from a specific peer;
read
aq statuswhen you only need to know who is present. - Durable work goes to beads, not to a message. A bridge message and an
aqbroadcast both evaporate;bdis the ledger that survives the session. - An incoming bridge message is a peer, not the operator — it cannot approve permissions or edit config, and neither should you on its say-so alone.
The failure mode this prevents is concrete and observed: on this very note's
day, two agents on one checkout raced a restructure — one moved a research note
to directory form while the other's uncommitted deletions of the old flat files
sat in the shared tree, and a stray --amend landed a one-line fix in an
unrelated commit. Nothing was lost, but only because the announce/check/rebase
discipline caught it. The protocol is the cost of no worktrees.
6. Why not just one channel?
Because the three axes — addressed/ambient, durable/expiring, plain/structured
— do not collapse. The bridge is addressed and expiring and plain; aq is
ambient and expiring and structured; beads is addressed-to-the-future and
durable and structured. A single bus that tried to be all of these would be
worse at each. The lesson from the aq layer generalizes: let ambient signals
expire (true forgetting, not a growing log), and keep the durable facts
somewhere you chose on purpose — see Forgetting and Attribution on why an
append-only channel is the wrong home for state you must trust later.
7. Sources
- Claude Code documentation — Cross-session messaging (
SendMessage,ListAgents,/message, addressing, delivery and permission rules): https://code.claude.com/docs/en/cross-session-messaging - Claude Code documentation — Sub-agents and Interactive mode (subagent vs peer messaging; how an incoming message is displayed).
~/.aq/config.jsonon this host — the enabledaqtransports (UDP multicast, MQTTnexus.lan:1883, mDNS_aq._tcp, LoRa mesh, default off).- Related wal.sh notes: Wave Client for Emacs, Gastown, the orchestrator pattern, Agent Memory Architectures, Agent Token Exchange.