Status:
draft · Version 0.1 · Filed 2026-04-25SPEC-044: Channel-Based Signal Push — Wire Prism MCP Server as a Claude Code Channel for Real-Time Signal Delivery
Version
0.1Status
draftOrigin
Candi’s high-level design doc (“Prism MCP Notification System”) described a doorbell-not-delivery push architecture: the MCP server pushes a lightweight notification, Claude Code reacts by callingprism_signals_pending to drain the queue. The design was sound. The push leg was never implemented. Signals currently deliver only via startup drain (SPEC-037 §1) or verb-response piggyback (SPEC-037 §4). Idle agents — those waiting for user input with no active verb calls — go dark.
Claude Code v2.1.80 (March 2026) shipped Channels as a research preview: an official API for MCP servers to push events into a running session. This is exactly the mechanism Candi’s design assumed existed. GitHub issue #36665 on the claude-code repo independently describes the same multi-agent coordination gap (synapt project, 4 agents going dark when idle). Channels was Anthropic’s response.
This spec wires the existing Prism MCP server as a Claude Code channel, closing the signal delivery gap for idle agents without introducing new infrastructure.
§1 — What a Channel Is
A channel is an MCP server that declarescapabilities.experimental['claude/channel'] in its Server constructor. Claude Code registers a notification listener for that server. When the server calls mcp.notification({ method: 'notifications/claude/channel', params: { content, meta } }), the event arrives in the model’s context as a <channel> tag. The model sees it and can act on it.
Key properties from the official Channels reference (code.claude.com/docs/en/channels-reference):
- Runs as a stdio subprocess — Prism MCP server already does this
- Events arrive while the session is open — maps to our existing “online delivery” requirement
- Two-way channels can expose reply tools — we already have
prism_signalas the reply mechanism instructionsstring goes into Claude’s system prompt — tells the agent how to handle events- Sender gating via allowlist — not needed for self-hosted Prism (the server IS the trusted source)
§2 — Architecture
- Signal queued — sender calls
prism_signal, backend persists tosignal_queueand PUBLISHes to Redis - Subscriber receives — existing Redis subscriber thread in
mcp/subscriber.pydequeues the event - Doorbell rings — subscriber calls
mcp.notification()with the channel method, pushing a lightweight notification into Claude Code’s context - Agent drains — Claude Code sees the
<channel>tag and callsprism_signals_pending(or any other verb, triggering piggyback drain)
§3 — Implementation
3.1 Capability Declaration
Inmcp/server.py, the MCP Server constructor gains the channel capability:
3.2 Notification Emission
Inmcp/subscriber.py (or the delivery strategy path), when a signal arrives via Redis pub/sub for the current session’s identity:
3.3 Push Coalescing
At most one outstanding notification per drain cycle. When the subscriber receives multiple signals before the agent drains, only the first emits a notification. Subsequent signals are silently queued — the drain call will pick them all up. Implementation: a boolean_notification_pending flag on the subscriber, reset when prism_signals_pending or any piggyback drain fires.
3.4 Strategy Integration
TheChannelsPushStrategy in SPEC-034 §7.2 currently exists as a concept but was never wired. This spec replaces its implementation with the channel notification path above. The strategy’s deliver() method calls _emit_channel_notification(). supports_push() returns True.
PiggybackStrategy (for Claude Desktop / non-channel surfaces) is unchanged — it remains the fallback for any surface that doesn’t support channels.
3.5 Launcher Update
Claude Code must be started with the channel flag to enable push notifications. Two options: Option A — Development flag (research preview):--channels plugin:prism@marketplace.
For v0.1, Option A is acceptable. bin/coder.sh and bin/coder.ps1 gain the flag when launching Claude Code.
3.6 Reconnect Reconciliation
Already handled. Startup drain (SPEC-037 §1) runs on everyprism_start, catching any signals missed during disconnection. The channel notification is advisory; the queue is the source of truth (Candi’s design choice #3).
§4 — Claude Desktop Posture
Claude Desktop does not support the Channels API (it’s a Claude Code feature). Desktop agents continue usingPiggybackStrategy — signals deliver on the next verb call. No change needed. The STRATEGY_MAP (SPEC-034 §7.4) routes correctly:
§5 — Candi’s Open Questions, Resolved
- Idle-push reliability: Answered. Channels API delivers events while the session is open, including during idle. Empirically validated by the community (Telegram/Discord/Slack channel plugins all operate on idle sessions).
- Multi-session semantics: Our signal routing already resolves to a specific identity. The channel notification is per-MCP-server-instance, which maps 1:1 to a Claude Code session. No ambiguity.
-
Cursor failure modes: Moot for the doorbell pattern. The notification carries no payload — it’s a “check the queue” nudge. If the notification is lost, the next verb call’s piggyback drain catches it. If the agent crashes between drain and processing, the signals remain in queue with
delivered_at = NULLand re-deliver on next startup.
§6 — Constraints
- Research preview — API surface may change. Channel registration syntax may evolve.
- Requires claude.ai login — API key auth is not supported for channels. We already use claude.ai login.
- Custom channels need
--dangerously-load-development-channels— acceptable for our deployment; or apply for allowlist. - Events only arrive while session is open — offline signals queue and deliver via startup drain (existing behavior, no regression).
- Team/Enterprise must explicitly enable —
channelsEnabledmanaged setting. Not applicable to our personal install.
§7 — Verification
-
Idle delivery: Donna’s Claude Code session is idle (waiting for user input). Lola sends
prism_signal(to="Donna", signal_type="TaskAssigned", ...). Donna’s session receives a<channel>tag within seconds. Donna callsprism_signals_pendingand acts on the signal. Zero human relay. -
Push coalescing: Send 3 signals to Donna in quick succession. Only 1 channel notification fires. Donna calls
prism_signals_pendingonce and receives all 3. -
Reconnect reconciliation: Send a signal while Donna’s session is down. Start a new Donna session. Signal delivers via startup drain on
prism_start. No channel notification needed. - Piggyback fallback: Lola (Desktop) receives a signal. No channel notification fires (Desktop doesn’t support channels). Signal delivers on Lola’s next verb call via piggyback. Existing behavior, no regression.
-
Launcher integration:
coder --as Donnastarts Claude Code with the channel flag. Verifyprismappears in/mcpserver list with channel capability registered.
§8 — Decisions
- Q1 (notification content): MINIMAL. The notification says “signal arrived, drain the queue” — not the full payload. Doorbell, not delivery. Keeps the push channel lightweight and avoids leaking potentially large payloads into the channel tag.
- Q2 (coalescing strategy): ONE NOTIFICATION PER DRAIN CYCLE. Boolean flag, not a timer or count. Simple, correct, zero configuration.
-
Q3 (launcher flag): DEVELOPMENT FLAG FOR NOW.
--dangerously-load-development-channels server:prismis acceptable during research preview. Migrate to allowlist when/if channels exits preview. -
Q4 (two-way channel / reply tool): NOT NEEDED. The agent already has
prism_signalas the reply mechanism. Exposing a separate channel reply tool would duplicate functionality. The channel is one-way: doorbell in, agent uses existing verbs to act and respond. -
Q5 (permission relay): NOT IMPLEMENTED. The
claude/channel/permissioncapability (v2.1.81+) allows forwarding tool approval prompts to remote devices. Interesting for mobile control scenarios but out of scope for v0.1 signal delivery.
§9 — Files Changed
| File | Change | What |
|---|---|---|
mcp/server.py | Modified | Add claude/channel capability + instructions to Server constructor |
mcp/subscriber.py | Modified | Add _emit_channel_notification() method; call on signal receipt |
mcp/strategies/channels_push.py | Modified | Wire deliver() to channel notification instead of stub |
bin/coder.sh | Modified | Add --dangerously-load-development-channels server:prism when launching Claude Code |
bin/coder.ps1 | Modified | Same flag for Windows |
§10 — Relationships
- Implements SPEC-034 §7.2
ChannelsPushStrategy— the strategy that was designed but never wired - Complements SPEC-037 — piggyback drain remains the primary delivery for non-idle agents and non-channel surfaces; channel push covers the idle gap
- Resolves Candi’s design doc open question #1 (idle-push reliability)
- Informed by SPEC-032 — Redis subscriber thread pattern reused for channel notification emission
- Independent of SPEC-028 — works with both Python and future TypeScript MCP server (Channels API is SDK-level, not language-specific)
§11 — Authorship
- Research + spec: Lola (Claude Desktop, session e094097c) 2026-04-25
- Design doc (Candi): prism-test.txt — high-level architecture that this spec implements
- Steering: Frank — confirmed the gap, directed research, approved implementation direction

