SPEC-0011: Channels Push Delivery
SPEC · SPEC-0011 · Status · approved · Date · 2026-07-10 · Implements · ADR-0013 · Requires · SPEC-0007, SPEC-0008, SPEC-0014
Overview
Switchboard pushes messages into human-owned harnesses (Claude Code and other MCP
harnesses) over the Claude Code Channels
open standard, realizing ADR-0013. A channel
is an MCP server that declares the experimental claude/channel capability; the harness registers a
listener, and the server wakes the live session by emitting a notifications/claude/channel
JSON-RPC notification that lands in-session as a <channel source="switchboard" …>…</channel> event.
This capability is a best-effort notify layer over the durable todo queue, not a delivery system
of record. The durable todo (ADR-0007) is the
work; the channel notification is only a doorbell. Delivery is lossy and unacknowledged by the
standard's own definition — a notifications/claude/channel resolves when written to the transport,
not when the session processes it, and events are dropped silently when no session is attached, the
channel is unloaded, or org policy blocks it. Therefore push MUST NOT gate correctness: when push is
unavailable the todo simply stays pending and is drained by the pull-based worker loop.
Transport. Channels ride the vended MCP endpoint's Streamable HTTP session
(SPEC-0014, ADR-0017):
the same session that serves the durable work verbs advertises the channel capability and carries the
doorbell notifications on its server-to-client stream. The earlier local stdio adapter
(switchboard channel) is retired per ADR-0017; session mechanics, authentication, tool serving, and
stream lifecycle are governed by SPEC-0014. This spec governs push semantics: what may be pushed,
when, in what shape, and with what delivery guarantees.
Requirements
Requirement: Channel Capability on the Vended Session
Every vended MCP session (SPEC-0014) MUST advertise
capabilities.experimental["claude/channel"] = {} alongside capabilities.tools = {} on
initialize, MUST identify itself with serverInfo.name = "switchboard", and MUST supply
human-readable instructions explaining that todos arrive as <channel> doorbell events while the
durable queue remains the record. Protocol-version negotiation is delegated to the SDK per
SPEC-0014.
Scenario: Initialize advertises the channel capability
- WHEN a harness sends
initializeto a vended endpoint - THEN the result includes
capabilities.experimental["claude/channel"]andcapabilities.tools,serverInfo.name = "switchboard", and instructions describing doorbell-over-durable-queue semantics
Requirement: Push Notification Shape
On a todo transition that should wake a channel-attached consumer (default: create and
assign), switchboard MUST emit exactly one notifications/claude/channel per todo per attached
session in scope. Its content field MUST be a single legible one-line summary. Its meta field
MUST carry routing identifiers and, because the standard silently drops keys that are not
identifier-safe, all meta keys MUST be snake_case (letters, digits, underscore only): todo_id
and queue are REQUIRED; kind and source MUST be included when present on the todo. The
notification MUST NOT itself carry a lease — the agent reads todo_id and then claims via the
durable verbs.
Scenario: New todo produces one identifier-safe notification
- WHEN a todo becomes ready in a queue within an attached session's scope
- THEN switchboard MUST emit one
notifications/claude/channelon that session whosemeta.todo_idequals the todo id, whosemeta.queueequals the todo queue, and whosemeta.kind/meta.sourceare set from the todo when present, using snake_case keys only
Scenario: Notification carries no lease
- WHEN a push notification is emitted for a todo
- THEN the notification MUST contain only a summary and identifiers, and the agent MUST claim the
todo through the durable
claimverb rather than treating the notification as delivery
Requirement: Scope-Filtered Fan-Out
A doorbell MUST be delivered only to sessions whose vended endpoint scope (SPEC-0007) covers the todo's queue. Sessions MUST NOT receive notifications for queues outside their grant, regardless of verb allowlist.
Scenario: Out-of-scope todo is not pushed
- WHEN a todo becomes ready in a queue not covered by an attached session's endpoint scope
- THEN that session MUST receive no notification for it
Requirement: Best-Effort Lossy Delivery and Degradation to Pull
Delivery MUST be treated as best-effort and unacknowledged. A push MUST NOT be a precondition for a
todo being worked. When no session is attached, the channel is unloaded, org policy blocks, or the
push fails for any reason, the todo MUST remain pending and MUST be drained by the pull-based worker
loop (ADR-0007) with no loss. Notification is
at-least-once; duplicate notifications MUST be harmless, relying on idempotency-key dedup and an
idempotent claim. A slow or full subscriber MUST cause the push to be dropped (never blocking the
publisher), because the queue is the ledger and the push is only a doorbell.
Scenario: No attached session loses nothing
- WHEN a todo is created but no harness session is attached to receive the push
- THEN the push is dropped silently AND the todo MUST remain
pendingand MUST be delivered later when the harness reconnects and drains the queue by pull
Scenario: Duplicate notifications do not double-process
- WHEN the same todo is notified more than once
- THEN the agent's
claimMUST be idempotent so that at most one worker takes the todo and no double-processing occurs
Scenario: Slow subscriber is dropped, not blocked
- WHEN a subscriber's buffered fan-out channel is full at publish time
- THEN the publisher MUST drop the event for that subscriber rather than block, and the todo remains recoverable by pull
Requirement: Sender Gate and Injection Safety
Only verified, human-attributed todos MUST be eligible to push; switchboard's per-source
verification (ADR-0003) and human
ownership (ADR-0008) are the "gate on
the sender" the standard requires. Before emitting a notification, switchboard MUST neutralize any
</channel> sequence in payload-derived content so a webhook body cannot break out of the
<channel> wrapper. Secret-bearing values MUST NOT be inlined into a push; they MUST remain behind a
fetchable secret-ref.
Scenario: Payload cannot break out of the channel wrapper
- WHEN a todo title contains a literal
</channel>sequence - THEN switchboard MUST replace it with a safe substitute (e.g.
«/channel») before emitting the notification so the<channel>wrapper cannot be closed by attacker-controlled content
Scenario: Only verified, attributed todos are pushed
- WHEN a todo has not passed per-source verification and human attribution
- THEN it MUST NOT be eligible for a channel push
Requirement: Error Handling Standards
Push emission MUST follow structured error handling: publish failures MUST be logged with context (endpoint slug, todo id) and dropped per the lossy contract — never retried into a blocking path and never silently swallowed without a log line; structured key-value logging MUST be used; the bearer credential MUST never appear in logs or notification content.
Scenario: Publish failure is logged and dropped
- WHEN writing a notification to a session stream fails
- THEN the failure MUST be logged with endpoint and todo context and the notification dropped, leaving the todo recoverable by pull
Security Requirements
Transport authentication, rate limiting, headers, body limits, CSRF posture, and redirect posture for the carrying session are governed by SPEC-0014 — channels introduce no additional endpoints. This capability's own security obligations are the sender gate and injection-safety requirements above, plus:
Authentication
Doorbells MUST only ever be written to sessions that authenticated per SPEC-0014; there is no unauthenticated notification path.
Rate Limiting
Push emission is intrinsically bounded by todo creation rate and by the fan-out hub's per-subscriber buffer; when the buffer is full, events are dropped rather than queued, providing natural backpressure. No additional rate limit is required for emission itself.