diff --git a/plugins/discovery/.claude-plugin/plugin.json b/plugins/discovery/.claude-plugin/plugin.json index f70394835c..5690dcf0f9 100644 --- a/plugins/discovery/.claude-plugin/plugin.json +++ b/plugins/discovery/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "discovery", - "version": "0.9.0", + "version": "0.9.1", "description": "Structured discovery before changes: explore the local codebase and run disciplined multi-source external research, both dispatching a purpose-built subagent by default so the reading stays out of the main conversation — with source tiers, falsification, recency gates, and a corpus-coverage ledger — persisting EXPLORE.md / RESEARCH.md index-plus-sidecar handoff artifacts.", "author": { "name": "Melodic Software", diff --git a/plugins/discovery/CHANGELOG.md b/plugins/discovery/CHANGELOG.md index ef20eb6828..105149cda6 100644 --- a/plugins/discovery/CHANGELOG.md +++ b/plugins/discovery/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog — discovery plugin +## [0.9.1] + +### Fixed + +- **Nested-spawn availability claims described a state that lasted two releases.** Four places + asserted that the harness "filters `Agent` out of every non-fork subagent unless + `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH` is set" — true only for Claude Code 2.1.217–2.1.218. + Verified 2026-07-26 against the byte-exact release changelog: nesting shipped at a fixed five + layers (v2.1.172 — "Sub-agents can now spawn their own sub-agents (up to 5 levels deep)"), went off + by default (v2.1.217), and returned at v2.1.219 — "Subagents can now spawn nested subagents up to + depth 3 by default (was 1); set `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH=1` to disable nesting." + Confirmed behaviorally on 2.1.220: a non-fork `general-purpose` subagent held a fully-schema'd + `Agent` tool with the variable unset. Note the polarity flip the stale wording hides — the variable + now *lowers* the ceiling as readily as it raises one, so "absent" no longer implies "off." + - `agents/explorer.md`, `agents/researcher.md` — the necessary-not-sufficient framing and the + check-the-tool-is-actually-there instruction were already right and are kept; only the reason + changes, plus a new caution that a denied spawn is a permission verdict rather than a depth one + (spawns are classifier-evaluated before launch). + - `skills/setup/SKILL.md` — the dispatch-capability row no longer recommends setting the variable + on the assumption that absent means off. It now reports the value against the version and names + which window each reading belongs to. + - `skills/research-deep/SKILL.md` — two spots restated as availability that must be observed + rather than derived. The claim that `Agent` "errors even inside a fork" is replaced by the + invariant that actually holds: a fork cannot spawn a further fork. +- Load-bearing behavioral claims here are now version-pinned, so the next default move is visible as + drift instead of reading as settled fact. + ## [0.9.0] ### Removed diff --git a/plugins/discovery/agents/explorer.md b/plugins/discovery/agents/explorer.md index 4c55f6e3f8..5558e565e4 100644 --- a/plugins/discovery/agents/explorer.md +++ b/plugins/discovery/agents/explorer.md @@ -72,11 +72,14 @@ existing repo file in a single call. It does **not** make you read-only, and it mechanically enforce the memory-tier boundary. The boundary above holds by instruction. Honor it deliberately. -`Agent` is listed, but **listing is necessary and not sufficient**: the harness filters it out of -every non-fork subagent unless `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH` is set in the session. Both -conditions must hold, which is why your dispatch prompt carries a nesting flag rather than leaving -you to infer one — and why you check whether the tool is actually there rather than treating the -flag as a guarantee. +`Agent` is listed, but **listing is necessary and not sufficient**: the harness also has to be +allowing nested spawning at your depth, and that default has moved repeatedly (fixed five layers, +then off, then a configurable default of three as of Claude Code v2.1.219 — tunable via +`CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH`, which now *lowers* the ceiling as readily as it raises one). +Both conditions must hold, which is why your dispatch prompt carries a nesting flag rather than +leaving you to infer one — and why you check whether the tool is **actually there** rather than +treating either the flag or a version number as a guarantee. A spawn that comes back denied is not +an answer about depth: spawns are permission-classified before launch, so read the error text. ## Untrusted-content posture (standing instruction) diff --git a/plugins/discovery/agents/researcher.md b/plugins/discovery/agents/researcher.md index f695033c36..2756bd3629 100644 --- a/plugins/discovery/agents/researcher.md +++ b/plugins/discovery/agents/researcher.md @@ -69,11 +69,14 @@ existing repo file in a single call. It does **not** make you read-only, and it mechanically enforce the memory-tier boundary — `Bash` and `Write` both write. The boundary above holds by instruction. Honor it deliberately. -`Agent` is listed, but **listing is necessary and not sufficient**: the harness filters it out of -every non-fork subagent unless `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH` is set in the session. Both -conditions must hold, which is why your dispatch prompt carries a nesting flag rather than leaving -you to infer one — and why you check whether the tool is actually there rather than treating the -flag as a guarantee. +`Agent` is listed, but **listing is necessary and not sufficient**: the harness also has to be +allowing nested spawning at your depth, and that default has moved repeatedly (fixed five layers, +then off, then a configurable default of three as of Claude Code v2.1.219 — tunable via +`CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH`, which now *lowers* the ceiling as readily as it raises one). +Both conditions must hold, which is why your dispatch prompt carries a nesting flag rather than +leaving you to infer one — and why you check whether the tool is **actually there** rather than +treating either the flag or a version number as a guarantee. A spawn that comes back denied is not +an answer about depth: spawns are permission-classified before launch, so read the error text. ## Untrusted-content posture (standing instruction) diff --git a/plugins/discovery/skills/research-deep/SKILL.md b/plugins/discovery/skills/research-deep/SKILL.md index 11040b7143..7ebfbe745c 100644 --- a/plugins/discovery/skills/research-deep/SKILL.md +++ b/plugins/discovery/skills/research-deep/SKILL.md @@ -25,7 +25,7 @@ If no topic was provided, infer it from the current conversation — identify th ## Dispatch decision (multi-topic check, then three tiers) -**Multi-topic check — run FIRST, before any tier.** Count the independent sub-topics in the ask (numbered list, enumerated questions, separable subjects that share no claims). **N ≥ 2 separable topics → do NOT dispatch an engine on the combined blob.** An engine decomposes ONE question into generic research *angles*; fed a multi-topic blob, every broad agent researches all N topics shallowly — N× the wall-clock and tokens for worse depth. Instead: spawn **N parallel topic agents** (Agent tool, `general-purpose`, one per topic, each running the full `/research` discipline; instruct each to cite primary sources by URL — a subagent return without citations is ungrounded synthesis). **Give each agent its own sub-slice** — `///`, assigned by this session in the dispatch envelope, never chosen by the worker (two workers choosing independently can choose the same one). Each writes the normal `RESEARCH.md` index, its sidecars, and its own `research-checklist.md` inside that sub-slice; those filenames are fixed, so N agents pointed at one slice root would overwrite one another's index and ledger rather than producing separable artifacts. **This session owns each topic's post-dispatch boundary — synthesis is the last step, not the only one.** The `/research` gate assigns its independent-corroboration and HIGH-confidence rows to a fresh verifier precisely because a producing context may not grade its own choices, and a topic worker generally cannot dispatch that verifier: `Agent` is filtered out of every non-fork subagent unless `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH` is set. So for **each** topic, this session dispatches the sibling verifier, applies project fit, and writes both results back into that topic's index — before synthesizing. Skipping it produces the worst available artifact: a root `RESEARCH.md` presenting claims as gate-passed when the rows that matter were never graded by anyone. Then synthesize the slice-root `RESEARCH.md` from the per-topic indexes. An engine is for a SINGLE contested or deep question that needs falsification rounds and adversarial claim-checking. +**Multi-topic check — run FIRST, before any tier.** Count the independent sub-topics in the ask (numbered list, enumerated questions, separable subjects that share no claims). **N ≥ 2 separable topics → do NOT dispatch an engine on the combined blob.** An engine decomposes ONE question into generic research *angles*; fed a multi-topic blob, every broad agent researches all N topics shallowly — N× the wall-clock and tokens for worse depth. Instead: spawn **N parallel topic agents** (Agent tool, `general-purpose`, one per topic, each running the full `/research` discipline; instruct each to cite primary sources by URL — a subagent return without citations is ungrounded synthesis). **Give each agent its own sub-slice** — `///`, assigned by this session in the dispatch envelope, never chosen by the worker (two workers choosing independently can choose the same one). Each writes the normal `RESEARCH.md` index, its sidecars, and its own `research-checklist.md` inside that sub-slice; those filenames are fixed, so N agents pointed at one slice root would overwrite one another's index and ledger rather than producing separable artifacts. **This session owns each topic's post-dispatch boundary — synthesis is the last step, not the only one.** The `/research` gate assigns its independent-corroboration and HIGH-confidence rows to a fresh verifier precisely because a producing context may not grade its own choices, and a topic worker cannot be relied on to dispatch that verifier: whether a non-fork subagent holds `Agent` depends on the harness's current nesting depth allowance (`CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH`), a default that has moved three times and is not worth designing against. So for **each** topic, this session dispatches the sibling verifier, applies project fit, and writes both results back into that topic's index — before synthesizing. Skipping it produces the worst available artifact: a root `RESEARCH.md` presenting claims as gate-passed when the rows that matter were never graded by anyone. Then synthesize the slice-root `RESEARCH.md` from the per-topic indexes. An engine is for a SINGLE contested or deep question that needs falsification rounds and adversarial claim-checking. For a single-topic ask, detection is **engine-biased**: prefer the heaviest available tier UNLESS the task is clearly small/targeted. Unknown scope or any doubt → heavier tier. @@ -81,8 +81,10 @@ This variant tracks `/research`'s conventions — same discipline file, same art angles; given N separable topics, every broad agent researches all N shallowly — N× the cost for worse depth. Run the multi-topic check FIRST, before any tier selection. - **Dispatching this skill itself.** It must run in main context: `Workflow` is unavailable in every - non-fork subagent, and the multi-topic path needs the `Agent` tool, which errors even inside a - fork. A dispatched `/research-deep` silently loses Tier 1 and the N-topic fan-out — the two things + non-fork subagent, and the multi-topic path needs the `Agent` tool, whose availability inside a + subagent depends on a nesting default that has moved three times — and which, inside a fork, + cannot spawn a further fork at all. A dispatched `/research-deep` risks silently losing Tier 1 and + the N-topic fan-out — the two things it exists for. The sibling `/research` is the one that dispatches. - **Accepting a subagent return without cited primaries.** That is ungrounded synthesis, Tier 3 by the discipline's own rule. Instruct every topic agent to cite primary source URLs, and treat a diff --git a/plugins/discovery/skills/setup/SKILL.md b/plugins/discovery/skills/setup/SKILL.md index e0babef0c1..2e5e547f48 100644 --- a/plugins/discovery/skills/setup/SKILL.md +++ b/plugins/discovery/skills/setup/SKILL.md @@ -61,13 +61,21 @@ Report the effective concern and the guard result as a PASS/FAIL/INFO table. Do blocked the invoking turn and the narrow background tool set did not apply to it. Report the observed version and, when it is under the floor, name which of those the session does not have. The skills still run — inline is always available — so this is INFO, not FAIL. - - **`CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH`** — present or absent. Absent is INFO plus the - recommendation to set it (`"5"` matches the default depth that applied through 2.1.216), because - it only buys **throughput**: without it a dispatched agent fans out sequentially — slower, same - coverage. Note the variable is one of **two** conditions: it lifts the harness filter that - removes `Agent` from every non-fork subagent, but it cannot add a tool an agent definition left - out. The shipped `discovery:explorer` / `discovery:researcher` definitions list `Agent` for - exactly this reason; a third-party agent that does not is unaffected by setting it. It is not a correctness prerequisite here, because the one control that needs a context + - **`CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH`** — report the value, present or absent, and say what + the running harness does with it rather than assuming. This default has moved three times: + nesting shipped at a fixed five layers (2.1.172), went **off** by default (2.1.217), then + returned at **a configurable default of three** (2.1.219) — so on 2.1.219 or later, absent + means nesting is *available*, and the variable now lowers the ceiling (`"1"` disables nesting) + as readily as it raises one. Read absent against the observed version, in four windows: below + **2.1.172** nesting does not exist at all and the variable buys nothing; 2.1.172–2.1.216 absent + meant available at a fixed five; 2.1.217–2.1.218 absent meant *off*, the only window where + setting it was the way to turn nesting on; 2.1.219 and later absent means available at three. + Report absent as INFO in every window: nesting buys + **throughput**, not coverage — without it a dispatched agent fans out sequentially, slower for + the same result. The variable is still only one of **two** conditions: it cannot add a tool an + agent definition left out. The shipped `discovery:explorer` / `discovery:researcher` definitions + list `Agent` for exactly this reason; a third-party agent that does not is unaffected by setting + it. It is not a correctness prerequisite here, because the one control that needs a context which has not seen the work is the outcome-gate verifier, and the parent dispatches that as a **sibling** rather than the agent as a child. Note that env vars are read at session start, so a value set now takes effect next session.