Status:
accepted · ADR-25 · Filed 2026-04-25Decision
prism_start, prism_checkpoint, and prism_wrap MUST NOT carry pending
signals in their response payloads. These three “session lifecycle” verbs
are exempt from the SPEC-037 per-verb piggyback drain. Pending signals are
delivered via either (a) the explicit prism_signals_pending poll, or (b)
implicit piggyback on any non-lifecycle bootstrapped verb call.
This rule is captured as a one-line principle in PRISM.md and elaborated
in SPEC-041. Implementation modifies mcp/server.py:1649-1661 to remove
the explicit drain from prism_start, and updates the require_bootstrap
decorator at mcp/server.py:237-265 to skip _piggyback_drain for all
three lifecycle verbs.
Rationale
Three concerns motivate the separation: 1. Single responsibility. A session-lifecycle verb has one job — manage session state (begin / persist / end). Mixing in messaging delivery couples two unrelated concerns; every future change to either has to reason about both. The verb contract grows opaque to vibe-coding callers, who then write code expecting messaging guarantees from verbs that should have made only session guarantees. 2. Race correctness on prism_start specifically. Identity-targeted signals queued before a new session existed are stale by definition. They may have been intended for the prior (now-wrapped) session of the same identity, not the fresh one. Delivering them onprism_start
silently re-routes them. Forcing the agent to call any other verb first
(or prism_signals_pending explicitly) creates an opportunity for
stale-signal hygiene to apply. (This concern does not apply to wrap or
checkpoint — those run after registration. Symmetry of the principle
alone justifies exempting them.)
3. Composition with hd_exec. SPEC-040 introduces hd_exec as a
non-start session verb that DOES drain via piggyback. The asymmetry is
intentional and load-bearing: a pickup is a continuation (inherits
unread messages); a fresh start is a beginning (does not). The
distinction collapses if both verbs multiplex messaging.
Alternatives Considered
Drain on all bootstrapped verbs uniformly (status quo before this ADR). Rejected — couples session lifecycle to messaging, creates the prism_start race condition, drives opaque contract growth. Practical hazard: caller code grew to expect signals on bootstrap, which would make future protocol changes (push-based delivery, lease-based ownership) breaking changes. Drain only on prism_start (narrow fix). Rejected — leaves the asymmetry where two of three lifecycle verbs (checkpoint, wrap)
still multiplex while one (start) doesn’t. Hard to defend the line.
Frank flagged “session lifecycle is session lifecycle” — any
cherry-picking erodes the principle.
Drain on transitions (start, wrap) but not steady-state (checkpoint).
Rejected — arbitrary split. Checkpoint is a lifecycle verb regardless
of whether it’s a transition or a save-point.
TTL on queued signals to prevent indefinite accumulation. Deferred,
not rejected. Current scope is correctness (no signal loss). Hygiene is
a separate concern; if queue growth becomes problematic, a future spec
adds per-type TTL.
