Skip to content

Commit e765a3b

Browse files
authored
docs: stop Stage 2 reading the sub-agent's own system prompt (#654) (#655)
Stage 2's main agent listed `.claude/agents/bess-analyst.md` (25,681 B) as REQUIRED READING item 3. That file IS the bess-analyst sub-agent's system prompt -- frontmatter `name: bess-analyst`, `tools: Read, Grep, Glob, Bash, WebFetch` -- so the main agent was loading a second copy of another agent's instructions into its own context, on every turn. Nothing consumed it. The six PROCESS steps are: get issue context, identify the current problem, delegate, verify the cited file:line, publish, label. No step applies a "domain expertise checklist". The one distinctive task, step 4, needs the cited code -- not a triage procedure the main agent never runs. The content was in fact loaded three times. The section that could plausibly serve as a judging standard, "Separate Evidence from Claims", is already restated almost verbatim in the sub-agent task the same prompt passes: hypothesis-not-diagnosis, triage-the-bundle-first, verify-the-code-path- matches-this-user's-setup, intentional-design-is-not-a-bug. Sub-agent system prompt, main-agent file read, and inline paraphrase. All four landed Stage 2 analyses (#118, #252, #624, #627) use the workflow's four headings, never bess-analyst.md's seven-item Output Format or its six-item type-B shape -- the main agent follows the prompt, not the file. So item 3 is removed, with an explicit note saying why, and step 4 gains the check that was missing: a report fails verification if it claims a CODE bug with no file:line, if a cited location does not say what is claimed, or if it blends P-optimality / forecast error / control noise into one verdict when actual behavior diverged from plan. Dead weight becomes an instruction that fires. Three things the first draft got wrong, found in review: - "cites no file:line" alone would have rejected CORRECT diagnoses. The same prompt tells the analyst that unavailable sensors, wrong inverter type or an HA integration mismatch are likely the real cause, and those cite no code. Now qualified to code-bug claims only. - The inconclusive path never removed `ready-for-analysis`, and backlog-digest.sh checks that label BEFORE `needs-human-review`, so the backlog pass reports the issue as un-analysed and re-dispatches analyze -- re-billing the stage. Widening that path without fixing it would have made a cost bug worse. It now clears the label. - The new criterion used vocabulary defined only in the file this diff stops the agent reading. P-optimality had a gloss; P≠R never expanded P or R and control noise had no definition. All three are now glossed inline. quality-check.sh gains a context contract gate, written RED first: no workflow may list a `.claude/agents/*.md` file as a numbered required read, and Stage 2 must still delegate. Both assertions were mutation-tested -- removing the delegation reddens it, and reformatting `subagent_type` with backticks or quotes does not, which the first bare-substring version got wrong in both directions. Saving is up to 25,681 B off the main agent per turn; the prompt itself grew 7,098 -> 7,821 B, so net is roughly -24.9 KB. "Up to", because whether a run obeyed the read instruction is not observable -- #646 showed it need not. No CHANGELOG entry: agent-facing, zero user-visible effect, as #649 and #651. Closes #654
1 parent dd47d59 commit e765a3b

2 files changed

Lines changed: 94 additions & 2 deletions

File tree

.github/workflows/issue-analyze.yml

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@ jobs:
5656
──────────────────────────────────────────────────────────────────
5757
1. docs/agents/rules.md — hard constraints
5858
2. docs/agents/architecture.md — codebase layout & data flow
59-
3. .claude/agents/bess-analyst.md — domain expertise checklist
59+
60+
Do NOT read `.claude/agents/bess-analyst.md`. It is the
61+
`bess-analyst` sub-agent's own system prompt — it already has it in
62+
full, and you never run the procedure it describes. Reading it costs
63+
25 KB of your context on every turn and tells you nothing you act on.
6064
6165
──────────────────────────────────────────────────────────────────
6266
PROCESS
@@ -120,6 +124,32 @@ jobs:
120124
reading the file:line locations it cited. Do not just trust
121125
the summary — quote the actual code.
122126
127+
The report does NOT pass verification if:
128+
- it claims a CODE bug but cites no `file:line` — there is
129+
nothing to check. A diagnosis that the real problem is an
130+
unavailable sensor, the wrong inverter type, an HA
131+
integration mismatch or a stale add-on version is a
132+
legitimate root cause and needs no code citation;
133+
- a cited location does not say what the report claims it says;
134+
- actual behavior diverged from the plan (unplanned import,
135+
floor breach, spike) and the report gives ONE blended verdict
136+
instead of a separate verdict for each of:
137+
- **P-optimality** — was the allocation optimal against the
138+
inputs the DP actually had at the decision point, judged
139+
WITHOUT hindsight?
140+
- **Forecast error (P≠R)** — the Plan was optimal for its
141+
own forecast, but that forecast differed from what was
142+
Realized.
143+
- **Control/execution noise** — the plan's commanded rate
144+
was never actually achieved by the inverter.
145+
Blending these has repeatedly produced wrong conclusions in
146+
this repo.
147+
148+
A report that fails verification is an inconclusive result, not a
149+
diagnosis. Publish what you found and what is still missing via
150+
the IF YOU CAN'T REACH A CONCLUSION path below — do not dress it
151+
up as a root cause.
152+
123153
5. **Publish the analysis by RUNNING these two commands.** Writing
124154
the diagnosis as your reply does NOT publish it — you are running
125155
headless, nothing is watching your output, and a final message
@@ -169,7 +199,7 @@ jobs:
169199
ANALYSIS_EOF
170200
171201
gh issue comment ${{ github.event.issue.number }} --body-file /tmp/analysis.md
172-
gh issue edit ${{ github.event.issue.number }} --add-label needs-human-review
202+
gh issue edit ${{ github.event.issue.number }} --add-label needs-human-review --remove-label ready-for-analysis
173203
174204
Be honest — "I couldn't find the bug in the current code" is a
175205
valid outcome. Do not invent a root cause to fill the template.

scripts/quality-check.sh

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,68 @@ then
515515
ERRORS=$((ERRORS + 1))
516516
fi
517517

518+
echo ""
519+
echo "📋 Checking bot workflow context contract..."
520+
echo "-------------------------------------------"
521+
522+
# A workflow prompt must not tell its MAIN agent to read a `.claude/agents/*.md`
523+
# file. Those files are sub-agent system prompts: the sub-agent already has one
524+
# in full, so naming it as the main agent's REQUIRED READING loads a second copy
525+
# into a different context window, on every turn, for an agent that never runs
526+
# the procedure it describes.
527+
#
528+
# Stage 2 did exactly that with bess-analyst.md (25,681 B) and nothing consumed
529+
# it (#654). Its six steps are: get context, identify the current problem,
530+
# delegate, verify the cited file:line, publish, label -- no step applies a
531+
# "domain expertise checklist". Worse, the section of that file which could
532+
# plausibly serve as a judging standard (Separate Evidence from Claims) was
533+
# already restated inline in the sub-agent task the same prompt passes, so the
534+
# content was loaded three times over.
535+
#
536+
# The paired assertion matters as much as the removal: Stage 2 must still
537+
# DELEGATE. Dropping the read is the saving; dropping the delegation would gut
538+
# the stage, and the same "trim the prompt" instinct reaches for both.
539+
if ! python3 - <<'PY_CTX'
540+
import re, sys, pathlib
541+
542+
WF = pathlib.Path(".github/workflows")
543+
bad = []
544+
545+
for f in sorted(set(WF.glob("*.yml")) | set(WF.glob("*.yaml"))):
546+
text = f.read_text()
547+
for i, line in enumerate(text.splitlines(), 1):
548+
# Match the shape the regression actually takes: a NUMBERED entry in a
549+
# reading list, e.g. "3. .claude/agents/bess-analyst.md — checklist".
550+
# Deliberately narrow. A prose line that merely names the path is not
551+
# flagged, because the prompt now carries an explicit "Do NOT read
552+
# .claude/agents/bess-analyst.md" note and a gate that fought its own
553+
# documentation would be worse than one with a known edge. An
554+
# unnumbered "read <path>" instruction would slip through; that is an
555+
# accepted narrowness, not a claim of completeness.
556+
if re.match(r"^\s*\d+\.\s+\.claude/agents/", line):
557+
bad.append(f"{f}:{i}: main agent told to read a sub-agent definition: {line.strip()}")
558+
559+
analyze = (WF / "issue-analyze.yml").read_text()
560+
_, _, prompt_block = analyze.partition("prompt: |")
561+
if not re.search(r"subagent_type[\s:`'\"]*bess-analyst", prompt_block):
562+
bad.append("issue-analyze.yml: Stage 2 no longer delegates to the bess-analyst "
563+
"sub-agent -- that delegation IS the stage")
564+
565+
if bad:
566+
for b in bad:
567+
print(f" {b}")
568+
sys.exit(1)
569+
570+
print("✅ Bot workflow context contract intact (no sub-agent definition read by a "
571+
"main agent, Stage 2 still delegates)")
572+
PY_CTX
573+
then
574+
echo "❌ Bot workflow context contract violated"
575+
echo " A sub-agent's definition is its own system prompt. Do not also list"
576+
echo " it as the main agent's REQUIRED READING (#654)."
577+
ERRORS=$((ERRORS + 1))
578+
fi
579+
518580
echo ""
519581
echo "📋 Checking agent context budget..."
520582
echo "-------------------------------------------"

0 commit comments

Comments
 (0)