Status:
draft · Version 0.1 · Filed 2026-04-23SPEC-034 v0.1 — Agent-to-Agent Signal Delivery
Status
draft1. Summary
Enable bidirectional real-time communication between Prism agents (Lola↔Donna initially) using Redis pub/sub as the transport layer, with pluggable delivery strategies per agent surface. Agents can send structured signals (task requests, review feedback, acknowledgments) to named peers without human relay. The design is MVP-scoped to Claude Desktop↔Claude Code but architecturally prepared for Codex (App Serverthread/inject_items), Cursor, and ACP-compatible agents.
2. Motivation
Today, Lola and Donna can see each other viaprism_status but cannot communicate. When Lola reviews a spec and wants Donna to act on it, or when Donna ships code and Lola needs to know, the only path is Frank relaying between chat windows. This is the human-as-message-bus antipattern — it wastes human attention on routing that the system should handle.
With SPEC-032 Phase A deployed, both agents have active Redis-backed sessions with stable heartbeats. The pub/sub infrastructure exists (prism:events:session:{id} and prism:events:project:{tenant}:{project} channels per SPEC-032 §5.6). What’s missing is: (1) a subscriber that listens, (2) a delivery strategy that gets the signal to the agent, and (3) a verb that lets agents compose and send signals.
3. The Acceptance Test (drives the entire spec)
This test defines “done.” If this scenario works end-to-end, SPEC-034 is complete.Preconditions
- Lola (Claude Desktop, master) and Donna (Claude Code, peer) both registered and heartbeating on PID-PGR01.
- Signal infrastructure deployed (subscriber threads + delivery strategies).
Step 1 — Lola sends a task to Donna
Frank says: “Ask Donna to review SPEC-033.” Lola calls:prism:events:session:{donna_session_id}.
Step 2 — Donna receives and acts
Donna’s MCP server subscriber thread dequeues the event. HerChannelsPushStrategy pushes it into her Claude Code session via MCP channels. Donna sees inline:
📨 Signal from Lola (ReviewRequested): “Summarize SPEC-033, review it, and provide feedback on gaps or concerns. Reply via signal when complete.”Donna reads SPEC-033 via
semantic_recall, writes her review, then replies:
Step 3 — Lola receives feedback
Lola’s MCP server subscriber thread dequeues theReviewCompleted event. Since Claude Desktop uses PiggybackStrategy, the event buffers. On Lola’s next Prism verb call, the response includes:
Step 4 — Donna receives acknowledgment
Donna sees inline via MCP channels push:📨 Signal from Lola (Acknowledgment): “Thanks Donna. Received your review. Frank or I will get back to you with decisions on the gaps.”Loop closed. Zero human relay for agent↔agent communication.
What this test proves
- Bidirectional signal delivery works (Lola→Donna and Donna→Lola).
- Two different delivery strategies work in the same flow (push for Code, piggyback for Desktop).
- Agents compose structured messages and act on them autonomously.
- Identity-targeted addressing resolves correctly (“Donna” → her current session).
- Acknowledgment closes the loop — sender knows the message landed.
- Conversation threading works via
in_reply_to.
4. Architecture
4.1 Signal flow
4.2 Identity resolution (not session targeting)
Signals target agent identities (“Donna”, “Lola”), not session IDs. The backend resolves identity → current active session at send time:- Query Redis: scan
prism:project:{tenant}:{project}:sessionsset. - For each session,
HGET prism:session:{id} agent_identity. - Match target identity → get session_id → PUBLISH to
prism:events:session:{session_id}.
signal_queue table) and delivered on the target’s next prism_start. This makes signals durable across agent restarts — Redis is the real-time delivery layer, Postgres is the durability backstop.
4.3 Broadcast signals
Some signals target all agents on a project, not a specific identity:prism:events:project:{tenant}:{project} — the broadcast channel all subscribers already listen to.
5. Signal schema
5.1 Signal record
5.2 Signal types (MVP)
Agent-originated types (top 5) use
prism_signal verb. System-originated types (bottom 3) are emitted by the backend controller on registration/election events — no verb call needed.
5.3 Redis message format
6. The prism_signal verb
6.1 Contract
6.2 Backend behavior
- Validate
signal_typeagainst known types. - Generate
signal_id(UUID). - Persist to Postgres
signal_queuetable (always — durable record). - Resolve
toidentity → active session via Redis.- Found → PUBLISH to
prism:events:session:{target_session_id}. Setdelivered=true. - Not found (offline) → signal stays in
signal_queuewithdelivered_at=null. Setqueued=true.
- Found → PUBLISH to
- If
to="*"→ PUBLISH toprism:events:project:{tenant}:{project}. Also persist. - Return response.
6.3 Startup drain
Onprism_start, the backend checks signal_queue for any undelivered signals where to_identity matches the caller’s identity. Returns them in the prism_start response as pending_signals[]. Marks them delivered_at=now(), delivery_method='startup_drain'.
7. Delivery strategies (pluggable per agent surface)
7.1 Strategy interface
7.2 MVP strategies
ChannelsPushStrategy (Claude Code — Donna)- Uses MCP server channels/push API to inject signal directly into the Claude Code session.
supports_push() = True- Signal appears inline in Donna’s conversation immediately.
- Delivery confirmation: MCP channels API returns success/failure.
- Buffers signals in a thread-safe queue within the MCP server process.
supports_push() = False- On every Prism verb response, checks queue and appends
pending_signals[]to the response payload. - Delivery confirmation: set
delivered_atwhen signal is included in a verb response.
7.3 Future strategies (designed for, not implemented)
AppServerInjectStrategy (Codex)- WebSocket client connected to Codex App Server (
ws://host:port). - On signal → call
thread/inject_itemswith signal formatted as a system context item. supports_push() = True
- ACP protocol adapter — depends on ACP notification spec.
supports_push() = True(ACP supports server-initiated messages)
- POST signal as JSON to a configured webhook URL.
supports_push() = True- Useful for Slack/Discord notifications, CI integrations, custom dashboards.
7.4 Strategy selection
On MCP server startup, detectPRISM_AGENT_SURFACE and select:
PiggybackStrategy — always works, lowest common denominator.
8. Subscriber thread
8.1 Lifecycle
Spawned onprism_start, alongside the heartbeat thread (SPEC-032 §5.3). Same concurrency pattern: dedicated asyncio event loop on a daemon thread.
8.2 Thread management
- Spawned by
mcp/server.pyonprism_startreturn, after heartbeat thread. - Stopped by setting
stop_eventonprism_wrap, before the release pipeline. - If subscriber thread dies unexpectedly, signals are not lost — they’re persisted in Postgres
signal_queueand will drain on nextprism_start.
9. Postgres signal_queue table
Durable backing store. Redis pub/sub is fire-and-forget; signal_queue ensures signals survive agent restarts and Redis unavailability.
10. Integration with existing infrastructure
10.1 System signals from controller
The controller already emits events on registration/election. SPEC-032 §5.6 defines the pub/sub channels. SPEC-034 standardizes the message format:controller_service.register→ if preempting, PUBLISHMasterPreemptedto preempted session’s channel.controller_service.register→ PUBLISHPeerJoinedto project broadcast channel.controller_service.release→ PUBLISHPeerLeftto project broadcast channel.
prism_signal verb — they’re emitted directly by the backend. But they use the same PrismSignal schema and are received by the same subscriber thread.
10.2 Piggyback injection point
ForPiggybackStrategy, every Prism verb response (from any endpoint) checks the MCP server’s signal buffer:
rules_reminders are injected into prism_start responses today — a cross-cutting concern appended to normal verb returns.
10.3 prism_start integration
prism_start response gains a new field:
11. What this does NOT cover (future specs)
- Signal-driven autonomous task execution. SPEC-034 delivers signals; it does not define how an agent should autonomously act on a
TaskAssignedsignal without human approval. That’s a methodology/approval question. - Signal routing across projects. MVP is project-scoped. Cross-project signals (e.g., Prism agent sending to MemRGR agent) are future.
- Signal encryption. MVP trusts the Redis + Postgres security posture (loopback/ufw/TLS per SPEC-032 §8). End-to-end signal encryption is future.
- Rate limiting. MVP has no throttle. If an agent loops on
prism_signal, it floods. Rate limiting (per sender per minute) is a Phase 2 concern. - UI for signal history. No dashboard. Signals are queryable via
semantic_recall(they’re persisted as deltas or in signal_queue). A visual signal log is future.
12. Deployment — file-by-file diff
13. Codex/Cursor readiness checklist
These are not implemented in MVP but the architecture must not block them:-
SignalDeliveryStrategyis an ABC, not a concrete class — new strategies plug in without modifying existing code. -
STRATEGY_MAPis configuration, not hardcoded logic — adding"codex": AppServerInjectStrategyis a one-line change. - Signal schema is agent-agnostic — no Claude-specific fields in
PrismSignal. - Identity resolution is backend-side — agents don’t need to know each other’s session IDs.
-
signal_queuepersistence ensures signals survive across any agent restart pattern, regardless of agent runtime. - Redis message format is plain JSON — any subscriber (Python, TypeScript, Elixir Codex SDK) can deserialize it.
- Broadcast signals (
to="*") work for any number of subscribers on the project channel.
14. Acceptance criteria
prism_signalverb exists and is callable from both Claude Desktop and Claude Code MCP servers.- Identity resolution:
to="Donna"resolves to Donna’s current active session via Redis lookup. - Online delivery: signal sent to an online agent is delivered within 5 seconds (push) or on next verb call (piggyback).
- Offline queuing: signal sent to an offline agent is persisted in
signal_queueand delivered on target’s nextprism_start. - Piggyback delivery: Lola receives signals appended to Prism verb responses as
pending_signals[]. - Push delivery: Donna receives signals inline in her Claude Code session via MCP channels.
- Threading:
in_reply_tocorrectly links reply signals to their parent. - System signals:
MasterPreempted,PeerJoined,PeerLeftare emitted by the controller and received by the subscriber thread. - Broadcast:
to="*"delivers to all active agents on the project. - Full acceptance test (§3) passes end-to-end: Lola sends ReviewRequested → Donna receives and reviews → Donna sends ReviewCompleted → Lola receives on next verb call → Lola sends Acknowledgment → Donna receives. Zero human relay.
- Smoke test
mcp/smoke_spec034_signal.pycovers send → receive → reply → ack round trip. signal_queuetable exists with correct schema and indexes.- Strategy selection is driven by
PRISM_AGENT_SURFACEenv var — no hardcoded agent detection. - Adding a new strategy for Codex/Cursor requires only: (a) new strategy class implementing
SignalDeliveryStrategy, (b) one entry inSTRATEGY_MAP.
15. Relationship to other specs
- Depends on SPEC-032 (Redis session plane — pub/sub channels, session registration, heartbeat thread pattern).
- Depends on SPEC-019 (env resolution —
PRISM_AGENT_SURFACEdetection). - Extends SPEC-030 (controller — system signal emission on register/release/preempt).
- Extends SPEC-033 (architecture — Layer 4 orchestrator evolving from tool server to coordination plane; Layer 5 Redis as broadcast domain).
- Informs future adapter spec (Codex
AppServerInjectStrategy, ACP adapter). - Informs SPEC-028 (TS MCP — subscriber thread pattern in TypeScript/Node.js).
16. Authorship
- Architecture + acceptance test: Frank — defined the Lola↔Donna test scenario as the driving requirement.
- Spec author: Lola (Claude Desktop, session 77016bac) 2026-04-23.
- Design constraint: MVP is Lola↔Donna; architecture must not block Codex/Cursor.

