fix(research): restore subagent fan-out, record that a skill body and its allowed-tools must agree

research instructed "spawn one subagent per URL" while its allowed-tools
granted no spawn tool, so it silently degraded to serial fetches. Three
other skills spawn subagents without trouble because they declare no
allowed-tools. The defect was the mismatch, not the spawning.

ADR-0027 records the agreement rule. research drops allowed-tools and
gets its steps 4-5 fan-out and the orchestrator-writes gotcha back
(1.0.1 -> 1.1.0).

Closes #116

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EGHFJextYtVQseaHPDDhxB
This commit is contained in:
2026-09-21 07:04:17 +00:00
parent af008b6d37
commit 6683da54ac
2 changed files with 52 additions and 12 deletions

View File

@@ -0,0 +1,48 @@
# A skill's body and its `allowed-tools` must agree — a spawning step needs a spawn tool or no list
**Status:** Accepted (2026-09-21)
`plugins/bin/.apm/skills/research/SKILL.md` once told the agent to "spawn one subagent per URL"
while its `allowed-tools` granted nothing that spawns. `WebFetch` was granted, so it never
hard-failed: it degraded to serial fetches in the orchestrator's own context, and the "in
parallel" wording, the page cap and the "subagents summarise, orchestrator writes" gotcha all
quietly stopped meaning anything. The #99 retrofit rewrote steps 4 and 5 as honest serial reads
with a real page cap and per-page reduction to notes (#116).
The defect was a **mismatch between what the body instructs and what `allowed-tools` permits**. It
was not that a skill spawned subagents. Three skills spawn today and work: `write-docs` (Reader
Testing sub-agent), `improve-codebase-architecture` (`Explore`, and 3+ parallel sub-agents in
`references/interface-design.md`) and `forge` (fork and clean-context subagents). None declares
`allowed-tools`, so all inherit every tool, spawning included. `research` was the only one that
both restricted the list and instructed spawning.
**Decision: a skill may instruct spawning subagents, provided its `allowed-tools` agrees with its
body.** Either:
- omit `allowed-tools`, so the skill inherits every tool on every target; or
- list the spawn tool — only once its per-target name is known, since `allowed-tools` is a flat list
and `claude`, `copilot` and `codex` name it differently.
A step that needs a tool the list does not grant must be rewritten as a step that does not need it.
That is what the #99 retrofit did to `research`, and it stays correct until one of the two options
above is taken.
Fan-out is not confined to agents. `CONTEXT.md` says a plugin-scope agent *delegates to skills*
because it cannot disclose to itself; it does not say skills may not delegate.
## Consequence for `research`
Its fan-out is restored, and `allowed-tools` is dropped to do it (version 1.0.1 → 1.1.0). That is
the first of the two options above; the second is unavailable until the per-target spawn tool names
are known.
The cost is stated rather than hidden: `research` fetches arbitrary web pages, and inheriting every
tool widens what an injected page could reach for. Two things bound it. The subagents only read and
summarise, and the orchestrator alone writes files, so the write surface is unchanged in intent. And
the least-privilege list was never enforceable across targets anyway, since it could not name a
spawn tool. If a per-target form of `allowed-tools` appears, restore a list that includes the spawn
tool.
Rejected: banning spawning in skills (contradicted by three working skills, and unsupported by
`CONTEXT.md`), and guessing a per-target spawn tool name in `allowed-tools` (no per-target form
exists, and a wrong guess reproduces the defect silently).