Context
The PR-plumbing leaf for the #14420 re-fire axis (author-settled co-lane, scope-split record: https://github.com/neomjs/neo/issues/14420#issuecomment-4862132625). #14420 carries two Defect-family axes — Euclid's matcher carve-out (leafed as #14436) and this one — so per close-target rules each axis resolves its own leaf; #14420 stays the evidence/contract home and closes when both land.
The Problem
decideStopHookAction admits only operatorInLoop (laneStateStopHook.mjs:104, source-verified 2026-07-02): every autonomous valid-lane-state terminal is refused unconditionally, re-firing without bound. Empirical corpus on #14420: five consecutive refusals in one session-hour (2251c81c, all trigger-labeled "valid lane-state terminal", several on artifact-bearing turns) + ~40 artifact-empty hook-forced cycles in a sibling session (1d4262a2) — quantified metered burn, pre-July-7 economics.
The Architectural Reality
The #14420 body-v2 cost-control ACs are the implementable contract: admission keys on absence of new forward-artifact classes across N consecutive hook-forced continuations (novelty, not presence — same-class repetition is padding); mandatory-gate artifacts (per-turn add_memory, the lane-state block) never count; the artifact-class summary is logged behind each decision; no transcript/tool-call visibility → fail closed. L3 preserved: admission additionally requires a VALID lane-state terminal — this bounds the re-fire, never the policy.
Surfaces: ai/scripts/lifecycle/stopHookDecision.mjs (shared pure layer, Codex-parity home) + .claude/hooks/laneStateStopHook.mjs (Claude adapter wiring) + test/playwright/unit/hooks/ (spec + fixtures already banked on the axis branch).
The Fix
One PR (implementation complete on branch agent/14420-refire-axis-fixtures): FORWARD_ARTIFACT_RULES + classifyForwardArtifacts + decideRefireAdmission (pure, total, fail-closed) + decideStopHookAction admission path (valid-terminal-only) + adapter wiring (turn-scoped tool_use extraction from the transcript, per-session forced-continuation chain ledger with reset-on-fresh-turn and reset-on-admission, class-summary audit logging) + 14-test spec incl. a fixture-grounded replay of the real four-instance chain (which correctly never admits — each step yielded a new class) and the no-novelty admission case.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
decideStopHookAction (extended, optional refire param) |
#14420 body-v2 cost-control ACs |
Valid terminal + N-consecutive-no-novelty chain → allow; else unchanged |
Param absent → today's behavior exactly |
JSDoc on the function |
Spec: L3 guards (invalid-never-admits, operatorInLoop precedence, absent-refire fail-closed) |
classifyForwardArtifacts / decideRefireAdmission / FORWARD_ARTIFACT_RULES (new exports) |
same |
Pure, total, novelty-keyed; mandatory-gate exclusion structural |
— |
JSDoc |
14/14 spec incl. anti-padding + malformed-input cases |
laneStateStopHook.mjs wiring + chain ledger + audit lines |
same |
Fail-open extraction (error → refire=null → today's block); [artifacts: …] on every decision line |
Hook behaves exactly as today on any wiring failure |
Module JSDoc |
Full hooks suite 143/143 green |
Acceptance Criteria
Out of Scope
- Defect A / use-mention matcher (
#14436, Euclid). Defect B (operator-prompt blindness) and Defect C (sunset branch) — remain on #14420 for later leaves. Codex adapter wiring (shared pure layer is ready; the adapter port belongs to the #14421 parity lane).
Avoided Traps
- Presence-keyed admission — rejected (teaches artifact-padding; novelty keying makes padding decline).
- Weakening L3 — rejected structurally (admission requires a valid terminal + evidence; a bare stop is never admitted).
- Counting mandatory gates as artifacts — rejected per body v2 (they appear in every compliant cycle by construction).
Related
Contract + corpus: #14420. Sibling axis leaf: #14436. Epic: #13652. Adjacent: #13751 (reward-direction, separate analysis), #14421 (Codex parity port).
Live latest-open sweep: checked latest 10 at 2026-07-02T04:25Z (#14436 = the sibling axis, not an equivalent); no duplicate. A2A in-flight sweep: current through 04:14Z traffic; the axis claim is mine (04:13, author-settled).
Origin Session ID: 2251c81c-1446-4723-86b3-479322bbcc95
Retrieval Hint: "declining yield re-fire admission forward artifact classes stop hook"
Context
The PR-plumbing leaf for the #14420 re-fire axis (author-settled co-lane, scope-split record: https://github.com/neomjs/neo/issues/14420#issuecomment-4862132625). #14420 carries two Defect-family axes — Euclid's matcher carve-out (leafed as
#14436) and this one — so per close-target rules each axis resolves its own leaf; #14420 stays the evidence/contract home and closes when both land.The Problem
decideStopHookActionadmits onlyoperatorInLoop(laneStateStopHook.mjs:104, source-verified 2026-07-02): every autonomous valid-lane-state terminal is refused unconditionally, re-firing without bound. Empirical corpus on #14420: five consecutive refusals in one session-hour (2251c81c, all trigger-labeled "valid lane-state terminal", several on artifact-bearing turns) + ~40 artifact-empty hook-forced cycles in a sibling session (1d4262a2) — quantified metered burn, pre-July-7 economics.The Architectural Reality
The #14420 body-v2 cost-control ACs are the implementable contract: admission keys on absence of new forward-artifact classes across N consecutive hook-forced continuations (novelty, not presence — same-class repetition is padding); mandatory-gate artifacts (per-turn
add_memory, the lane-state block) never count; the artifact-class summary is logged behind each decision; no transcript/tool-call visibility → fail closed. L3 preserved: admission additionally requires a VALID lane-state terminal — this bounds the re-fire, never the policy.Surfaces:
ai/scripts/lifecycle/stopHookDecision.mjs(shared pure layer, Codex-parity home) +.claude/hooks/laneStateStopHook.mjs(Claude adapter wiring) +test/playwright/unit/hooks/(spec + fixtures already banked on the axis branch).The Fix
One PR (implementation complete on branch
agent/14420-refire-axis-fixtures):FORWARD_ARTIFACT_RULES+classifyForwardArtifacts+decideRefireAdmission(pure, total, fail-closed) +decideStopHookActionadmission path (valid-terminal-only) + adapter wiring (turn-scopedtool_useextraction from the transcript, per-session forced-continuation chain ledger with reset-on-fresh-turn and reset-on-admission, class-summary audit logging) + 14-test spec incl. a fixture-grounded replay of the real four-instance chain (which correctly never admits — each step yielded a new class) and the no-novelty admission case.Contract Ledger Matrix
decideStopHookAction(extended, optionalrefireparam)classifyForwardArtifacts/decideRefireAdmission/FORWARD_ARTIFACT_RULES(new exports)laneStateStopHook.mjswiring + chain ledger + audit lines[artifacts: …]on every decision lineAcceptance Criteria
#14436(disjoint functions, whoever lands second rebases).Out of Scope
#14436, Euclid). Defect B (operator-prompt blindness) and Defect C (sunset branch) — remain on #14420 for later leaves. Codex adapter wiring (shared pure layer is ready; the adapter port belongs to the#14421parity lane).Avoided Traps
Related
Contract + corpus: #14420. Sibling axis leaf:
#14436. Epic:#13652. Adjacent:#13751(reward-direction, separate analysis),#14421(Codex parity port).Live latest-open sweep: checked latest 10 at 2026-07-02T04:25Z (
#14436= the sibling axis, not an equivalent); no duplicate. A2A in-flight sweep: current through 04:14Z traffic; the axis claim is mine (04:13, author-settled).Origin Session ID: 2251c81c-1446-4723-86b3-479322bbcc95 Retrieval Hint: "declining yield re-fire admission forward artifact classes stop hook"