---
name: event-envelope-reply-to-removal
description: "ADR-0020: <EVENT> envelope = sole arriving-message format on agent perches (shell-link relay EXEMPT); __REPLY_TO__ removed. SHIPPED in v0.7.1 (counter 13) 2026-06-15 + VERIFIED on the published surface by perri's byte-capture. Loop CLOSED end-to-end; F-002 resolved-shipped, F-003 docs-closed"
metadata: 
  node_type: memory
  type: project
  originSessionId: ce7ce158-107f-4da4-a203-cea98da80b27
---

**ADR-0020 (2026-06-15, crystallized via /grill-with-docs, operator-ruled).** Triggered by
perri's finding **F-002** (`api poll` multi-message output non-self-delimiting). Operator clarified the
design intent → grilled against CONTEXT/ADRs → these LOCKED decisions:

- **The `<EVENT type="msg" from="…">body</EVENT>` envelope (spt-proto::event, the ADR-0001 grammar) is
  the SOLE canonical arriving-message format at EVERY delivery surface — `api listen`/`spt ready` AND
  `api poll`/`worker-poll`, byte-identical.** Adapters parse the `<EVENT>` envelope (the `from` attr).
  Reverses REQ-MSG-4's "hook drains keep the raw frame by contract."
- **`__REPLY_TO__` was MIS-ELEVATED** during the clean-room port to a fake ADR-0001 "stable wire
  format" (spt-msg/wire.rs, lib.rs). It is REMOVED entirely: spool format_row, the spt-msg TCP frame,
  emit::parse_frame. `(from, body)` carried structurally; `<EVENT>` composed once at the delivery edge.
- **No legacy sister-interop** — spt-core never required byte-compat with legacy spt; the wire.rs
  "byte-for-byte with a sister" claim was part of the mis-elevation. (Operator confirmed.)
- **Reply-correlation rebinds onto the structural `from` / `<EVENT from=…>`** — ADR-0009 access-gate
  (reply-vs-new) + ADR-0012 Psyche/spt-live reply-target. Both ADRs amended (dated notes). Sender-
  identity granularity preserved (the relic carried the same `from`); no new correlation attribute.
- **F-002 dissolves** — `<EVENT>…</EVENT>` self-delimits; no record delimiter introduced.
- WAN wire (spt-net/wanmsg.rs) UNAFFECTED — `from` already structural (JSON field).

**DOCS DONE:** ADR-0020 + ADR-0009/0012 amendments + CONTEXT.md §Messaging-substrate glossary entry
("arriving-message envelope") + REQ-MSG-ENVELOPE registered (required_stages=["doc"], doc evidenced;
traceable 208/208 EXIT=0).

**IMPL DISPATCHED TO TODLANDO 2026-06-15 (spt-core executor; doyle gates — NOT doyle's to execute, NOT perri's).** Plan = `REQ-MSG-ENVELOPE-PLAN.md` (7 tasks, 3 open impl-decisions: TCP-wire shape, drain API, anon from). todlando JIT-plans → design-check to doyle → builds → doyle gates. Multi-crate scope (activate impl/unit/int when it lands): spt-store
(spool format_row), spt-msg (wire.rs delete, emit parse_frame, deliver/listener/ring/ready, lib.rs
ADR-0001 framing), spt (api poll/worker-poll delivery.rs::emit → compose `<EVENT>`, cli --from help),
spt-daemon (psyrelay/notif/access/shellchan reply-target), spt-live (outbound), KNOWN-HAZARDS Psyche
invariant. Until it lands, `api poll` STILL emits the raw `__REPLY_TO__` relic on the public binary.

**IMPL BUILT + COMMITTED 2026-06-15 @ `5da7fcd`** (branch docs/msg-envelope-adr-0020, stacked on docs `24e0628`; both off main). doyle blessed the 3 decisions (TCP wire = u32 len(from)+from+body structural inner-frame; DrainedMsg{from,body} struct; anon from="" preserved) + 2 re-emphases (adversarial typed-passthrough no-double-wrap; grep-clean incl test fixtures). All 7 tasks done across 27 files: spool DrainedMsg (format_row deleted), wire encode/decode_frame (parse_frame deleted), emit render_event_whole (hook drains, no chunk) + render_event_lines (listener, chunks), api poll/worker compose WHOLE <EVENT>, daemon/live/net reply-target on structural `from`. Self-verify GREEN: 1053 nextest + doctests + clippy -D + traceable EXIT=0 (REQ-MSG-ENVELOPE +doc+impl+unit+int) + grep-clean ZERO __REPLY_TO__ in crates/ + new poll-whole-self-delimiting int + xtask OK. **doyle GATE-PASS 2026-06-15** (independent repro from 5da7fcd: 1054 nextest, traceable 208/208 EXIT=0, reviewed structural frame/DrainedMsg/render_event_whole/adversarial no-double-wrap/psyrelay anti-spoof — all sound). **PR #12 CI then RED on my new poll int** — FALSE-GREEN bug: `spt api poll` is AUTH-GATED (REQ-HAZARD-LOCAL-API-AUTH; needs session_id matching info.json or --token); my E2E pinned neither → passed only where ambient $OWL_SESSION_ID set (dev box), AUTH_REFUSED on clean CI (random sid). FIX @ `ac6c802`: pin const SID on BOTH `ready --once` + the `poll` via .env(OWL_SESSION_ID) + .env_remove(SPT_API_TOKEN); verified with both vars UNSET locally (CI-clean repro). LESSON: any E2E spawning `api poll`/auth-gated machinery must pin OWL_SESSION_ID hermetically — never lean on ambient env (false green); re-verify with the env UNSET. doyle's local gate missed it (his env was set) — gate-repro must clear ambient env too. **PR #12 RED a 2nd time (different test): `notify_shell_e2e` — my cmd_poll_shell over-applied `<EVENT>` to the SHELL-LINK relay (`api poll <shell-id> --link`). Shell frames are RAW MAC'd stamped payloads (spooled anon, from="") the shell child consumes verbatim — NOT a harness `<EVENT>` reader; wrapping broke MAC/parse → never rendered.** FIX @ `caaf797`: cmd_poll_shell raw passthrough (`println!("{}", m.body)`); agent-perch poll/worker/listen keep `<EVENT>`. SCOPE CARVE-OUT documented 4 places (comment at cmd_poll_shell + ADR-0020 clause + REQ-MSG-ENVELOPE title + CONTEXT:685): `<EVENT>`-everywhere = harness ARRIVING-MESSAGE surfaces on AGENT perches only; shell-command channel = distinct internal transport, EXEMPT; notify_shell_e2e = named guard. **2nd LESSON: `cargo nextest run --workspace` SILENTLY SKIPS env-gated E2Es** (notify_shell_e2e self-skips when SPT_NOTIFY_SHELL_BIN unset; the CI `test` job = plain `cargo test -p spt --test notify_shell_e2e` runs it for real). Pre-push sweep MUST build the adapter (`projects/spt-shell-notify` → `cargo build --release` → target/release/notify-shell.exe) + set SPT_NOTIFY_SHELL_BIN + run the cargo-test-only E2Es; nextest-green ≠ CI-green.

**3rd RED: Windows docs-drift gate exe-lock** (`xtask check` → "failed to remove target\debug\spt.exe", os-error-5). A test-spawned detached daemon (REQ-DAEMON-3 autostart) holds spt.exe; Windows can't overwrite a running exe (Linux can → Linux passed). LATENT on ANY cli.rs-touching PR (doyle's PR #13 hit it too with NO new E2Es — the docs-drift xtask rebuild collides; main never rebuilt there so it stayed hidden). FIX @ `f747e1e`: a Windows-only reap step BEFORE the docs-drift gate, **$GITHUB_WORKSPACE-path-scoped** (see [[no-machinewide-killon-shared-runner]] — NEVER machine-wide on the shared self-hosted runner). Guard rode PR #12 to main → fixes it fleet-wide.

**ALL MERGED to main @ `25ffd7a` (PR #12, 2026-06-15):** 5da7fcd impl + d2433d4 doyle-T6-docs + ac6c802 poll-hermetic + caaf797 shell-link-exempt + f747e1e CI-guard. doyle pinged perri (byte-capture waits for the operator-gated next release). doyle next: rebase PR #13 (update-apply [[update-apply-confident-message]] + docs incl F-003 strings-pointer) onto main → merge → release. **doyle found T6 doc-drift = MY MISS** (I built T0–T5+T7 code/tests, skipped T6's doc sweep: KNOWN-HAZARDS:348 Psyche-routing 7.x + FAULT-MATRIX:42 + docs-site still call __REPLY_TO__ the live wire header); doyle clearing it himself (docs+impl one PR), I stayed hands-off to avoid collision. NEXT: doyle merges branch→main → ping perri (poll byte-capture confirm) on publish. SHARED-WORKTREE LESSON: doyle saw my mid-flight uncommitted edits on the shared docs branch + tried to build (RED) — coordinate hands-off + gate-from-commit when both agents touch one tree.

**SHIPPED + VERIFIED — LOOP CLOSED 2026-06-15.** PR #13 ([[update-apply-confident-message]] + the F-003 [strings]-pointer/{key}-catalog/[digest] docs) rebased onto main (inherited the f747e1e guard) → green → merged @ `30d2a92`. Released **v0.7.1 (patch, counter 13)**: doyle gated CI-green pre-tag on cb05600, todlando tagged, deployah signed+published (transient TLS blip on draft-create → rerun --failed, no re-tag). Hashes linux `81eededc…`, win `94bb5429…` — see [[v071-published]]. **perri's REQ-MSG-ENVELOPE byte-capture on the LIVE v0.7.1 binary = CONFIRM-MATCH** (od-verified): `<EVENT type="msg" from=…>body</EVENT>\n` per msg, body escaping `<br>`+entities+`&amp;`-LAST exact, WHOLE self-delimiting envelopes, NO `<EVENT-PART>` on normal hook drains, NO `__REPLY_TO__`; raw drain → her render_frames → correct `<sptc_messages>` (parser confirm-match PASS); notify rides the same envelope. **F-002 DISSOLVED on the published surface, F-003 docs-residual CLOSED.** perri flipping REQ-DIST-HOOKS-API + REQ-UPS-INJECTION int + committing the SPTC_ACCEPTANCE-gated poll-int guard. Design→impl→gate→ship→real-surface-verify, end to end. Don't re-plan.

**DOWNSTREAM (perri/sptc):** parse the `<EVENT>` envelope, NOT `__REPLY_TO__` (told perri; their
838fff8 render_frames retargets parse side to `<EVENT>`, `from`→`<sptc_messages from=>` unchanged). Build
against canonical `<EVENT>`; don't validate vs poll's CURRENT relic output — **doyle/todlando ping perri when this MERGES to main + publishes** (perri owes a byte-capture confirm of poll output then). See [[spt-core-harness-boundary-and-grounding]], [[spt-claude-code-next]].
