Skip to content

docs: extend AGENTS.md with conventions mined from PR reviews - #12352

Draft
julian-risch wants to merge 3 commits into
mainfrom
docs/agents-md-from-pr-reviews
Draft

docs: extend AGENTS.md with conventions mined from PR reviews#12352
julian-risch wants to merge 3 commits into
mainfrom
docs/agents-md-from-pr-reviews

Conversation

@julian-risch

@julian-risch julian-risch commented Aug 14, 2026

Copy link
Copy Markdown
Member

Related Issues

  • none

Proposed Changes:

Our AGENTS.md documents the mechanics — hatch, tests, mypy, release notes — but none of the judgement reviewers actually apply. That knowledge lives only in review comments, so contributor agents rediscover it one review round at a time.

This adds 117 rules mined with pydantic/braindump (see Scaling open source with AI) from 2,746 PR review comments written by 14 deepset reviewers between 2025-07-01 and 2026-08-14. The hand-written sections of AGENTS.md are untouched; the mined rules follow underneath.

Layout. Rules are split by scope rather than piled into one file, so agents load them lazily — only when work touches that directory:

File Rules Loaded
AGENTS.md 70 always (~2.8k tok, down from 5.0k as one file)
test/AGENTS.md 17 when touching tests
docs-website/docs/AGENTS.md 11 when editing component docs
releasenotes/notes/AGENTS.md 8 when writing a release note
docs-website/AGENTS.md 6 when editing the docs site
haystack/hooks/compaction/AGENTS.md 4 when touching compaction

CLAUDE.md becomes a one-line @AGENTS.md import, and each directory with an AGENTS.md gets the same. Claude Code reads CLAUDE.md, not AGENTS.md (docs), so the import is what puts the guidance in context. Codex and Cursor read AGENTS.md directly, so it stays the single source of truth.

Verified with canary fixtures (claude -p, file tools disabled so the model can only answer from loaded context):

Setup Canary in context?
CLAUDE.md = @AGENTS.md yes
AGENTS.md alone, no CLAUDE.md no — Claude Code does not read AGENTS.md on its own
CLAUDE.md = the old prose, tools disabled no — nothing is preloaded
nested sub/CLAUDE.md = @AGENTS.md, cwd at root, after reading sub/thing.py yes — loads lazily, which is what the split relies on
nested, cwd inside sub/ yes

For fairness: with Read enabled the old prose form did work — the model went and read AGENTS.md in 3/3 runs. The import's advantage is that it is deterministic and costs no tool-call round-trip, not that the old form was broken.

Pipeline: download → extract (1 LLM call per comment) → embed & cluster → dedupe → place → group → generate. 2,643 candidate generalisations collapsed into 442 deduplicated rules, of which 117 survived scoring and review. Rules seen in only one PR are penalised, so what remains is what reviewers say repeatedly.

Handling the 3.0 boundary. The corpus has to span 13 months to be large enough — only ~220 team review comments exist since 3.0 shipped on 2026-07-20 — so most of it is 2.x-era, and a naive run would tell agents to use OpenAIGenerator. Rules were therefore filtered against the current tree before inclusion, deterministically rather than by asking a model:

  • A git diff of v2.31.0..HEAD over git ls-files (not the filesystem — the deleted component directories still exist on disk holding __pycache__) yields 75 removed symbols: 45 that moved to haystack-core-integrations, 30 gone entirely.
  • Each rule's identifiers are checked against that set. Verdicts: 395 current, 45 rewritten, 2 stale. Removed symbols appearing only in a rule's example never condemn the rule — the reviewer just happened to be looking at that component.
  • Rewrites keep the insight and drop the dead name, e.g. "For AsyncPipeline.stream()-style APIs, define, document and test the exact error semantics…""For Pipeline streaming APIs, …".
  • A recency weighting (v3-era share plus a 300-day half-life on the newest evidence) demotes rules whose only support is old.
  • Six hand-written rules state 3.0 conventions explicitly, so ToolInvoker and AsyncPipeline appear exactly twice in the file — in the two rules that warn against them.

Five rules removed and nine generalised on review. Clustering preserves whichever example the source review comments happened to discuss, so several rules read as though they only applied to one class or path — e.g. "preserve all runtime config, including Watsonx max_retries", or an assertion stated only for DocumentSplitter when core ships 11 splitter classes. Those now state the convention, keeping the specific case only where it illustrates it.

Four of the removals were about rule quality rather than scope. One was a genuine defect: "Use typing.Union[...] instead of | unions until Python 3.10+ is required" contradicted the rule three lines above it and is factually wrong — requires-python is already >=3.10, and 106 modules use PEP 604 unions against 12 using Optional[. The staleness gate could not catch it: it diffs symbols, and this is a convention change with no symbol footprint. The other three ("Revert review-rejected changes exactly", "Compute values once per function", "Delete lines explicitly marked to remove") carry no project-specific information.

How did you test it?

Markdown-only change, so no test suite applies. Verified instead:

  • No stale rule leaked. Every rule joined back to its verdict: 114 current, 8 rewritten, 0 stale, 0 unknown ids.
  • Symbol sanity. All 185 backticked identifiers and 5 file paths across the six files resolve in the current tree, except ToolInvoker and AsyncPipeline in the two deliberate warnings.
  • Stale bucket read by hand. Small enough (2 rules) to inspect individually; both genuinely name removed classes in their core text.
  • Extraction quality gated by a 50-comment smoke test before committing to the full run.

Notes for the reviewer

  • What to actually review: the rules themselves. Skim for anything you disagree with — a rule you would not enforce in review should not be here. The files carry no provenance metadata: judge each rule on whether it reads as a convention this project actually holds, not on where it came from.
  • The rules are weighted toward the highest-volume reviewers (sjrl ~1,000 actionable comments, anakin87 ~480), so they reflect their emphases more than the team average.
  • Verification limitation worth knowing: every recent PR is inside the training corpus, so I could not test against held-out reviews. Rules do generalise across PRs by construction, but "would this have pre-empted comment X" was not independently measurable.
  • The gate is deliberately biased toward keeping rules: a false stale silently deletes real knowledge, a false current is one dated bullet you will catch here.
  • Tooling (braindump patches, staleness gate, merge script) lives in a local braindump fork, not in this repo. Happy to push it somewhere shared if useful for re-running.

Checklist

  • I have read the contributors guidelines and the code of conduct.
  • I have updated the related issue with new insights and changes.
  • I have added unit tests and updated the docstrings. — documentation-only change.
  • I've used one of the conventional commit types for my PR title: docs:.
  • I have documented my code.
  • I have added a release note file — not user-facing; AGENTS.md is contributor documentation.
  • I have run pre-commit hooks and fixed any issue.

🤖 Generated with Claude Code

Our AGENTS.md documents the mechanics -- hatch, tests, mypy, release notes -- but
none of the judgement reviewers actually apply. That knowledge lived only in review
comments, so agents rediscovered it one review round at a time.

This adds 122 rules mined with pydantic/braindump from 2,746 review comments written
by the deepset team between 2025-07-01 and today, clustered and deduplicated across
PRs so that only repeatedly-enforced conventions survive.

Because the corpus is mostly Haystack 2.x era, rules were filtered against the
current tree before inclusion: a git diff of v2.31.0..HEAD identifies the 75 symbols
removed in 3.0, and any rule whose text depends on one is dropped or rewritten to
its 3.x equivalent. No rule naming a removed API survives except the handful that
deliberately warn against it.

Each <!-- rule:N --> marker traces back to its source review comments.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
haystack-docs Ready Ready Preview Aug 15, 2026 9:51am

Request Review

Review feedback on the single-file version:

- Nested layout, as braindump generates it. Rules about tests, release notes and
  the docs site now live next to the code they govern, so agents load them only
  when work touches those directories. The root file drops 4,993 -> 2,789 tokens
  (-45%) with more relevant guidance loaded per session, not less.
- CLAUDE.md now imports AGENTS.md via '@AGENTS.md'. Claude Code reads CLAUDE.md,
  not AGENTS.md, and the previous prose ('read the AGENTS.md file...') only worked
  if the model chose to act on it. The import inlines the content at session start.
  Each directory with an AGENTS.md gets the same one-line CLAUDE.md, since nested
  memory files are picked up lazily.
- Dropped the inline <!-- rule:N --> markers, ~11% of every file for traceability
  nothing reads at runtime.

Rules removed:

- 'Use typing.Union[...] instead of | unions until Python 3.10+ is required'.
  This was wrong and contradicted the rule three lines above it: requires-python
  is already >=3.10, and 106 modules use PEP 604 unions against 12 using Optional[.
  The staleness gate could not catch it -- it diffs symbols, and this is a
  convention change with no symbol footprint.
- 'Revert review-rejected changes exactly', 'Compute values once per function and
  reuse them', 'Delete lines explicitly marked to remove' -- generic developer
  hygiene carrying no project-specific information.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d rules

The <!-- braindump:begin/end --> comments existed so the mined block could be
replaced in place on a re-run. That anchor now lives in the generator instead, so
the shipped files carry no scaffolding.

Clustering preserves whichever example the source review comments happened to
discuss, which leaves some rules reading as if they only apply to one class or
path. Generalised, with the specific case kept only where it is illustrative:

- 'preserve all runtime config, including Watsonx max_retries, ...' -> every
  constructor argument that affects runtime behaviour must round-trip.
- 'use WATSONX_API_KEY for Watsonx components' -> default each Secret from the
  provider's conventional env var. WATSONX_API_KEY is one of ~10 such variables
  in this repo (COHERE_API_KEY, NVIDIA_API_KEY, JINA_API_KEY, ...).
- 'preserve Elasticsearch bulk write/delete try/except behavior' -> preserve
  documented bulk write/delete error behaviour. DocumentStoreError is used by 17
  document stores, not just Elasticsearch.
- 'update ... Google GenAI model names and RagasEvaluator ragas.metrics.collections
  usage' -> refresh docstrings, cookbooks and integration docs when model names or
  provider APIs change.
- 'especially in integrations/mcp/src/haystack_integrations/tools/mcp/' -> dropped
  the path; the warm_up() rule is repo-wide.
- 'Assert ... in DocumentSplitter tests' and the RecursiveDocumentSplitter overlap
  rule -> stated for splitters generally; core ships 11 splitter classes.
- Test-layout and pipeline-components docs rules trimmed to the convention.

Removed: 'Update integrations/amazon_bedrock/tests/ with generator changes', which
is entirely about one integration, and 'Label agents-1 docs sections clearly',
which is vague and pinned to a Docusaurus slug.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant