> ## Documentation Index
> Fetch the complete documentation index at: https://prism.ntecdev.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# SPEC-041 v0.2: prism_start Signal Separation — Session Lifecycle Does Not Multiplex with Messaging

> prism_start Signal Separation — Session Lifecycle Does Not Multiplex with Messaging

<Info>**Status:** `draft` · **Version** `0.2` · Filed 2026-04-25</Info>

# SPEC-041: prism\_start Signal Separation — Session Lifecycle Does Not Multiplex with Messaging

## Version

0.2

## Status

draft

## Origin

`prism_start` currently drains pending signals as part of its bootstrap
return payload (`mcp/server.py:1649-1661`). Frank's principle: *prism\_start
is for starting a session, not for getting signals.* Conflating the two:

* couples session lifecycle to messaging — every future change to either
  has to think about both;
* creates a race: signals are drained against the resolved identity before
  the new session is registered, so they target an identity, not a session;
* makes the verb's contract grow over time, opaque to vibe-coding callers.

This spec strips the drain from `prism_start` and codifies the separation
as a methodology rule. Per Q1 decision, the rule extends to all three
session-lifecycle verbs (`prism_start`, `prism_checkpoint`, `prism_wrap`).
Per Q3 decision, the rationale is captured in **ADR-25** with a one-line
rule mirrored to PRISM.md.

## §1 — Rule

Session-lifecycle verbs MUST NOT drain or return pending signals. Three
verbs are in scope: `prism_start`, `prism_checkpoint`, `prism_wrap`.

* Their response payloads MUST NOT contain a `pending_signals` field.
* The `require_bootstrap` decorator (which gates these verbs and applies
  the SPEC-037 per-verb piggyback drain to all bootstrapped verbs) MUST
  special-case all three — drain is skipped for any verb in this set.

Signals are delivered via:

* **Explicit verb**: `prism_signals_pending(pid)` — agent's deliberate
  poll. Idempotent, drains undelivered signals for the caller's identity.
* **Implicit piggyback**: SPEC-037 §4 — every bootstrapped verb EXCEPT
  the three lifecycle verbs carries pending signals on its return payload
  via `_piggyback_drain` (`mcp/server.py:174-234`). `hd_exec` (SPEC-040)
  is NOT a lifecycle verb — it's a session-pickup verb and DOES drain.

## §2 — Rationale

Captured in full in **ADR-25** (Session Lifecycle Does Not Multiplex with
Messaging). Summary:

**Single responsibility.** A lifecycle verb has one job — manage session
state. Mixing in messaging delivery couples two unrelated concerns;
every future change to either has to reason about both.

**Race correctness on prism\_start specifically.** Identity-targeted
signals queued before a new session existed are stale by definition.
Delivering them on `prism_start` silently re-routes them. (This concern
does not apply to wrap or checkpoint — those run after registration.
Symmetry of the principle alone justifies exempting them.)

**Composition with hd\_exec.** SPEC-040's `hd_exec` is intentionally a
non-lifecycle session verb that DOES drain. The asymmetry is load-bearing:
a pickup is a continuation (inherits unread messages); a fresh start is
a beginning (does not).

## §3 — Implementation

**`mcp/server.py:1649-1661`**: delete the `drain_pending_signals` call and
the `result["pending_signals"]` assignment. Adjust the surrounding logic
that may reference the field.

**`mcp/server.py:237-265` (`require_bootstrap`)**: introduce a
`LIFECYCLE_VERBS = {"prism_start", "prism_checkpoint", "prism_wrap"}` set
in module scope. Inside the wrapper, if `func.__name__` is in
`LIFECYCLE_VERBS`, skip `_piggyback_drain` entirely.

**`prism_wrap` docstring update (Q2 paired with D)**: add a note —
*"Consider calling `prism_signals_pending` immediately before
`prism_wrap` if you want last-chance delivery of any signals queued
during the session. The wrap itself does not drain."*

**Constitution updates (Q3 — ADR + PRISM.md)**:

* ADR-25 already filed (this commit).
* PRISM.md §3 (or wherever signal handling is discussed): add a
  one-line rule pointing at ADR-25.
  *"Session lifecycle verbs (`prism_start`, `prism_checkpoint`, `prism_wrap`)
  do not multiplex with messaging. See ADR-25."*
* Mirror the PRISM.md edit to `templates/prism-base.md`,
  `templates/CLAUDE.md`, `templates/AGENTS.md`.

## §4 — Verification

1. Fresh `prism_start` call → response has no `pending_signals` key.
2. `prism_checkpoint` call → response has no `pending_signals` key.
3. `prism_wrap` call → response has no `pending_signals` key.
4. Send a signal to identity `Donna` while no Donna session is active.
   Start a new Donna session via `prism_start` → response has no
   `pending_signals`. Call any other bootstrapped verb (e.g.,
   `prism_status`) → signal is delivered via piggyback.
5. Same scenario, but caller invokes `prism_signals_pending` immediately
   after `prism_start` → signal is returned.
6. SPEC-037 piggyback test suite still passes (drain works on
   non-lifecycle verbs).
7. `hd_exec` (SPEC-040) — being a non-lifecycle session verb — DOES drain
   pending signals via piggyback. Verify: send signal to Donna while no
   Donna active, then Donna calls `hd_exec` → signal returned in
   `pending_signals` field.
8. Signals queued during a session that ends without a draining verb
   call (pathological case: `prism_start → prism_wrap` with no other
   verbs between) accumulate in queue. Next session of same identity
   drains them on first non-lifecycle verb (Q2 decision).

## §5 — Decisions

* **Q1 (scope of lifecycle exemption)**: ALL THREE EXEMPT.
  `prism_start`, `prism_checkpoint`, and `prism_wrap` all skip the
  SPEC-037 piggyback drain. The principle "session lifecycle verbs do
  not multiplex with messaging" applies uniformly. Cost: signals queued
  during a session may not deliver before wrap; mitigated by SPEC-037's
  contract that signals persist in the queue across sessions.
* **Q2 (signal queue hygiene / TTL)**: NO EXPIRY in v0.1. Signals
  accumulate in the queue, drained by future verb calls or explicit
  `prism_signals_pending` polls. Per-type TTL is a future spec if queue
  growth becomes a concern. PAIRED with discipline note (D): `prism_wrap`
  docstring suggests `prism_signals_pending` before wrap for last-chance
  delivery.
* **Q3 (constitution rule placement)**: BOTH. ADR records the *why*
  (rationale, alternatives considered, race-correctness analysis) — see
  **ADR-25**. PRISM.md gets a one-line rule pointing at the ADR for
  agents to enforce at runtime.
