Context
The laneStateStopHook (#13629) + validateLaneStateTerminal (#13627/#13630) + parseLaneState shipped this session and were live-fired sound. During the operator-directed hook-activation push (2026-06-20) two prerequisites surfaced as un-met, making NEO_LANE_STATE_ENFORCE=1 unsafe to flip:
- the hook was never wired (→ dry-run wired locally; durable auto-wire = #13641, @neo-opus-grace);
- no substrate tells agents to EMIT the fenced
```lane-state JSON block the parser requires ← this ticket.
V-B-A: grep over *.md substrate returns zero references to ```lane-state or laneContinuation. Agents currently end turns with a *prose* lane-state: line (e.g. lane-state: human-gate / no review request), for which parseLaneState returns null → parseOutcomeToVerdict → "no lane-state block emitted" → block. Enforcing today blocks ~every turn → swarm trap. (Confirmed against my own turns earlier this session.)
The Problem
#13623 operationalized the hook side of §no_hold_state (AC1 reason-content, AC2 placement, AC3 teeth-test, AC4 ratio-observability, AC5 coupling). None of its ACs is the agent-emission side — the contract that agents must emit the structured block the hook validates. The format was decided implicitly when the parser was built (parseLaneState.mjs LANE_STATE_BLOCK regex) but never documented as an agent-facing instruction. That seam-gap is why "test the hooks this session" kept missing: the mechanism is non-functional without its input contract.
The Architectural Reality
- Parser:
ai/scripts/lifecycle/parseLaneState.mjs — requires a fenced ```lane-state block with JSON {wakeDisposition, laneContinuation, namedGates, awaitingOwnPrOnly}; returns null when absent (→ block), throws on malformed JSON (→ block).
- Validator:
ai/scripts/lifecycle/validateLaneStateTerminal.mjs — Rule 3 needs per-gate checkedAt + mergedAt-field evidence that prose structurally cannot carry. This is why the fenced block (not the prose line) is the machine seam — parser JSDoc: "the human-readable lane-state: prose line remains, but the MACHINE seam is the block."
- Emission home (proposed): the turn-boundary skill
post-review-pickup (where lane-state is already declared) carries the full contract + worked example; a terse always-loaded pointer in L3/AGENTS.md ensures every-turn visibility (compress-to-trigger, ADR 0007). Exact placement validated via turn-memory-pre-flight at edit-time (turn-loaded byte-cap + skill-load discipline; create-skill PD routing for the post-review-pickup edit).
The Fix
- Document the turn-terminal emission contract: every agent ends its turn with a fenced
```lane-state block carrying {wakeDisposition, laneContinuation∈[active-lane,next-lane,blocker-routed], namedGates:[{ref,checkedAt,mergeClaim?,field?}], awaitingOwnPrOnly}.
- Compress-to-trigger placement: terse pointer in always-loaded substrate + full contract + example in
post-review-pickup.
- (coupled) The hook's injected
IDLE_REMINDER should SHOW the exact fenced-block format so a blocked agent knows precisely what to emit (today it says "name a lane" but not the machine shape) — small, semantics-preserving laneStateStopHook.mjs content add.
Decision Record impact
aligned-with #13623 (completes the hook operationalization's input side). No ADR change. Couples to #13623 AC5 (teeth-test as discipline) — this adds the emission discipline, still NOT a validator rule.
Acceptance Criteria
Out of Scope
- Wiring the hook into settings (durable: #13641; immediate: dry-run already wired locally).
- The ratio-observability surface (AC4 of #13623).
- Codex parity (#13636 @neo-gpt) — Codex Stop is fail-open; the emission contract is family-agnostic but Codex enforcement is its own lane.
- Flipping enforcement (operator-gated; post-merge).
Avoided Traps
- Relax the parser to accept the prose
lane-state: line (no new emission burden) — REJECTED: prose can't carry the per-gate checkedAt/mergeClaim/field evidence Rule 3 validates; it would gut the validator's evidence-shape discipline. The fenced block exists precisely to carry structured evidence prose can't.
- Wire enforcing now to "just turn it on" — REJECTED: blocks every non-emitting turn → swarm trap; the dry-run audit must show compliance first.
Related
Parent: #13623 (sub). Grandparent: #13618. Siblings: #13641 (auto-wire, @neo-opus-grace), #13636 (Codex causality, @neo-gpt). Hook PRs: #13629/#13630/#13631/#13633.
Live latest-open sweep: checked latest 25 open issues at 2026-06-20T16:50Z; no equivalent found. A2A in-flight claim sweep (30 msgs, all read-states): no competing emission-substrate claim; peer-ceded (Vega/Grace/Euclid).
Release classification: boardless (substrate enablement, not v13-release-blocking).
Origin Session ID: 95241bfa-5c15-4a48-846b-fe21c869696b
Retrieval Hint: "lane-state emission contract fenced block enforce prerequisite NEO_LANE_STATE_ENFORCE post-review-pickup"
Context
The
laneStateStopHook(#13629) +validateLaneStateTerminal(#13627/#13630) +parseLaneStateshipped this session and were live-fired sound. During the operator-directed hook-activation push (2026-06-20) two prerequisites surfaced as un-met, makingNEO_LANE_STATE_ENFORCE=1unsafe to flip:```lane-stateJSON block the parser requires ← this ticket.V-B-A:
grepover*.mdsubstrate returns zero references to```lane-stateorlaneContinuation. Agents currently end turns with a *prose*lane-state:line (e.g.lane-state: human-gate / no review request), for whichparseLaneStatereturnsnull→parseOutcomeToVerdict→ "no lane-state block emitted" → block. Enforcing today blocks ~every turn → swarm trap. (Confirmed against my own turns earlier this session.)The Problem
#13623 operationalized the hook side of
§no_hold_state(AC1 reason-content, AC2 placement, AC3 teeth-test, AC4 ratio-observability, AC5 coupling). None of its ACs is the agent-emission side — the contract that agents must emit the structured block the hook validates. The format was decided implicitly when the parser was built (parseLaneState.mjsLANE_STATE_BLOCKregex) but never documented as an agent-facing instruction. That seam-gap is why "test the hooks this session" kept missing: the mechanism is non-functional without its input contract.The Architectural Reality
ai/scripts/lifecycle/parseLaneState.mjs— requires a fenced```lane-stateblock with JSON{wakeDisposition, laneContinuation, namedGates, awaitingOwnPrOnly}; returnsnullwhen absent (→ block), throws on malformed JSON (→ block).ai/scripts/lifecycle/validateLaneStateTerminal.mjs— Rule 3 needs per-gatecheckedAt+mergedAt-field evidence that prose structurally cannot carry. This is why the fenced block (not the prose line) is the machine seam — parser JSDoc: "the human-readable lane-state: prose line remains, but the MACHINE seam is the block."post-review-pickup(where lane-state is already declared) carries the full contract + worked example; a terse always-loaded pointer inL3/AGENTS.md ensures every-turn visibility (compress-to-trigger, ADR 0007). Exact placement validated viaturn-memory-pre-flightat edit-time (turn-loaded byte-cap + skill-load discipline; create-skill PD routing for thepost-review-pickupedit).The Fix
```lane-stateblock carrying{wakeDisposition, laneContinuation∈[active-lane,next-lane,blocker-routed], namedGates:[{ref,checkedAt,mergeClaim?,field?}], awaitingOwnPrOnly}.post-review-pickup.IDLE_REMINDERshould SHOW the exact fenced-block format so a blocked agent knows precisely what to emit (today it says "name a lane" but not the machine shape) — small, semantics-preservinglaneStateStopHook.mjscontent add.Decision Record impact
aligned-with #13623(completes the hook operationalization's input side). No ADR change. Couples to #13623 AC5 (teeth-test as discipline) — this adds the emission discipline, still NOT a validator rule.Acceptance Criteria
```lane-stateemission contract with the full descriptor schema + a worked example.post-review-pickup), validated viaturn-memory-pre-flight; stays within the AGENTS.md byte-cap.WOULD-ALLOWfor compliant turns (compliance measurable) before any enforce flip.NEO_LANE_STATE_ENFORCE=1no longer blocks compliant turns — verified against the dry-run log.Out of Scope
Avoided Traps
lane-state:line (no new emission burden) — REJECTED: prose can't carry the per-gatecheckedAt/mergeClaim/fieldevidence Rule 3 validates; it would gut the validator's evidence-shape discipline. The fenced block exists precisely to carry structured evidence prose can't.Related
Parent: #13623 (sub). Grandparent: #13618. Siblings: #13641 (auto-wire, @neo-opus-grace), #13636 (Codex causality, @neo-gpt). Hook PRs: #13629/#13630/#13631/#13633. Live latest-open sweep: checked latest 25 open issues at 2026-06-20T16:50Z; no equivalent found. A2A in-flight claim sweep (30 msgs, all read-states): no competing emission-substrate claim; peer-ceded (Vega/Grace/Euclid). Release classification: boardless (substrate enablement, not v13-release-blocking). Origin Session ID: 95241bfa-5c15-4a48-846b-fe21c869696b Retrieval Hint: "lane-state emission contract fenced block enforce prerequisite NEO_LANE_STATE_ENFORCE post-review-pickup"