What is broken
"Not decision-eligible" means two different things in this specification, and subagentStop carries a different label in its schema than it does in the hook catalogue.
hooks.md uses the phrase for postCompact and then permits a disposition in the same sentence:
Decision: Not decision-eligible — compaction has already occurred. A Guardian MAY return MODIFY (rewrite the summary, e.g. to redact a region the policy can't compact), but MAY NOT return DENY.
It uses the same phrase for turnEnd, where nothing is permitted:
Decision: Not decision-eligible — the turn has already happened.
So the phrase covers both "MODIFY is available, DENY is not" and "no disposition is available." Nothing distinguishes the two readings.
subagentStop then gets one label in each place. Its schema says:
Not decision-eligible — the subagent has already terminated.
hooks.md says:
Decision: Audit only.
Given the postCompact precedent, those are not the same statement. "Not decision-eligible" demonstrably permits MODIFY. "Audit only" does not. Two normative surfaces describe the same hook and a reader cannot tell which governs.
Where
specification/v0.1.0/hooks/subagent-stop.json, docs/spec/instrument/hooks.md, docs/spec/conformance.md
What the specification says, and what happens instead
The label is doing real work, because §6.5 now builds machinery that keys off it.
§6.5 requires a Guardian facing a MODIFY-incapable client to substitute DENY, and then carves out a named exception for the case where DENY is not legal:
Exception — postCompact. postCompact fires after compaction has occurred and DENY is not a legal disposition there, so the substitution above has no legal answer at that hook. For a MODIFY-incapable client the Guardian MUST return ALLOW at postCompact and MUST record an audit event with reason_codes: ["modify_unsupported"] naming the rewrite it could not deliver, so the unmodified summary standing is visible rather than silent.
That exception describes a class: content has already been produced, DENY is meaningless because there is nothing left to stop, MODIFY is the only gate that does anything, and a client that cannot apply it substitutes ALLOW and audits the gap. The class currently has one member.
subagentStop fits the same description in every respect. Both hooks define summary with required: ["value", "provenance"]. Both carry content produced by a process that has finished into a context another process will act on. Both state their rationale the same way, that compaction has already happened and that the subagent has already terminated. postCompact treats that rationale as ruling out DENY alone. subagentStop inherits a label that, read through hooks.md, rules out everything.
The question this issue asks is which of those two outcomes was intended for subagentStop, because the artifacts disagree and the overloaded phrase makes the disagreement easy to miss.
Impact on implementers
A Guardian implementer reading the schema and a Guardian implementer reading hooks.md build different things, and both can defend their reading. That is a conformance-testability problem before it is a security problem, since no test can be written against a requirement two normative documents state differently.
If "Audit only" is the intended answer, the security consequence should be recorded rather than left implicit. A Guardian that enforces output redaction at toolCallResult has no equivalent control on subagent output, so content the redaction gate would catch reaches the parent intact when it arrives through a subagent instead. That is reachable by anyone who can influence what a subagent retrieves, and it does not require compromising the subagent, only feeding it. The monotonicity rule that postCompact states normatively, that no amount of LLM processing launders untrusted-classified data into trusted-classified data, has no attachment point on the subagent return path.
If "Not decision-eligible" in the postCompact sense is the intended answer, then subagentStop belongs in the §6.5 exception alongside postCompact and the fix is small: MODIFY available, DENY not, and a MODIFY-incapable client returns ALLOW with the same audit event. Nothing new goes on the wire.
Proposed, in the order that keeps the two questions separate:
- Define "Not decision-eligible" once, or stop using it for two different rules. The cleanest version states eligible dispositions per hook rather than naming a category, which also removes the schema and
hooks.md disagreement by construction. Tracked more broadly in the eligibility-declaration issue, since six hooks constrain eligibility today across five different phrasings.
- Then settle
subagentStop on its merits rather than on an inherited label.
The counterargument to gating, stated so a reviewer does not have to raise it: a MODIFY gate on subagent return adds a round trip on a hot path in coding-agent workloads, paid per subagent return by exactly the deployments that fan out most. That cost is the reason to keep DENY unavailable and to leave MODIFY optional under the SHOULD-support level, not a reason to leave the hook unlabelled.
Written against the tree as PR #21 leaves it. #21 moves final_chain_hash out of subagent-stop.json's required set and adds §6.5 with the postCompact exception quoted above. It does not touch the "Not decision-eligible" sentence, so every quotation here survives that merge. Related: #132 tracks the floor decision #21 implements, and #131 wires subagentStop emission in the adapters, which is the producing side rather than this question.
Current Priority Scope
Feeds conformance evidence
What is broken
"Not decision-eligible" means two different things in this specification, and
subagentStopcarries a different label in its schema than it does in the hook catalogue.hooks.mduses the phrase forpostCompactand then permits a disposition in the same sentence:It uses the same phrase for
turnEnd, where nothing is permitted:So the phrase covers both "MODIFY is available, DENY is not" and "no disposition is available." Nothing distinguishes the two readings.
subagentStopthen gets one label in each place. Its schema says:hooks.mdsays:Given the
postCompactprecedent, those are not the same statement. "Not decision-eligible" demonstrably permits MODIFY. "Audit only" does not. Two normative surfaces describe the same hook and a reader cannot tell which governs.Where
specification/v0.1.0/hooks/subagent-stop.json,docs/spec/instrument/hooks.md,docs/spec/conformance.mdWhat the specification says, and what happens instead
The label is doing real work, because §6.5 now builds machinery that keys off it.
§6.5 requires a Guardian facing a MODIFY-incapable client to substitute
DENY, and then carves out a named exception for the case whereDENYis not legal:That exception describes a class: content has already been produced,
DENYis meaningless because there is nothing left to stop,MODIFYis the only gate that does anything, and a client that cannot apply it substitutesALLOWand audits the gap. The class currently has one member.subagentStopfits the same description in every respect. Both hooks definesummarywithrequired: ["value", "provenance"]. Both carry content produced by a process that has finished into a context another process will act on. Both state their rationale the same way, that compaction has already happened and that the subagent has already terminated.postCompacttreats that rationale as ruling outDENYalone.subagentStopinherits a label that, read throughhooks.md, rules out everything.The question this issue asks is which of those two outcomes was intended for
subagentStop, because the artifacts disagree and the overloaded phrase makes the disagreement easy to miss.Impact on implementers
A Guardian implementer reading the schema and a Guardian implementer reading
hooks.mdbuild different things, and both can defend their reading. That is a conformance-testability problem before it is a security problem, since no test can be written against a requirement two normative documents state differently.If "Audit only" is the intended answer, the security consequence should be recorded rather than left implicit. A Guardian that enforces output redaction at
toolCallResulthas no equivalent control on subagent output, so content the redaction gate would catch reaches the parent intact when it arrives through a subagent instead. That is reachable by anyone who can influence what a subagent retrieves, and it does not require compromising the subagent, only feeding it. The monotonicity rule thatpostCompactstates normatively, that no amount of LLM processing launders untrusted-classified data into trusted-classified data, has no attachment point on the subagent return path.If "Not decision-eligible" in the
postCompactsense is the intended answer, thensubagentStopbelongs in the §6.5 exception alongsidepostCompactand the fix is small:MODIFYavailable,DENYnot, and a MODIFY-incapable client returnsALLOWwith the same audit event. Nothing new goes on the wire.Proposed, in the order that keeps the two questions separate:
hooks.mddisagreement by construction. Tracked more broadly in the eligibility-declaration issue, since six hooks constrain eligibility today across five different phrasings.subagentStopon its merits rather than on an inherited label.The counterargument to gating, stated so a reviewer does not have to raise it: a
MODIFYgate on subagent return adds a round trip on a hot path in coding-agent workloads, paid per subagent return by exactly the deployments that fan out most. That cost is the reason to keepDENYunavailable and to leaveMODIFYoptional under the SHOULD-support level, not a reason to leave the hook unlabelled.Written against the tree as PR #21 leaves it. #21 moves
final_chain_hashout ofsubagent-stop.json's required set and adds §6.5 with thepostCompactexception quoted above. It does not touch the "Not decision-eligible" sentence, so every quotation here survives that merge. Related: #132 tracks the floor decision #21 implements, and #131 wiressubagentStopemission in the adapters, which is the producing side rather than this question.Current Priority Scope
Feeds conformance evidence