You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Claude Code v2.1.224+ shipped cross-session messaging: sessions register themselves on disk, bind a per-session Unix inbox socket, and can list/message each other via the ListAgents and SendMessage tools. Announced 2026-08-07 (@ClaudeDevs, docs).
This is a first-party, ground-truth replacement for two things squadrant currently reverse-engineers by scraping terminal pixels:
"Is this agent idle or busy?" — today: glyph matching + LivenessRegistry + pid floor.
"Did my message actually get submitted?" — today: confirmedSendToPane paste → settle → Enter → re-read screen.
The squadrant captain session is itself registered. Captains are launched by squadrant into cmux panes, exactly the same way crews are — so squadrant-spawned Claude sessions bind an inbox socket and are addressable with no changes on our side. Feasibility for the --agent claude path is already proven; nothing needs to be built to make sessions discoverable.
ListAgents from the squadrant captain right now returns live peers with liveness state:
oneplanapp-61 [10fa68] · interactive · idle · started 8m ago
observer-sessions-0d [ed69b3] · interactive · · started 7s ago
career-stack-f6 [3effa4] · interactive · idle · started 6m ago
Note peerProtocol: 1 — the registry is explicitly versioned, so it is a contract rather than an incidental artifact.
Opportunity A — status field replaces liveness screen-scraping
~/.claude/sessions/<pid>.json carries status: "idle" | "busy" plus statusUpdatedAt, self-reported by the agent. That is strictly better than inferring activity from pane output, because it is the agent's own account of whether it is mid-turn rather than a guess from whether glyphs changed.
This is the cheap, high-value half: read a JSON file. No protocol reverse-engineering, no new transport. It slots straight into the existing LifecycleSource port from #333 as a fourth source alongside CmuxStore, NativeHook, and CodexAppServer.
Directly relevant to #567 (liveness trusts its own event history over observable reality) and #618 (adopt structured APIs as ground truth). See "Relationship to #618" below — these are complementary, not competing.
Opportunity B — inbox socket replaces confirmedSendToPane
packages/workspaces/src/crew-pane.ts:152 is the paste-settle-Enter machine, wired in at packages/cli/src/commands/crew.ts:70. Its own doc comment enumerates four hardening steps and three separate bug references, and the body carries #455, #484, #516 guards inline. It infers delivery from whether an input box emptied after a draft was seen.
The peer channel replaces that inference with a real answer. Delivery resolves to one of three documented outcomes — delivered, held, or refused — and, for same-machine sends, the sender is notified: a notice when the message is held, and a follow-up when the receiver later delivers, denies, or lets it expire.
That is precisely the receipt #551 asks for (ping has no delivery or read receipt) and precisely the false-negative that #514/#657 is about.
The seam already exists: crew.ts:70 injects sendToPane, so a ClaudePeerSocket delivery driver can slot in behind it without touching call sites.
So this work would fix the delivery path we are currently using least, and leave the one we use most untouched. Adopting it naively would also re-commit the exact mistake CLAUDE.md warns about and that #657 already diagnosed once: "the v0.13.3 UserPromptSubmit fix was Claude-only and never got a cross-agent migration path." Shipping a second Claude-only delivery path without a seam would repeat that, one layer down.
Therefore: this must be built as a capability behind the existing driver seam, never as a special case.AgentDriver should expose something like supportsPeerMessaging() / peerAddress(), with the pane-scraping path retained as the fallback for agents that lack it.
Open questions (spike needed)
Socket wire format. The docs explicitly say to read the inbox-socket section "when you want a script or hook to post into a session", and export CLAUDE_CODE_MESSAGING_SOCKET to hooks and Bash commands (confirmed: our captain has /tmp/cc-socks/37511.sock). But the payload format is not publicly documented and there is no claude CLI subcommand for posting. The daemon is a Node process, not a Claude session — it has no SendMessage tool. Determining whether the daemon can post directly to the socket is the single blocking unknown for Opportunity B.
Sender identity / inbound controls. The daemon is not a child process of the crew session, so the "own-child" verification path does not apply (and on macOS that check only works while the posting process is still running). Delivery then falls to crossSessionInbound. Crews launch with --permission-mode auto, which counts as prompting, so the default should deliver — but we should set crossSessionInbound: "accept" explicitly in crew settings rather than rely on a default we did not choose.
Rate limiting is a real hazard for us. Claude Code "drops identical repeats arriving within a short window" and caps unread messages at 50. Our retry logic re-sends the same text. A silently-dropped retry would look exactly like the false negative we are trying to eliminate. Needs explicit handling.
Feature-flag fragility.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, DISABLE_TELEMETRY, DO_NOT_TRACK, and DISABLE_GROWTHBOOK each disable the feature. We must detect capability at runtime, never assume it.
Plain text only. No structured protocol across sessions. Fine for prompt delivery; rules out using it as a typed control-plane transport.
Channels as an alternative for Opportunity B.Channels are the documented way to push external events into a running session, which is literally what the daemon does. But: research preview, requires --channels at launch, requires Bun, requires an allowlisted plugin (custom needs --dangerously-load-development-channels), and the flag syntax and protocol contract may change. Heavier and less stable than the socket, but officially sanctioned. Worth evaluating against option 1 rather than assumed inferior.
#618 proposes adopting cmux structured session/feed APIs as ground truth. These are complementary rather than alternatives, and the distinction is worth stating plainly:
The Claude registry is the more accurate signal where it applies, because an agent knows whether it is mid-turn and a terminal can only guess. cmux is the more universal one. A LifecycleSource that prefers the peer registry when present and falls back to cmux otherwise gets both.
Immediate usable win — captain ↔ captain chat
Independent of any crew delivery work, this is usable today with zero code: captains are Claude Code sessions, they already appear in each other's ListAgents, and they can message each other directly with real delivery receipts.
That is a better transport than what squadrant ping does today (#551: ✔ Pinged only means "sent into the void") and is the closest thing yet to the cross-captain channel #552 asks for. Worth validating with a live captain-to-captain round trip before committing to any of the above.
Proposed phasing
Phase 0 — spike (small). Answer Q1 and Q2 empirically on the liveness-lab throwaway project: spawn a --agent claude crew, confirm it registers, attempt a socket post from a plain Node process, observe the outcome. Per the v0.18.0 lesson — smoke test is the real gate, not the test count — this must be run live, not unit-tested.
Phase 2 — delivery (gated on Phase 0). Only if the spike proves the daemon can post: add a peerMessaging capability to AgentDriver and a ClaudePeerSocket delivery driver behind the sendToPane seam, with confirmedSendToPane retained as the fallback.
Summary
Claude Code v2.1.224+ shipped cross-session messaging: sessions register themselves on disk, bind a per-session Unix inbox socket, and can list/message each other via the
ListAgentsandSendMessagetools. Announced 2026-08-07 (@ClaudeDevs, docs).This is a first-party, ground-truth replacement for two things squadrant currently reverse-engineers by scraping terminal pixels:
LivenessRegistry+ pid floor.confirmedSendToPanepaste → settle → Enter → re-read screen.Both are the source of a long tail of shipped bugs: #447, #455, #466, #484, #492, #516, #566, #590, and the currently-open #514/#657.
Local evidence (verified on this machine, 2026-08-08)
claude --version→2.1.226(feature requires ≥ 2.1.224; macOS + Linux only, which matches our macOS-only stance).Every running Claude Code session writes
~/.claude/sessions/<pid>.json:{"pid":37511,"sessionId":"bb38bbd8-...","cwd":"/Users/q3labsadmin/me/squadrant", "startedAt":1786167988194,"version":"2.1.226","peerProtocol":1, "kind":"interactive","entrypoint":"cli", "messagingSocketPath":"/tmp/cc-socks/37511.sock", "name":"squadrant-e8","nameSource":"derived", "status":"busy","updatedAt":1786168376031,"statusUpdatedAt":1786168376031}The squadrant captain session is itself registered. Captains are launched by squadrant into cmux panes, exactly the same way crews are — so squadrant-spawned Claude sessions bind an inbox socket and are addressable with no changes on our side. Feasibility for the
--agent claudepath is already proven; nothing needs to be built to make sessions discoverable.ListAgentsfrom the squadrant captain right now returns live peers with liveness state:Note
peerProtocol: 1— the registry is explicitly versioned, so it is a contract rather than an incidental artifact.Opportunity A —
statusfield replaces liveness screen-scraping~/.claude/sessions/<pid>.jsoncarriesstatus: "idle" | "busy"plusstatusUpdatedAt, self-reported by the agent. That is strictly better than inferring activity from pane output, because it is the agent's own account of whether it is mid-turn rather than a guess from whether glyphs changed.This is the cheap, high-value half: read a JSON file. No protocol reverse-engineering, no new transport. It slots straight into the existing
LifecycleSourceport from #333 as a fourth source alongsideCmuxStore,NativeHook, andCodexAppServer.Directly relevant to #567 (liveness trusts its own event history over observable reality) and #618 (adopt structured APIs as ground truth). See "Relationship to #618" below — these are complementary, not competing.
Opportunity B — inbox socket replaces
confirmedSendToPanepackages/workspaces/src/crew-pane.ts:152is the paste-settle-Enter machine, wired in atpackages/cli/src/commands/crew.ts:70. Its own doc comment enumerates four hardening steps and three separate bug references, and the body carries#455,#484,#516guards inline. It infers delivery from whether an input box emptied after a draft was seen.The peer channel replaces that inference with a real answer. Delivery resolves to one of three documented outcomes — delivered, held, or refused — and, for same-machine sends, the sender is notified: a notice when the message is held, and a follow-up when the receiver later delivers, denies, or lets it expire.
That is precisely the receipt #551 asks for (
pinghas no delivery or read receipt) and precisely the false-negative that #514/#657 is about.The seam already exists:
crew.ts:70injectssendToPane, so aClaudePeerSocketdelivery driver can slot in behind it without touching call sites.Cross-session messaging is Claude Code only. It does nothing for codex, opencode, or gemini crews.
This matters more than it first appears:
--agent opencode(operator is out of Claude credit).So this work would fix the delivery path we are currently using least, and leave the one we use most untouched. Adopting it naively would also re-commit the exact mistake
CLAUDE.mdwarns about and that #657 already diagnosed once: "the v0.13.3UserPromptSubmitfix was Claude-only and never got a cross-agent migration path." Shipping a second Claude-only delivery path without a seam would repeat that, one layer down.Therefore: this must be built as a capability behind the existing driver seam, never as a special case.
AgentDrivershould expose something likesupportsPeerMessaging()/peerAddress(), with the pane-scraping path retained as the fallback for agents that lack it.Open questions (spike needed)
CLAUDE_CODE_MESSAGING_SOCKETto hooks and Bash commands (confirmed: our captain has/tmp/cc-socks/37511.sock). But the payload format is not publicly documented and there is noclaudeCLI subcommand for posting. The daemon is a Node process, not a Claude session — it has noSendMessagetool. Determining whether the daemon can post directly to the socket is the single blocking unknown for Opportunity B.crossSessionInbound. Crews launch with--permission-mode auto, which counts as prompting, so the default should deliver — but we should setcrossSessionInbound: "accept"explicitly in crew settings rather than rely on a default we did not choose.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC,DISABLE_TELEMETRY,DO_NOT_TRACK, andDISABLE_GROWTHBOOKeach disable the feature. We must detect capability at runtime, never assume it.--channelsat launch, requires Bun, requires an allowlisted plugin (custom needs--dangerously-load-development-channels), and the flag syntax and protocol contract may change. Heavier and less stable than the socket, but officially sanctioned. Worth evaluating against option 1 rather than assumed inferior.Relationship to #618
#618 proposes adopting cmux structured session/feed APIs as ground truth. These are complementary rather than alternatives, and the distinction is worth stating plainly:
The Claude registry is the more accurate signal where it applies, because an agent knows whether it is mid-turn and a terminal can only guess. cmux is the more universal one. A
LifecycleSourcethat prefers the peer registry when present and falls back to cmux otherwise gets both.Immediate usable win — captain ↔ captain chat
Independent of any crew delivery work, this is usable today with zero code: captains are Claude Code sessions, they already appear in each other's
ListAgents, and they can message each other directly with real delivery receipts.That is a better transport than what
squadrant pingdoes today (#551:✔ Pingedonly means "sent into the void") and is the closest thing yet to the cross-captain channel #552 asks for. Worth validating with a live captain-to-captain round trip before committing to any of the above.Proposed phasing
liveness-labthrowaway project: spawn a--agent claudecrew, confirm it registers, attempt a socket post from a plain Node process, observe the outcome. Per the v0.18.0 lesson — smoke test is the real gate, not the test count — this must be run live, not unit-tested.ClaudePeerRegistryas aLifecycleSource, readingstatusfrom the registry files. Read-only, no delivery changes, immediately improves Daemon must detect a cmux/system restart and REMAP everything — liveness trusts its own event history over observable reality #567.peerMessagingcapability toAgentDriverand aClaudePeerSocketdelivery driver behind thesendToPaneseam, withconfirmedSendToPaneretained as the fallback.ping/dispatchtransport now that real receipts exist (ping has no delivery or read receipt — '✔ Pinged' only means 'sent into the void' #551, No first-class cross-captain artifact channel — captains resort to writing files into each other's vaults, with no notification #552).#514/#657 stays open and separately prioritised regardless — none of this touches the opencode path.
References
packages/workspaces/src/crew-pane.ts:152·packages/cli/src/commands/crew.ts:70·packages/core/src/liveness.ts