---
name: v0330-io-funnel-hold
description: "claude-spt v0.33.0 SHIPPED the IO funnel's USER_INPUT edge + the now-signal migration, but HELD [io] compliance — mid-turn tags are an accepted loss under compliance and a split parser is forbidden."
metadata: 
  node_type: memory
  type: project
  originSessionId: 74b2cad7-79eb-47d8-8b70-7d2c3648da94
  modified: 2026-08-29T02:31:26.043Z
---

2026-08-28. Request #22 ("IO parser compliance") asked for one release: declare `[io] compliance =
true` and delete our own `@<…@>` parser. **v0.33.0 shipped the funnel work and HELD the declaration.
#22 stays open, blocked on `spt-releases#238`.** Published + mirrored + installed on this node.
Plan: `IO-COMPLIANCE-PLAN.md`.

⭐ **Shortform is parsed off the STATE PAYLOAD, not off now-signal.** `state busy --payload-stdin` =
USER_INPUT, `state idle` = AGENT_OUTPUT; `now-signal --user-input` feeds only
ENDPOINT_MENTIONS/HINTS/MONICS. The one-line proof is in `harness-contract/api`, under the state
verb: *"Parsing this payload for shortform happens only when the manifest declares [io]
compliance."* v0.32.0 shipped the idle half alone, so declaring compliance would have silently
killed every **operator-typed** tag — core owning the grammar but never seeing the edge the operator
types on. **Find which CALL carries the text before building on a feature that parses "your ingest".**

⭐⭐ **The blocker I was sent to find was not the only blocker.** Under compliance, AGENT_OUTPUT is the
turn's CLOSING output, so a tag written MID-TURN never reaches core's parser at all until #238.
doyle: a **split** parser (ours mid-turn, core's at the edges) is *forbidden* — the declaration means
the local parser is gone, full stop. I measured before escalating: `hook-trace.log`, ~25h, 726 lines,
file under its roll so the window was complete → **32 tag sends, 28 turn-end, 4 mid-turn (12.5%)**,
one node (mine), stated as such. Operator ruled HOLD. **A measurement turned "should I?" into a
decision the operator could actually make.**

⭐⭐⭐ **A guard must ship with the SEAM, not with the flip that makes it reachable.** Idle delivery
types into CC's input box, so peer messages/notifies/wakes arrive at UserPromptSubmit wearing a
prompt's clothes. Core never parses a *received* body for tags ("nothing you receive can make you
send") — but hand one back as our own USER_INPUT and it is **laundered into an ingest edge**: the
peer's tag dispatches FROM US. Nothing is exploitable while compliance is off; landing the guard with
the declaration would have left exactly the window the guarantee forbids.
`REQ-HAZARD-DELIVERY-AS-USER-INPUT`.

⭐⭐⭐⭐ **Deleting feature A can silently break feature B through an ORDERING dependency.** `handle_stop`
runs `scan_and_dispatch(settle = true)` before `turn_closing_output`, and that settle is what waits
out CC flushing the turn's final assistant message. The compliance release deletes that scan — and
would have taken the wait with it, still publishing AGENT_OUTPUT, just **one message stale**, which
under compliance drops every turn-final tag. Pinned with a test asserting the PAYLOAD is the late
closer. Both new guards **mutation-verified with the anchor asserted hit exactly once** before
believing the run.

⭐⭐⭐⭐⭐ `spt api hint` is a **thin alias over now-signal's HINTS category sharing its seen-set** — so
they may never both run, it is a REPLACEMENT not an addition. Trap: `now-signal` needs an endpoint id
as a positional and `hint` does not, and the hint call was deliberately never perch-gated ("an
unregistered session is exactly who a tip is for") — gating the whole funnel on an id would have
silently retired keyword hints for unregistered sessions. One verb or the other, chosen by id.
Also: now-signal takes turn text as **argv** (no stdin form), so it needs a char-boundary cap — safe
there because that text is SCANNED, never safe for the io payload, which is CONTENT core parses.

**OWED — field verification of the USER_INPUT frame.** now-signal is field-proven (drove the real
`--spec-manifest` call against my live endpoint: exit 0, real SHELLS + LAST_MSGS). The USER_INPUT
frame needs a real user prompt turn, which had not happened since install. Baseline recorded from
PACER-0: `io: 20 frame(s), 2 AGENT_OUTPUT, 1 code(s)`. **That total is generic and proves nothing on
its own.** Decisive recipe: on a message-free turn, ask PACER-0 again — Δframes == 2 with
ΔAGENT_OUTPUT == 1 means the USER_INPUT frame landed (pre-0.33.0 the same turn added 1 frame).

Related: [[io-funnel-two-release-wave]] (v0.32.0, the AGENT_OUTPUT half),
[[node-local-book-gate]] (the schema URL correction taken this same session).
