diff --git a/plugins/discovery/.claude-plugin/plugin.json b/plugins/discovery/.claude-plugin/plugin.json index 4f9e6566f9..0a0b78e0ed 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.19.8", + "version": "0.19.9", "description": "Structured discovery before changes: explore the local codebase, run disciplined multi-source external research, and reconstruct why a past decision was made from evidence outside the code \u2014 each dispatching a purpose-built subagent by default so the reading stays out of the main conversation, with source tiers, falsification, recency gates, an intent-evidence tier, and a corpus-coverage ledger \u2014 persisting EXPLORE.md / RESEARCH.md / INTENT.md index-plus-sidecar handoff artifacts.", "author": { "name": "Melodic Software", diff --git a/plugins/discovery/CHANGELOG.md b/plugins/discovery/CHANGELOG.md index d2fbf763eb..b16699da59 100644 --- a/plugins/discovery/CHANGELOG.md +++ b/plugins/discovery/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog — discovery plugin +## [0.19.9] + +### Fixed + +- **`explorer`, `explore`:** the explore lane now carries the same preload-miss recovery the + research and trace-intent lanes have. `agents/explorer.md` tells a dispatched explorer to confirm + the skill body is in context before any exploration work and otherwise Read + `skills/explore/SKILL.md` from disk, and its payload gains the `preload: fired | fallback` + provenance field. Previously the agent's only instruction on a miss was to set + `preload_token: MISSING` and stop, and a run that looked for the token in the dispatch prompt or + the memory slice instead ran undisciplined and wrote an artifact shaped like the prompt's + description of the deliverable rather than `EXPLORE.md` plus sidecars. `skills/explore/SKILL.md` + now demotes a matching token to file-identity, grades `preload:` in gate step 1, and treats a + payload without the field as an out-of-date agent definition; `reference/dispatch.md` carries + the rationale; `contract.test.sh` section 11 pins the explore lane beside the other two; the + explore evals gain the fallback-recovery case. The early-emission checklist in + `agents/explorer.md` names `preload:` beside `preload_token`, as the researcher and intent-tracer + checklists do, so an interrupted fallback run does not leave the template's `fired` default in + its early payload; section 11 pins that clause in all three agent definitions. + ## [0.19.8] ### Added diff --git a/plugins/discovery/agents/explorer.md b/plugins/discovery/agents/explorer.md index 510ac76e1d..f21e14b5f1 100644 --- a/plugins/discovery/agents/explorer.md +++ b/plugins/discovery/agents/explorer.md @@ -13,8 +13,9 @@ of exploration — file reads, Glob results, Grep output, git archaeology — ne orchestrator's context window. You start with no conversation history by design. Everything you need arrives in your dispatch prompt. -The `/discovery:explore` skill is preloaded into your context at startup. Its exploration -dimensions, output format, and outcome gate are your procedure. It names a sibling +The `/discovery:explore` skill reaches you through your `skills:` preload, and a preload that fails +is skipped silently, so "Preload liveness" below is the first thing you do. Its exploration +dimensions, output format, and outcome gate are your procedure, not a suggestion. It names a sibling ecosystem-discovery reference — Read that at the dimension that needs it rather than up front. That reference composes `/toolchain:check`'s covered-ecosystem set and root adjacency when the `toolchain` plugin is installed (fallback table when it is @@ -72,14 +73,27 @@ downstream edit lands against the project's declared direction. A `skills:` entry that fails to resolve is skipped **silently**: Claude Code logs a warning to the debug log and starts you anyway. An undisciplined run that still writes an artifact is -indistinguishable from a good one at every other seam. The dated record for that harness behavior +indistinguishable from a good one at every other seam, which is exactly the failure the token exists +to prevent. The dated record for that harness behavior is [`${CLAUDE_PLUGIN_ROOT}/reference/parent-contract.md`](${CLAUDE_PLUGIN_ROOT}/reference/parent-contract.md), "Harness facts the dispatch design rests on". -The preloaded skill declares a **preload token**. Echo it verbatim into `preload_token` in your -return payload. If no skill content reached you — no exploration dimensions, no outcome gate, no -token — set `preload_token: MISSING` and stop with `status: truncated`. Do not reconstruct the -workflow from memory. +Before any exploration work, confirm the skill body is already in your context — its exploration +dimensions, its output format, its outcome gate, and the token it declares. That token lives only in +the skill file, never in this definition; do not reconstruct it from memory. If the body is not in +context, **Read** `${CLAUDE_PLUGIN_ROOT}/skills/explore/SKILL.md` and work under it. The scope, the +slice path, and the reason still come only from your dispatch prompt: a body you Read from disk is +no more a source of them than a preloaded one. + +The skill file declares a **preload token**. Echo it verbatim into `preload_token` in your return +payload, and set `preload:` to how the skill body reached you: `fired` if it was already in context +at startup and you did not Read the skill file, `fallback` if you Read it from disk. A matching +token is file-identity evidence that the body reached you, **not** proof that preload fired; the +parent grades `preload:` for that, and `fallback` is the accepted recovery. If no skill content +reached you by either route — no exploration dimensions, no outcome gate, no token — set +`preload_token: MISSING`, omit a fabricated `preload:` value, and stop with `status: truncated`. Do +not reconstruct the workflow from memory. Never treat a token you found by Reading the skill file as +`fired`. ## Tool honesty @@ -190,7 +204,8 @@ findings. The 7-section report is what the artifact is for. Your file reads and here; that is the entire point of dispatching you. ```yaml -preload_token: +preload_token: +preload: fired # fired | fallback — how the skill body reached you; never inferred from the token scope_as_received: status: complete # complete | truncated persistence: written # written | by-value @@ -222,9 +237,11 @@ more read. **Do not rely on budgeting a turn at the end for it.** You cannot observe your own remaining turn budget, so "leave a turn spare" is a schedule against a limit you cannot see. Instead **emit the payload block early and keep it current**: as soon as the scope is resolved, write the block with -`status: truncated`, `preload_token` echoed, `scope_as_received` quoted, and the fields you do not -have yet left as placeholders; then re-emit it, updated, whenever a section lands. A stop at any -point after that leaves the parent a well-formed payload instead of silence. +`status: truncated`, `preload_token` echoed, `preload:` set, `scope_as_received` quoted, and the +fields you do not have yet left as placeholders; then re-emit it, updated, whenever a section lands. +A stop at any point after that leaves the parent a well-formed payload instead of silence. Setting +`preload:` in the early block matters most on the fallback path: an interrupted recovery that +copied the template's default would report `fired` for a body it Read from disk. ### `persistence:` — when the work finished but the write did not diff --git a/plugins/discovery/scripts/contract.test.sh b/plugins/discovery/scripts/contract.test.sh index 0aeae0d719..3536978a14 100755 --- a/plugins/discovery/scripts/contract.test.sh +++ b/plugins/discovery/scripts/contract.test.sh @@ -260,6 +260,8 @@ else fi assert_present 'researcher payload contract carries preload: fired|fallback' \ 'agents/researcher.md' 'preload: fired' +assert_present 'researcher early-emission checklist sets preload: beside the token' \ + 'agents/researcher.md' '`preload_token` echoed, `preload:` set' assert_present 'research SKILL.md demotes the token to file-identity' \ 'skills/research/SKILL.md' 'file-identity, \*\*not\*\* proof that preload fired' assert_present 'research SKILL.md requires the structured preload field' \ @@ -268,6 +270,29 @@ assert_present 'research dispatch contract forbids inferring fired from the toke 'skills/research/context/dispatch.md' 'MUST NOT infer' assert_present 'research evals grade matching-token-is-not-preload-proof' \ 'skills/research/evals/evals.json' 'matching-token-is-file-identity-not-preload-proof' +if grep -qE 'discovery-explore-preload-8e2b7d' "$PLUGIN_ROOT/agents/explorer.md"; then + fail 'agents/explorer.md does not embed the preload token' +else + pass 'agents/explorer.md does not embed the preload token' +fi +assert_present 'explorer reads the skill body from disk when preload did not deliver it' \ + 'agents/explorer.md' 'skills/explore/SKILL\.md' +assert_present 'explorer payload contract carries preload: fired|fallback' \ + 'agents/explorer.md' 'preload: fired' +assert_present 'explorer early-emission checklist sets preload: beside the token' \ + 'agents/explorer.md' '`preload_token` echoed, `preload:` set' +assert_present 'explorer never reports a token it Read from disk as fired' \ + 'agents/explorer.md' 'Never treat a token you found by' +assert_present 'explore SKILL.md demotes the token to file-identity' \ + 'skills/explore/SKILL.md' 'file-identity, \*\*not\*\* proof that preload fired' +assert_present 'explore SKILL.md requires the structured preload field' \ + 'skills/explore/SKILL.md' 'preload: fired \| fallback' +assert_present 'explore SKILL.md gate treats a missing preload field as out-of-date' \ + 'skills/explore/SKILL.md' 'A missing or unrecognized `preload:` field is an out-of-date agent definition' +assert_present 'explore dispatch contract forbids inferring fired from the token' \ + 'skills/explore/reference/dispatch.md' 'MUST NOT infer' +assert_present 'explore evals grade matching-token-is-not-preload-proof' \ + 'skills/explore/evals/evals.json' 'matching-token-is-file-identity-not-preload-proof' if grep -qE 'discovery-trace-intent-preload-7b3e2d' "$PLUGIN_ROOT/agents/intent-tracer.md"; then fail 'agents/intent-tracer.md does not embed the discipline-liveness token' else @@ -275,6 +300,8 @@ else fi assert_present 'intent-tracer payload contract carries preload: fired|fallback' \ 'agents/intent-tracer.md' 'preload: fired' +assert_present 'intent-tracer early-emission checklist sets preload: beside the token' \ + 'agents/intent-tracer.md' '`preload_token` echoed, `preload:` set' assert_present 'trace-intent SKILL.md demotes the token to file-identity' \ 'skills/trace-intent/SKILL.md' 'file-identity, \*\*not\*\* proof that preload fired' assert_present 'trace-intent SKILL.md requires the structured preload field' \ diff --git a/plugins/discovery/skills/explore/SKILL.md b/plugins/discovery/skills/explore/SKILL.md index 7ab6721edb..f419d19e72 100644 --- a/plugins/discovery/skills/explore/SKILL.md +++ b/plugins/discovery/skills/explore/SKILL.md @@ -44,19 +44,19 @@ These values orient this session only. The project root is an absolute machine p **One named alternative:** the **built-in Explore subagent**, for raw "where is X / how does Y work" search. Fast, read-only, context-isolated. It skips project memory (convention-blind) and neither runs this 6-dimension workflow nor writes `EXPLORE.md`. Pass key constraints in the prompt when conventions matter, and expect to write the artifact yourself. Scale 1→N by dispatching more, each owning a disjoint area. -**Preload-liveness sentinel.** A dispatched agent receives this body through its `skills:` preload, and a preload that fails to resolve is skipped **silently**. Logged to the debug log and nowhere else. The dated record for that harness behavior is [`${CLAUDE_PLUGIN_ROOT}/reference/parent-contract.md`](${CLAUDE_PLUGIN_ROOT}/reference/parent-contract.md), "Harness facts the dispatch design rests on". A dispatched run therefore echoes this token verbatim as `preload_token` in its return payload: +**Preload-liveness sentinel.** A dispatched agent receives this body through its `skills:` preload, and a preload that fails to resolve is skipped **silently**. Logged to the debug log and nowhere else. The dated record for that harness behavior is [`${CLAUDE_PLUGIN_ROOT}/reference/parent-contract.md`](${CLAUDE_PLUGIN_ROOT}/reference/parent-contract.md), "Harness facts the dispatch design rests on". A dispatched run therefore echoes this token verbatim as `preload_token` in its return payload. The disk fallback Reads this same file, so a matching `preload_token` is file-identity, **not** proof that preload fired: ```text discovery-explore-preload-8e2b7d ``` -A missing or mismatched token is a **hard failure: the parent discards the run**, never downgrades or accepts the artifact. Without it, a preload miss produces an undisciplined run that still writes an artifact. Indistinguishable from success at every other seam. +A missing or mismatched token is a **hard failure: the parent discards the run**, never downgrades or accepts the artifact. Without it, a preload miss produces an undisciplined run that still writes an artifact. Indistinguishable from success at every other seam. Provenance is `preload: fired | fallback`; `fallback` is the accepted recovery, not a discard. Rationale and the parent-side contract: [`${CLAUDE_PLUGIN_ROOT}/skills/explore/reference/dispatch.md`](${CLAUDE_PLUGIN_ROOT}/skills/explore/reference/dispatch.md), "Discipline liveness". **Post-dispatch acceptance gate. Parent-side, before the payload is believed.** `status: complete` is the agent's claim about its own run, and a claim is not evidence. Grade the run **off disk**, against the memory-slice path from the parent's own pre-dispatch envelope, **carry that path across the dispatch, because it is this gate's input**, and never a path read out of the payload, because the failure this gate exists to catch is a payload that comes back carrying no pointer at all. In order: **Pre-dispatch:** create the memory slice and touch `/.explore-dispatch` as the gate's freshness baseline, then hand that file to the gate as `--newer-than`. Without it a slice that already holds an earlier run's artifact set passes every on-disk check even when this dispatch wrote nothing at all. **Both shell forms of that one command are in [`${CLAUDE_PLUGIN_ROOT}/reference/parent-contract.md`](${CLAUDE_PLUGIN_ROOT}/reference/parent-contract.md) ("The pre-dispatch baseline"). Copy the one matching this session's shell, because the POSIX form's `touch` is not a command in PowerShell and its directory flag is a parameter error there. Same file carries the envelope template this dispatch also owes.** The memory root's self-ignoring `.gitignore` guard is a separate obligation this gate does not grade. Same file, "What this gate does not grade". -1. **The payload is well-formed**. `preload_token` matches the sentinel verbatim, and an `artifact:` pointer is present. Missing either is a **failed dispatch** whatever the `status` field says; a missing token is a discard, per the rule above. +1. **The payload is well-formed**. `preload_token` matches the sentinel verbatim, `preload:` is `fired` or `fallback`, and an `artifact:` pointer is present. Missing token or artifact is a **failed dispatch** whatever the `status` field says; a missing token is a discard, per the rule above. A missing or unrecognized `preload:` field is an out-of-date agent definition, not a pass. A matching token does not prove preload fired; `preload: fallback` is not a discard. **And `scope_as_received` matches the scope the parent actually sent**. Compared against the envelope the parent wrote, not against what it meant. It is the only check here that fires on an input that is present and wrong. A mismatch is a **failed dispatch**: re-dispatch with the scope restated in a form that survives the trip (see the caveat under **Scope**); do not accept the artifact and mentally translate it. A well-formed payload carrying no `scope_as_received` is an out-of-date agent definition, not a pass. 2. **The artifact set is actually on disk, and this run put it there:** diff --git a/plugins/discovery/skills/explore/evals/evals.json b/plugins/discovery/skills/explore/evals/evals.json index 41fea0525f..e83d66e9a6 100644 --- a/plugins/discovery/skills/explore/evals/evals.json +++ b/plugins/discovery/skills/explore/evals/evals.json @@ -158,6 +158,21 @@ "The payload is treated as a FAILED dispatch, the same class as a by-value payload returning findings instead of artifact bodies, and the ladder continues from the resume or discard rung", "The response identifies why this path is the sensitive one: the parent holds wider write permission than the sandboxed worker" ] + }, + { + "id": 13, + "name": "matching-token-is-file-identity-not-preload-proof", + "prompt": "Explore the item-service command and query handlers before I unify them. [The dispatched discovery:explorer returns a well-formed payload: preload_token matching discovery-explore-preload-8e2b7d, preload: fallback, scope_as_received matching what you sent, status: complete, persistence: written, coverage: complete, artifact: /EXPLORE.md, sidecars: 4. check-dispatch-artifact.sh --index-name EXPLORE.md exits 0 against the slice. open_questions includes: 'The token is echoed because I read the skill file from disk, not because it was preloaded.']", + "files": [], + "expected_output": "Accepts the run as a successful fallback recovery. The matching token is file-identity evidence that the skill body reached the agent, not proof that skills: preload fired. The parent grades preload: fallback as provenance and does not infer fired from the token. Does not discard the run, does not warn as if preload succeeded, and does not treat the open_questions prose as the provenance seam. Proceeds to the sibling verifier as on any accepted run.", + "expectations": [ + "The matching preload_token is treated as file-identity evidence only — the skill body reached the agent — and is NOT treated as proof that preload fired", + "preload: fallback is graded as the structured provenance field and is accepted; fallback is the recovery, not a failed dispatch", + "The parent does not infer preload: fired from a matching token, even when the agent also discloses the fallback in open_questions prose", + "The run is NOT discarded on the strength of a matching token plus a fallback disclosure; missing or mismatched token remains the discard condition", + "A payload that echoed the token but omitted preload: is an out-of-date agent definition, not a pass, the same class as a missing scope_as_received", + "The artifact set is still graded off disk against the parent's own slice path; fallback provenance changes nothing about the gate's remaining steps" + ] } ] } diff --git a/plugins/discovery/skills/explore/reference/dispatch.md b/plugins/discovery/skills/explore/reference/dispatch.md index 89ea371f82..935fa7d8eb 100644 --- a/plugins/discovery/skills/explore/reference/dispatch.md +++ b/plugins/discovery/skills/explore/reference/dispatch.md @@ -12,6 +12,32 @@ and what to do when it cannot run, and the resume-before-discard ordering — is [`${CLAUDE_PLUGIN_ROOT}/reference/parent-contract.md`](${CLAUDE_PLUGIN_ROOT}/reference/parent-contract.md). This file does not restate it. +## Discipline liveness — why a token at all + +A `skills:` entry that is missing or disabled is **skipped silently**: the harness logs a warning to +the debug log and starts the agent regardless. The dated record for that harness behavior is +[`${CLAUDE_PLUGIN_ROOT}/reference/parent-contract.md`](${CLAUDE_PLUGIN_ROOT}/reference/parent-contract.md), +"Harness facts the dispatch design rests on". The resulting run has no exploration dimensions, no +output format, and no outcome gate — and it still reads the tree, still writes an artifact, and +still returns a payload with `status: complete`. At every seam this design builds, that failure is +indistinguishable from success, and the artifact it produces is shaped by the dispatch prompt's +description of the deliverable rather than by the contract. + +So the skill file carries a token, the agent echoes it verbatim, and **the parent discards any run +whose `preload_token` is missing or mismatched**. Not downgrade, not warn, not accept-with-a-note: +the artifact of an undisciplined run is worse than no artifact, because it will be read as though +the workflow ran. + +The token lives in `SKILL.md` and nowhere in the agent definition. An agent that never received the +skill has no way to produce it. That is **file-identity** evidence: the skill body reached the agent +by some route. + +It is **not** preload evidence. The agent's disk fallback Reads this same file before any exploration +work, so a recovered agent echoes the same token a preloaded agent would. Provenance is the +structured `preload: fired | fallback` field. The parent grades that field and MUST NOT infer `fired` +from a matching token. `fallback` is the accepted recovery, not a discard. A missing or unrecognized +`preload:` field is an out-of-date agent definition, the same class as a missing `scope_as_received`. + ## Why the gate reads the slice path, not the payload A dispatched `explorer` can return `status: complete` with a mid-stream narration line as its whole