Repository navigation
fix(pipeline): stop the agent files telling the model it has tools - #451
Conversation
The generation step returned a transcript of itself using tools instead of a
page:
I'll research the GitHub repo and peer pages before writing.
<tool_call>{"name": "Glob", "arguments": {"pattern": "docs/.../*.mdx"}}</tool_call>
<tool_response>docs/learning-how-tos/cookbook/context-aware.mdx ...</tool_response>
build_authoring_system_prompt pastes .claude/agents/docs-writer.md verbatim into
a plain Messages API call. That file is an agent definition: its frontmatter
declares `tools: Read, Write, Edit, Grep, Glob, Bash, Agent`, its body says to
search with Grep and Glob and to delegate to the diataxis agent, and it ends
with "Return the complete draft as file content (using Write or Edit)". The call
passes no tools, so the model did the only thing those instructions allow and
faked them. diataxis.md and editorial-reviewer.md declare tools too, so rework
and review shared the exposure.
- load_agent() strips the YAML frontmatter from every agent file.
- NO_TOOLS, appended after the guidelines so it wins, cancels what the bodies
still assume: no file reading, no Grep/Glob, no delegation, no tool calls, and
"using Write or Edit" means put the content in the reply.
- generate.py and rework.py check the reply's shape and spend one corrective
turn when it's wrong, rather than writing an unusable draft. rework validates
the `--- SPLIT:` markers for multi-target runs and frontmatter otherwise.
Salvaging a page out of a transcript is deliberately not attempted: a faked tool
result can quote another page's frontmatter, so cutting at the first `---` risks
promoting spliced content.
Assistant prefill would have been stronger, but claude-sonnet-4-6 rejects it:
"This model does not support assistant message prefill."
Verified by running the pipeline end to end on the one live gap
(thin_page for the google-sheets cookbook page, which reproduced the transcript
before this change): generate -> evaluate (0 errors, 0 warnings) -> review
(clean, 5 fixes applied) -> promote wrote the canonical .mdx. The promoted page
was reverted; it was a verification run, not content to ship.
Tests: pipeline/test_prompts.py (14), including the real transcript as a case.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
…mport Carries over the parts of #450 that #479 didn't cover: - detect_gaps: pages with child pages are exempt from the thin-page check. breaking-changes-change-notices is short because its content sits on its children; padding it out is the wrong fix. - generate: load_existing_page() raises when the page a gap edits is missing or empty, so a failed read can't become "write this page from scratch". - generate: import the Anthropic SDK only when a client is built, so --dry-run and the tests run without it. CI installs pyyaml for the new tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
57686ed to
fbd2f76
Compare
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
kabraham13
left a comment
There was a problem hiding this comment.
Approving. This fixes the root cause (agent definitions that declare tools being pasted into a no-tools API call), the corrective turn and the decision not to salvage a page out of a transcript are well reasoned, and the live run on the real gap is good evidence. CI is green and it's rebased on current main.
Three non-blocking suggestions:
- The corrective-turn path has no test. The tests cover
looks_like_mdxand the retry prompt text, but nothing callsgenerate_content(or thereworkflow) with a fake client to check that a bad first reply produces the user, assistant, user sequence and returns the second reply. The sequence looks valid on a read, so a small fake-client test would pin it. - A second bad reply isn't re-checked. After the retry, the reply is returned as is. If the model returns a transcript twice, it gets written as a draft. I'd expect
evaluateto catch it and fail the run, but it would be worth either confirming that or failing fast ingenerate_content. pip install pyyamlin the test workflow is unpinned. The old step was stdlib only. A pin, or a small requirements file, keeps CI reproducible.
The two gaps called out in the description (nothing gates on mint broken-links, and no read tools for generation) are worth tracking as follow-ups, but they don't need to block this.
Review follow-ups on #451: - generate_content raises if the reply is still not a page after the corrective turn. main() records that as an error for that gap only; writing it as a draft would fail evaluate for the whole run. - rework exits with an error in the same case instead of writing the reply. - Fake-client tests for generate_content: one call on a good reply, a user/assistant/user retry on a bad one, a raise on two bad ones, and no shape check for descriptions. - Pin pyyaml in the test workflow. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
|
Thanks, all three addressed in 343effe:
Agreed on tracking the broken-links gate and read tools for generation as follow-ups. |
kabraham13
left a comment
There was a problem hiding this comment.
Re-approving after 343effe. The fake-client tests pin the corrective-turn sequence (user, assistant, user), a second bad reply now raises so it only errors that gap instead of failing evaluate for the whole run, and rework exits 1 in the same case. The pyyaml==6.0.3 pin is in, and CI is green.
Summary
Running the pipeline end to end (which I should have done before opening #450) turned up a third bug. The generation step didn't return a page — it returned a transcript of itself using tools:
build_authoring_system_promptpastes.claude/agents/docs-writer.mdverbatim into a plain Messages API call. That file is an agent definition:tools: Read, Write, Edit, Grep, Glob, Bash, AgentThe call passes no tools, so the model did the only thing those instructions allow and faked them.
diataxis.mdandeditorial-reviewer.mddeclare tools too, sorework.pyandreview.pyshared the exposure.This is pre-existing and intermittent — #447's pages came out as valid
.mdxthrough the same assembly. #450 surfaced it here because the prompt now carries the page's real content, which mentions a GitHub repo, and that nudged the model toward "research first".What changed
load_agent()strips the YAML frontmatter from every agent file.NO_TOOLSis appended after the guidelines, so it wins: no file reading, no Grep/Glob, no delegation, no tool calls, and "using Write or Edit" means put the content in the reply.generate.pyandrework.pycheck the reply's shape and spend one corrective turn when it's wrong, instead of writing an unusable draft.reworkvalidates the--- SPLIT:markers on multi-target runs and frontmatter otherwise.Two decisions worth reviewing:
---is tempting and wrong: a faked tool result can quote another page's frontmatter, so salvage risks promoting spliced content. A corrective turn costs one call and can't mangle anything.---makes a preamble structurally impossible, but the API rejects it:This model does not support assistant message prefill.I found that by running it.Verification
Full run on the one live gap — the
thin_pageforcookbook/google-sheets, the same gap that produced the transcript:.mdxdraftdocs/learning-how-tos/cookbook/google-sheets.mdxrun.pyexited 0. The promoted page was reverted — this was a verification run, not content to ship.New
pipeline/test_prompts.py— 14 tests, including the real transcript as alooks_like_mdxcase and a check thatNO_TOOLSis positioned after the guidelines it overrides. 55 tests total inpipeline/.Two things this run exposed that I did not fix
promote.pyrunsmint broken-linksand prints the result, but doesn't fail on it. The generated page linked to/api-reference/translate/translate-text, which doesn't exist, and that sailed through both gates.Code.gs,Sidebar.html,appsscript.json) — plausible, unverified, and exactly what a reviewer has to check by hand. The docs-writer instruction to research peers exists for a reason; suppressing it doesn't give the model the knowledge, it just stops it pretending. Worth deciding whether the pipeline should get real read tools for generation.Carried over from #450
#479 fixed the gap paths. These parts of #450 it didn't cover are in the second commit:
breaking-changes-change-notices.load_existing_page()raises when the page a gap edits is missing or empty, so a failed read can't turn "expand this page" into "write it from scratch".generate.py, so--dry-runand the tests run without the SDK. CI now installspyyaml, because the new tests importdetect_gapsandgenerate.Verified after the rebase: 52 tests pass.
generate.py --dry-runruns in a venv with onlypyyamland leaves one generative gap (thin_pageforcookbook/google-sheets).supported-languagesis skipped as already changed in #403, andbreaking-changes-change-noticesis no longer flagged.🤖 Generated with Claude Code