Skip to main content

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 initialize to a vended endpoint
  • THEN the result includes capabilities.experimental["claude/channel"] and capabilities.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/channel on that session whose meta.todo_id equals the todo id, whose meta.queue equals the todo queue, and whose meta.kind/meta.source are 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 claim verb 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 pending and 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 claim MUST 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.