fix(procedures): bound the scout's search to the record stores - #9
Conversation
/how-do-i was slow on a miss. Nothing instructed the scout to fan out, but nothing stopped it either: procedure-scout runs with all tools, step 3 says "err inclusive", and Boundaries forbade acting, inventing, omitting, and flattening — never widening. With no stop condition, an empty result reads as a reason to keep grepping rather than an answer. query-records.sh is not the cost: a miss measures 0.259s against 0.168s for a hit. The latency is the agent's own follow-on search. - Boundaries: name the search surface (references/**, plans/, mistakes.jsonl) and forbid everything outside it. - Step 4 is the last pass, and now carries the mistakes.jsonl command it only described (skills/log/SKILL.md:47 had it; the scout had to improvise). - Step 5: a miss returns NOT FOUND immediately. - SKILL.md states the same contract for the caller. Prose only — no script behavior changes, so no bats test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 59 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…y to reads
use-case-reviewer, two findings:
- SKILL.md listed references/** and mistakes.jsonl but not plans/, and the new
"those stores are the whole search surface" sentence hardened that omission
into an authoritative claim. query-records.sh scans plans/ on every call
(scripts/lib/stores.sh) and the agent's own boundary allows it.
- The boundary read literally forbids touching anything outside CODEX_ROOT,
but step 2 runs query-records.sh from ${CLAUDE_PLUGIN_ROOT}. Say the
restriction scopes what is read, not where the tool lives.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
drewdrewthis
left a comment
There was a problem hiding this comment.
Own-PR review fan-out: principles, hygiene, security. Personas skipped under the prose/config-only guard — the diff is two .md files, no code surface. Verdict comment follows.
Review verdict: READYReviewed at: No blocking concerns. All review threads resolved at this SHA, and the final scoped gate at this head returned NO BLOCKING DEFECTS. Scope note: READY here means no unresolved blocking review threads. It is not merge-readiness — see the residuals below, which are declared rather than fixed. What the rounds actually foundWorth recording, because three of the four rounds found defects in the fixes, not the original diff:
The PR body also carried a false claim ("the recall sweep is uncapped") with a quoted transcript that no longer existed at HEAD; corrected at all four assertion sites and re-verified against the published body. Non-blocking (Decide / New Issue)
Evidence at this SHA
Verdict is prose, not a GitHub approval. Scope: review findings only — READY means no unresolved blocking review threads at this SHA. It is not a merge-readiness signal; that is |
… rule Addresses all four blocking review threads on #9. - Drop `| tail -5` from the mistakes.jsonl sweep. It silently discarded matches against this file's own boundary ("never omit a trap because it looks unlikely"), and unlike query-records.sh's --full cap, tail prints no truncation notice — so neither scout nor caller could tell. Replaced with a fused warning at the trap site. - Step 5 now says "emit only the NOT FOUND section, including its /create-new line". "Return NOT FOUND now" read as "emit the bare token", which would drop the instruction the Invariants make mandatory — the whole value of a miss. - Step 4's "Nothing searches after this step" forbade reading the records that step had just located (it produces paths; the batch-read lives in step 3). Rescoped to "No new terms and no new stores after this step — reading the records this sweep named is part of it." - Collapse the stop rule to one home. It was stated four times across two files (agent-facing-docs.md rule 1). Step 5's fused warning is the template-sanctioned form and survives; the Boundaries bullet is now the scope rule only, with its exiled rationale tail cut (rule 7). - Boundaries now point at the discovery mechanism ("the stores query-records.sh covers") instead of freezing a third prose copy of the store list (rule 2), and name step 2's synonym expansion as the only remaining widening lever — the design tension a bounded search creates. - SKILL.md trimmed to a pointer at the agent's Boundaries, and its "so the caller can write the missing procedure" corrected to "improvise and draft the procedure afterward" — it inverted the agent's own invariant that a procedure written before it has succeeded once is a guess with an id. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…le tail Three Must-Fixes from the scoped re-review of 90d4aef. - The boundary said "the stores `query-records.sh` covers" — a predicate the scout cannot resolve without reading two scripts, in a doc whose point is context economy. Rule 2 wants a discovery mechanism, not an unresolvable reference. Now names the SSOT by path: the STORES list in scripts/lib/stores.sh. - SKILL.md enumerated `references/**` + `plans/` while the boundary named the seven STORES dirs — not the same set (adrs/, policies/, scenarios/ and loose references/*.md fall between them), and the skill then said "the scout's Boundaries hold the rule" having just stated a different one. Contradictions are bugs (rule 5). Enumeration deleted; the agent file is the one home. - Fix 4 from round 1 did not hold. I deleted the old rationale tail and added a new one in the same commit ("a bounded search makes a false miss a keyword problem, so spend the breadth there") — the same defect class, reintroduced by its own fix. Cut. The bullet now ends at "are not the answer surface." Also: step 4's "reading the records this sweep named is part of it" needed a read command or the scout improvises a per-file loop — the context bloat step 3 exists to prevent. Points at step 3's awk batch-read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…bump
proof-reviewer found that round 1's fix made the reported problem worse.
Removing `| tail -5` left an uncapped `grep -iE` over mistakes.jsonl. Measured
against the live file:
$ wc -lc ~/.claude/mistakes.jsonl
428 440851
$ grep -icE 'pr|review' ~/.claude/mistakes.jsonl
428
A term set of the shape step 2 encourages ("expand synonyms", "the capability
as well as the identifier") returns the whole 430KB file — ~110k tokens — into
the scout's context. `grep -i` is unanchored, so it matches inside paths and
URLs too. Issue #15's symptom IS latency, and this was the one command in the
diff whose cost scales with corpus size: AC4 satisfied to the letter while
working against the goal.
"Uncapped" was never the only alternative to a silent cap. Now: count first,
read the 20 most recent, and report in STANDING NOTES when the count exceeds
what was read. A cap is allowed; a silent one is not.
Also bumps 0.2.0 -> 0.2.1. Without it the fix does not reach version-pinned
caches (precedent: 03830b2), so the change could not have shipped at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t the count
proof-reviewer, three defects introduced by the earlier fix rounds.
- The boundary pointed at `scripts/lib/stores.sh` — the only unqualified path
in the file, matching two real files on this box, and reading either is
itself outside the allowed surface. To obey the boundary the scout had to
breach it. That is round 2's "set the scout could not enumerate" relocated,
not removed. The seven stores are now inline, with the SSOT named (qualified
with ${CLAUDE_PLUGIN_ROOT}) for anyone who needs to confirm.
- Issue #15 AC5 regressed: SKILL.md named plans/ at c815903 and, after round
2 deleted the enumeration to fix a contradiction, named no surface at all.
It now describes exactly what query-records.sh searches, plus mistakes.jsonl
as the store it does NOT cover — verified against stores.sh.
- The STANDING NOTES overflow report had no slot in the Output block, and
"emit only the NOT FOUND section" on a miss contradicted reporting it. Added
the line to the schema and made it the one stated exception, since an
unreported truncation is the silent drop the cap is only permitted because
it reports.
Also corrects SKILL.md's pointer: Boundaries hold the scope rule, step 5 holds
the stop rule. It previously attributed both to Boundaries and then restated
the stop rule anyway.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…list Final gate returned NO BLOCKING DEFECTS; these are its Should-Improves. - Drop the "stores.sh is the SSOT if you ever need to confirm" half of the parenthetical. The carve-out one sentence later exempts RUNNING query-records.sh, not READING files under CLAUDE_PLUGIN_ROOT, so it invited a read the surrounding rule forbids — round 3's defect, softened but not gone. It was inert anyway: the scout is bound by the inlined list whatever stores.sh says. - Collapse the four-line rationale tail added by round 4 into the slot comment it was explaining. 517f7ae was literally "drop the new rationale tail"; round 4 added another one. Third time this file has grown a tail while removing one. - SKILL.md now uses ${CODEX_ROOT:-~/.claude} to match query-records.sh's own resolution, which the agent's Boundaries already got right. NEW: hooks/tests/store-list-drift.bats. Inlining the store list was the right call — the pointer it replaced named a file the scout could not read without breaching the boundary that names it — but it traded auto-freshness for silent drift with nothing guarding it. Four cases, both directions, with a non-empty assertion first so none can pass vacuously. Falsified on a copy: removing one store from the agent's list reddens case 2. The reverse check uses a negative lookbehind on `/` because `skills/create-new/references/create-procedure.procedure.md` otherwise reports a store nobody claimed. Suite 145 ok / 0 not ok. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Closes #15
What
/how-do-iwas slow when the corpus had no match. This bounds theprocedure-scoutagent's search surface and makes a miss a finished answer instead of a reason to keep grepping.Why
The script is not the cost. Measured:
Both sub-300ms. The latency is the agent's own follow-on searching.
Nothing instructs it to fan out — I grepped for it. The cause is an absent boundary:
procedure-scoutdeclares notools:restriction (full Bash/Grep/Glob), step 3 says "err inclusive", and Boundaries forbade acting, inventing, omitting a trap, and flattening a contradiction — never widening. With full tools, an instruction to err inclusive, and no stop sign, an empty result reads as "try one more grep". TheNOT FOUNDoutput section already existed; nothing routed to it.Changes
query-records.shcovers, plusmistakes.jsonl") rather than freezing a prose copy of the list, and names step 2's synonym expansion as the only remaining widening lever.mistakes.jsonlgrep it previously only described, with a reported cap — the originaltail -5dropped traps silently, against the file's own boundary.NOT FOUNDsection including its/create-newline, and stops.Human verification (for if you really don't trust me)
cd plugins/procedures && bats hooks/tests— 141 pass, unchanged frommain(this PR touches no script). Note this proves nothing about the diff: no test asserts anything aboutprocedure-scout.mdorSKILL.md, so the suite is green with the whole change reverted.grep -n 'tail' plugins/procedures/agents/procedure-scout.md— one hit, the sweep'stail -20; confirm the count line above it and the STANDING NOTES slot in the Output block, so truncation is reported rather than silent.agents/procedure-scout.mdBoundaries and step 5: confirm the stop rule has one canonical home and theNOT FOUNDinstruction is not reducible to a bare token.How I can prove I was successful
Suite is green at this head — proven, and non-load-bearing.
Stated for completeness only: nothing in
hooks/testscovers the two prose files this PR changes.The stop rule has one home — proven. Grepped
widen|Nothing searches|every pass you get|NOT FOUNDacross both files: step 5's fused warning is the only stop statement; the Boundaries bullet is now the scope rule, and SKILL.md is a pointer.The agent actually behaves differently on a miss — proven (n=2). Installed this branch and
origin/maininto throwawayCLAUDE_CONFIG_DIRs and ran the same guaranteed-miss query against each, twice:Measured at
0a4a7ad(artifacts written 09:58-10:03Z; that commit landed 09:56:45Z), i.e. against the capped sweep this PR ships. Round 4 (00ae1ea) changed prose only — the store list, the SKILL.md surface and the STANDING NOTES slot — not the search shape.Read this as weaker than it looks. Two different queries, one run per arm each — so within-query n=1, and no condition was repeated. Arms ran sequentially, so arm is confounded with time. And the cost is identical (~$0.45/run) in both arms, which argues against the stated mechanism: fewer follow-on greps should mean fewer tool calls and fewer tokens, hence lower cost. Equal cost with lower wall-clock is at least as consistent with API-load drift as with a behaviour change. The one observation that is not merely timing is the qualitative divergence below.
The arms are not emitting identical output, and the difference is the point rather than a confound. Main's run B improvised its own verdict header instead of the canonical section, and narrated searching outside the corpus:
skills/is not a record store and is not in the allowed surface — that is the widening this PR exists to stop, visible in the baseline arm. Branch runs returned the## NOT FOUNDsection with its/create-newrouting both times. So the wall-clock gap and the format divergence share one cause.The recall sweep is capped at 20 and the cap is reported — proven. Round 1 removed
tail -5outright; the re-review showed that made the reported problem worse, because on the live corpusgrep -icE 'pr|review' ~/.claude/mistakes.jsonlmatches all 428 lines / 440851 bytes — a broad term set would pull ~110k tokens. The sweep now counts first, reads the 20 most recent, and reports the count in STANDING NOTES when it exceeds what was read:Known limit, not hidden: a term set broad enough to match every line makes
tail -20"the last 20 mistakes" rather than the 20 most relevant. The count is what stops that from being silent — it does not make the selection good. Narrowing recall selection is out of scope here.Measurement path — stated, not glossed. The runs above go through
Backend-only: this diff is two markdown files that instruct a subagent. There is no UI, no rendered surface, and no running app to screenshot; the terminal output above is the whole observable behaviour.claude -p, which exercises the skill and theprocedure-scoutdispatch (the scout output above is real). It does not exercise this plugin's hooks: I confirmed separately that no plugin hook fires in print mode. That does not affect this PR, which changes no hook — but the measured path is print-mode skill dispatch, not an interactive session, and I have not shown the two are identical.Review
Reviewed by the standard own-PR fan-out across three rounds. Round 1 raised four blocking threads (fixed in
90d4aef). The scoped re-review found that two of those fixes did not hold — the stop-rule collapse had replaced one rationale tail with another, and the boundary pointed at a set the scout could not enumerate (fixed in517f7ae). proof-reviewer then found that removing thetailcap made the reported latency worse, not better (fixed in0a4a7ad). See the verdict comment.Acceptance criteria (issue #15)
mistakes.jsonl(00ae1ea); earlier revisions pointed at a file the scout could not read without breaching the boundary/create-newtail -20+ count + a STANDING NOTES slot in the Output block. The cap's selection quality on a very broad term set is a known limit, stated aboveplans/00ae1eaafter round 2 regressed it; list verified againstscripts/lib/stores.shReporter's case — "/how-do-i is slow when the corpus has no match": PARTIALLY VERIFIED. The qualitative divergence is real and reproducible in direction (main's scout searched
skills/, outside the corpus, and improvised its verdict format). The latency claim is weaker than the table suggests, for the reasons stated in that section — equal cost across arms is the disconfirming datum and I have not explained it away.