feat(ways): standing delegation authorization for the AgentTool gate (ADR-175) - #432
Conversation
…(ADR-175)
Claude Code 2.1.219+ injects a system-prompt constant on Opus 5 gating
AgentTool, workflows, and deep-research behind "unless the user requested
it". There is no opt-out: CLAUDE_INTERNAL_FC_OVERRIDES is dead code in
2.1.220, and the tengu_fennel_godwit killswitch is all-or-nothing across a
capability that also carries delivering_work_max and overcorrection.
Answer the gate on its own terms rather than overriding it. It asks a
permission question whose condition the operator can satisfy in advance.
Two tiers:
- Doctrine in meta/subagents — invocation is authorization, plus a
payoff clause answering the separately-gated counter_steer nudge.
- Operative clause inline at each site that actually directs a
delegation: skills/merge review gate, delivery/merge four-square,
research Gather fan-out.
Site-level placement rather than widening subagents.md's pattern: the
tokens that would reach "let's merge this" are common-word alternations
that ADR-155 section 5 exists to veto, and a skill is read at invocation
so the clause is present with no dependence on a way firing.
Scope is AgentTool only. Workflows and deep-research stay gated.
Also corrects subagents.md documenting the tool as Task; the model-facing
name is Agent. The Task matchers in settings.json are the harness event
namespace and are deliberately left alone.
Peer review on #432 caught /develop named in the authorization list while carrying no tier-2 clause. It spawns nothing — its own body calls it "a router, not an orchestrator" — but it holds Agent in allowed-tools, so a blanket grant would read as licence to spawn agents for stage work and contradict the skill's framing. Drop it from the list, and state the invariant that made the mistake possible: tier 1 names only procedures carrying a clause at their own dispatch site, and a router that sequences skills grants nothing. Record the drift as fail-closed in ADR-175 — a site without a clause grants nothing, so the model asks, which is the pre-change behaviour.
Code Review — ADR-175 standing delegation authorizationReviewed at Verification of the technical claims
ADR-155 §5 — half-verified, and the Alternatives entry overstates it. So ADR-175:75-76 and :139-141 claim more than the code supports. The conclusion survives on the other argument, which is the stronger one anyway: a skill is read at invocation with no dependence on the scanner, and reproducing another way's trigger surface inside Upstream reference checks out. Repro of the stated verification. Scope discipline — holdsNothing in the diff loosens workflows or deep-research. Both new mentions re-gate them explicitly ( One adjacent note, outside this PR: Blocking1.
Two ways can therefore land in one window telling the model offer and don't re-ask. Whichever is read last wins, which is the recency contest the Decision explicitly declines to enter (ADR-175:55-57). The fix is one of two edits: carry the Tier 2 clause here — on trigger-surface grounds this is arguably a better Tier 2 site than 2.
Drop it. While there: "or asks for a PR review or a research sweep" partly escapes the invariant too, since a bare "review PR #42" reaches Should fix before merge
Nice to have
What holds up
AI-assisted review via Claude |
Two blockers from the code-reviewer pass on #432. delivery/github said to "offer" a code-reviewer on the same trigger where delivery/merge now says to dispatch one — two ways, one query, opposite verbs, resolved by recency. That is the contest ADR-175 declines to enter, reintroduced by the change itself. Aligned github to dispatch, kept the merge-strategy ask distinct from it, and recorded the corollary in the ADR: a way covering a delegation moment has to be checked for the opposite verb. /code-review failed the invariant added one commit earlier. It is neither a skill nor a command in this repo, so it carries no tier-2 clause and cannot; naming it granted authority nothing backed. Replaced with the land/ship phrasing that does correspond to annotated sites. Also from the review: - Bound the grant's width. All three "still ask first" conditions keyed on being outside the procedure; none capped agent count inside one, leaving the research fan-out and the merge swarm unbounded. The grant covers whether, not how wide. - Moved the merge clause above "Running a single review:" so it covers the swarm corner rather than reading as scoped to the single dispatch. - Added the harness built-ins Explore and general-purpose to the roster. The research way now names them, and the roster listed only agents/. Corrects an overstated claim in the ADR: widening the pattern is only half blocked. `review` is in the linter's COMMON_WORDS, but `merge` is not, and delivery/merge already ships a lint-clean phrase form matching "let's merge this". Reach was not the constraint — winning the match is. meta/subagents ranked fifth on a query naming delegation outright.
Remediation — all findings closed at
|
Problem
Claude Code 2.1.219+ injects a system-prompt section (
heron_brook) on Opus 5 carrying asingle constant, emitted as one block:
Gate: the
opus_5_prompt_bundlemodel capability plus killswitchtengu_fennel_godwit,currently
false. Confirmed live from inside two sessions. Upstream:anthropics/claude-code#80988,
open, no staff reply.
No opt-out exists.
CLAUDE_INTERNAL_FC_OVERRIDESis dead code in 2.1.220 (unconditionalreturnbefore the env read), andDISABLE_GROWTHBOOK=1makes it worse since thekillswitch defaults
false. Flipping the killswitch is all-or-nothing: the same capabilitygates five other sections including
delivering_work_maxandovercorrection.The constant carves out no exception by agent type, so
code-revieweris suppressedexactly like
Explore. The operator has to name delegation explicitly every time —including for procedures whose own steps call for it.
Approach
Evidence, not override. The gate asks a permission question, and its condition is
satisfiable in advance and in writing by the operator. An "ignore the above" countermand
fails on its own terms and wins only on recency, since way text and
CLAUDE.mdshare theoperator's authorship.
A second gate (
subagent_steer_delegation, armcounter_steer) asks a cost/benefitquestion instead. It is answered by naming the payoff, not by overriding.
Changes
Tier 1 — doctrine in
hooks/ways/meta/subagents/subagents.md: invocation isauthorization, plus a payoff clause for the cost/benefit gate.
Tier 2 — operative clause inline at each site that actually directs a delegation:
skills/merge/SKILL.mdAgent(...)dispatchhooks/ways/softwaredev/delivery/merge/merge.mdhooks/ways/research/research.mdSite-level rather than widening
subagents.md'spattern. Reaching "let's merge this"by regex needs common-word alternations (
merge,review) — exactly the noise ADR-155 §5exists to veto. A skill is read at invocation, so the clause is present whenever delegation
is about to happen with no dependence on a way firing.
delivery/branchingwas considered and declined: it scores high on merge-shaped queries butdirects no delegation, so placing an authorization there is trigger-chasing under a
different filename.
Scope
AgentToolonly. Workflows and deep-research are the second line of the same constantand stay gated — proposed and discussed, never invoked unprompted. Authorization is also
conditional: ad-hoc delegation outside an invoked procedure still asks first.
Also
subagents.mddocumented the tool asTask; the model-facing name isAgent. TheTaskmatchers in
settings.jsonare the harness event namespace and are deliberately unchanged —renaming them would break subagent way injection.
Verification
ways lint hooks/ways --check— 137 files, 0 errors, 0 warningsadr lint— 0 errors (1 pre-existing warning on ADR-127, unrelated)Behavioural acceptance is only observable in live sessions; it can't be verified from inside
the repo. Noted as a limitation in the ADR.
Known limitation
counter_steerwas not observed in either sampled session, but both samples share oneaccount and GrowthBook buckets on stable user attributes, so they land the same arm by
construction. That gate is untested, not absent — §2 of the way text is written
defensively for it.
Implements ADR-175.