Skip to content

v1.8.0

Latest

Choose a tag to compare

@nmiller0113 nmiller0113 released this 09 Sep 21:22

A dispatched subagent must now declare, positively, the one tree it is allowed to read.
PreToolUse on the Agent tool refuses any dispatch whose prompt does not carry an
ALLOWED-SCOPE: line naming an absolute path that exists, rejects catch-alls such as the
filesystem root or the home directory, and requires the literal words IMPLICIT DENY so the
agent is actually told the rule rather than the dispatcher merely satisfying a regex.

This exists because the obvious way to fence an agent is a denylist -- do not touch that host,
that port, that service -- and a denylist fails open. A reviewer dispatched for a one-file
change followed an entirely reasonable chain of reasoning out of the repository it was given
and into unrelated infrastructure configuration, because that tree had never been named and so
was permitted by default. The wandering was not irrational. That is the point: nothing inside
the agent was ever going to stop it, and every tree the dispatcher failed to imagine was
allowed. You cannot secure a boundary by listing what sits on the far side of it.

The honest limit, stated in the hook itself: this cannot force a running subagent to honour its
fence. It guarantees the fence is stated, specific and real. An unstated boundary constrains
nothing, while a stated one at least travels into the agent's own context, which is the only
place it can do any work.

The same hook also bounds a review dispatch. The skill already says a review "asks two
questions and no others" and that a prompt naming further places to look "commissions findings
instead of checking work". That rule was prose, applied by judgement, and judgement is what
fails: whoever built the change writes the review prompt while still holding the build in their
head and enumerates everything they were worried about. Each worry is a commission, the reviewer
answers all of them, and the size of the finding list ends up set by the prompt rather than by the
code. A dispatch that reads as a review must now declare REVIEW-APERTURE: and may enumerate at
most two check directives, which is the number of questions a review gets to ask.

That second check is a heuristic backstop and says so in the source: it counts enumerated
directives, cannot read intent, and a worry list written as flowing prose walks past it. It is a
tripwire on the shape that actually recurs, not a proof. It is deliberately not a denylist of
suspicious phrasings, because a denylist fails open on everything nobody thought of, which is the
defect the scope fence exists to close.

A dispatch counts as a review when it asks for a verdict or declares an aperture, and not when
it merely contains the word. Substring matching was tried and was wrong in the obvious way:
"REWORK" sits inside "fireworks", and "rework the parser" is a perfectly good build instruction
that was being refused with a message about reviews. A false positive here is worse than a miss,
because a miss leaves a dispatch exactly as good as it was before this hook existed while a false
positive breaks a working one.

Install note: the dispatch gate needs Python 3, where the reminder hook needs only a POSIX shell.
Its command string probes for it and exits 0 when it is absent, so on a machine without Python 3
the gate is inert and silent. That is a guardrail getting out of the way rather than blocking
every agent on a machine it cannot run on, and the README now says so plainly.

Nothing changes for an existing dispatch that was already scoped, or for an agent that is not a
review. The skill text is untouched.