Originally created by: kumaakh
Explore an architecture where fleet-mcp instances can inject messages into each other's running conversations, enabling real-time PM↔member communication without polling.
Today, PM communicates with members only at dispatch time (execute_prompt). Once a session is running, the PM has no way to send new information into it. Members have no way to surface mid-session questions or status updates back to the PM without stopping (STOP + report).
Each fleet member, on startup, creates a named channel keyed to its member GUID:
/tmp/fleet/<member-guid>.sock\.\pipe\fleet-<member-guid>The channel stays open for the lifetime of the Claude Code session.
PM → Member (inject):
PM uses its fleet-mcp instance to write a message to fleet/<member-guid> channel. The running member's fleet-mcp instance reads from that channel and injects the message into the active conversation turn (e.g. appended as a system-level prompt injection or a new user turn).
Member → PM (reply):
Members write back on a well-known PM channel (e.g. fleet/pm.sock) or a response channel. PM's fleet-mcp surfaces the message.
Very preliminary — needs architecture exploration before any implementation. Log here for future sprint consideration.
fleet_status + monitor_task for local members
Originally posted by: kumaakh
Technical direction: This is a research/architecture spike — no immediate implementation. Before committing, verify two prerequisites:
Claude Code injection feasibility: Does the Claude Code CLI support injecting a message into an active running session from outside the process? The
--output-format stream-jsoninterface is output-only. The/injector stdin-append path needs investigation — if the CLI doesn't support mid-session injection, the architecture is blocked at the member side regardless of the pipe mechanism.MCP tool result injection: The more feasible near-term approach is for the member's fleet-mcp to expose a
receive_messagetool that the member's Claude session can call as a poll ('any new messages for me?'). This avoids the injection problem entirely and works today without CLI changes.If proceeding with pipes:
src/services/ipc-channel.tsmodule usingnet.createServer(Unix UDS) on macOS/Linux and\\.\pipe\fleet-<guid>via\\?\pipe\on Windows (Node.jsnetmodule supports both).fleet/pm); member channels keyed toagent.id.fs.chmodSync(sockPath, 0o700)on Unix; on Windows use default pipe ACLs (local user only).Recommend starting with the polling pattern as a lower-risk alternative — defer the pipe architecture until Claude Code CLI supports injection natively.
Originally posted by: kumaakh
Research: Inter-fleet messaging via named pipes / UDS — Feasibility findings
1. Node.js IPC capabilities for UDS and Named Pipes
Node.js
netmodule has solid cross-platform support:net.createServer()can listen on a filesystem path (Unix Domain Socket). Standard POSIX behavior — the socket file appears in the filesystem with permission bits.net.createServer()can listen on\\.\pipe\<name>. Named Pipes are a first-class Windows IPC mechanism. Node.js handles the\\.\pipe\prefix natively in thenetmodule — no special libraries needed.net.Server/net.SocketAPI. You pass a filesystem path on Unix or a\\.\pipe\...path on Windows. The fleet codebase already does this successfully insrc/services/auth-socket.ts.Gotchas:
\\.\pipe\<name>format. Backslash escaping in JS strings requires\\\\.\\pipe\\.... Fleet's existing code handles this correctly (auth-socket.ts line 33)..sockfile persists and blocks re-listen. Mustfs.unlinkSync()before binding. On Windows, named pipes are kernel objects and auto-cleanup on process exit. Fleet already handles both cases (auth-socket.ts lines 49-52, 99-104).Existing precedent in apra-fleet: The
auth-socket.tsmodule is a production-quality UDS/Named Pipe implementation using newline-delimited JSON protocol. It demonstrates the pattern works end-to-end across platforms.2. Claude Code injection point: Can a UDS message reach a running Claude Code process?
This is the critical blocker. Even if fleet can deliver a message to the member's channel, the question is how Claude Code receives and acts on it.
Claude Code has NO mechanism for mid-turn message injection:
claude -preads prompt → runs agentic loop → exits. No external input channel during execution.--input-format stream-jsonis for providing the initial input as a stream, not for injecting messages during execution.What a UDS channel COULD do today (without Claude Code changes):
claude -pprocess-cand the new directiveWhat would require upstream Claude Code changes:
--listen <socket-path>flag that makesclaude -ppoll a UDS for injected user turns during its agentic loop/injectAPI endpoint if Claude Code exposed a local HTTP server during sessions3. Security implications of UDS / Named Pipes
Unix Domain Sockets:
0600(owner read/write only) — other users cannot connect.fs.chmodSync(sockPath, 0o600)in auth-socket.ts line 112.0700permissions. Fleet uses~/.apra-fleet/data/with0o700mode.Windows Named Pipes:
net.createServer()inherit the creating process's security descriptor. Only the same user session can connect.netmodule). However, the default same-user-session scoping is sufficient.Risk assessment: Low. The same-user scoping is adequate. An attacker with same-user access already has full access to the member's files and processes anyway.
4. Verdict: Feasibility and effort assessment
Is this feasible without changes to Claude Code itself?
Partially. The UDS/Named Pipe infrastructure is fully feasible — fleet already has a working implementation (auth-socket.ts). The gap is on the receiving end: Claude Code cannot accept mid-turn injected messages.
What's achievable today (no upstream changes):
-c("cancel and redirect")execute_promptresumeWhat requires upstream Claude Code changes:
claude -p --listen /path/to/socketor an HTTP control planeEffort comparison:
Recommendation:
execute_prompt(issue [#75]). This is the highest-value, lowest-effort change.The UDS infrastructure from [#152] is the right long-term architecture, but its value is limited until Claude Code supports mid-turn injection. The pragmatic path is cancel+resume first, UDS transport second.
Related
Tickets: #152
Tickets: #75
Originally posted by: kumaakh
Research: Inter-fleet messaging via UDS — supplemental findings
Building on the prior research, I explored the codebase more deeply and identified both a strong existing precedent and an alternative to the "Claude Code injection blocker."
1. Node.js IPC:
auth-socket.tsis a production-ready templateThe existing
src/services/auth-socket.tsis effectively a complete UDS/Named Pipe implementation that can be adapted:waitForPassword()The path convention would be:
~/.apra-fleet/data/channels/<member-guid>.sock\\.\pipe\apra-fleet-channel-<member-guid>Effort estimate for the channel infra itself: ~2-3 days. Most of the code can be factored out of
auth-socket.tsinto a genericipc-channel.ts.2. Claude Code injection:
--input-format stream-jsonas a bypassThe prior research correctly identified that Claude Code has no mid-turn injection API. However, I found evidence that
--input-format stream-jsonmode may accept ongoing user messages via stdin (see my comment on [#75] for full details).If confirmed, the architecture becomes:
This eliminates the "Claude Code injection blocker" without requiring any upstream changes. The UDS channel serves as the transport;
stream-jsonstdin serves as the delivery mechanism.3. Security assessment (expanded)
Same-user scoping is the right model. Both UDS and Named Pipes naturally scope to the user:
0600in a0700directory. Only the owning user can connect. Fleet already uses this pattern (FLEET_DIRis created with0o700).One additional concern: If the PM channel (
fleet/pm.sock) accepts messages from any member, a compromised member could impersonate another member's responses. Mitigation: includemember_idin the message and validate it against the socket's expected sender (though this is weak since a same-user attacker can spoof). For fleet's trust model (all members are the same user), this is acceptable.4. Revised architecture with stream-json
Phase 1 — No UDS needed (issue [#75] scope):
stream-jsonstdin injection inexecute_promptfor local membersMap<agentId, { stdin, sessionId }>update: trueparameter to inject into running sessionsPhase 2 — UDS channels (this issue):
auth-socket.tsinto genericipc-channel.ts~/.apra-fleet/data/channels/<id>.sockstream-jsonstdin (local) or queues for next resume (remote)Phase 3 — Remote member support:
stream.end())execute_promptresume5. Verdict update
The UDS architecture is feasible today with the
stream-jsonapproach, though the full vision requires two prerequisites to be confirmed:--input-format stream-jsonmulti-turn injection works (needs testing — high confidence based on CLI help text but unverified)If
stream-jsoninjection is confirmed, the UDS architecture from this issue becomes the right long-term transport layer — it decouples message delivery (UDS) from message consumption (stream-json stdin), and both are implementable without upstream Claude Code changes.Recommended priority: Test
stream-jsoninjection first (1-2 hours). If confirmed, implement Phase 1 (no UDS, just stdin injection) under [#75], then build UDS channels as Phase 2 under this issue.Related
Tickets: #152
Tickets: #75