Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/discovery/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
27 changes: 27 additions & 0 deletions plugins/discovery/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
13 changes: 8 additions & 5 deletions plugins/discovery/agents/explorer.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
13 changes: 8 additions & 5 deletions plugins/discovery/agents/researcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
8 changes: 5 additions & 3 deletions plugins/discovery/skills/research-deep/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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** — `<memory_dir>/<slug>/<topic-slug>/`, 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** — `<memory_dir>/<slug>/<topic-slug>/`, 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.

Expand Down Expand Up @@ -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
Expand Down
22 changes: 15 additions & 7 deletions plugins/discovery/skills/setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down