Agent Notes & AGENTS.md: Discipline for Building AI with AI
Four-state design notes and coding standards written for AI — your team can copy them immediately.
Play first, then we'll talk. Below are the four folders under DSH's .agents/notes/ directory; the counts are real notes from the local snapshot. Hit Play to watch a real note move from proposed to implemented and into archived; switch to the Rejected route to see another note get vetoed and frozen. The middle row is the format-gate health check — who's guarding each step is crystal clear.
Ideas waiting to be tested
Living docs synced with the code
A vaccine against repeating the same mistake
A fossil layer you must not touch
DSH is a repo where AI writes code at scale. Every AI session starts fresh, and humans forget why a plan was rejected three months ago. So the same bad idea gets proposed again, the same code gets refactored back, and docs written once rot because nobody updates them.
DSH's answer is two artifacts. A set of design notes that move through states — Agent Notes — capturing what code and docs can't hold: why we did it this way, and what we abandoned. And a code of conduct written for AI — AGENTS.md — turning the repo's hard rules into standard instructions the model loads every session.
Start with the notes. Each note's path is its full identity: {lifecycle}/{class}/yyyy-mm-dd-topic.md. Lifecycle is the top-level folder — three active states proposed, implemented, rejected, plus an archived layer. Class is a nested folder — feature, bug-fix, simplification, architecture, process, testing — a closed set; anything else is rejected by the gate. Local snapshot counts: 25 proposed, 506 implemented, 11 rejected, 142 archived, each with a Chinese counterpart file and a consistency record.
Then the hard rule, on line 122 of the root AGENTS.md: nontrivial changes must add or update at least one note in the same PR. What counts as nontrivial? Behavior, architecture, cross-package contracts, process tooling, on-disk formats, protocol formats, or any decision maintainers may revisit later. Only purely mechanical local edits are exempt. Notes ride the same review and merge as the code — so there is no “ship code first, docs later.”
Every note must also have an Alternatives considered section listing each real alternative and why it lost. Line 115 of .agents/notes/README.zh.md puts it in black and white: “Recording a decision without recording what it beat is an invitation to argue it again.” That section is the core of anti-amnesia: next time someone (or an AI) proposes the same plan, open the note and see who it lost to, and why.
State is the folderChanging a note's state means moving the file and editing the Status line — both in the same change, cross-checked by the gate. When proposed becomes implemented, the Proposal section must be rewritten as a present-tense Decision.
rejected is a vaccineVetoed proposals are frozen in place; the conclusion sits on the Status line where you see it first. Retention has a bar: keep it only if the rationale still blocks a tempting, high-impact mistake — otherwise delete all three files together.
archived is a fossilImplemented notes whose guidance value has faded move into the archive layer and freeze forever: no edit, translate, move, or delete — manifest is append-only. History is evidence; altered evidence cannot testify.
The system doesn't stop at documentation. scripts/verify-agent-note-format.ts is 94 lines, part of the doc-sync gate, run on every CI. Below is its rule table: Status-line grammar and required sections per lifecycle.
const STATUS: Record<string, RegExp> = {
proposed: /^Status: proposed$/,
implemented: /^Status: implemented$/,
rejected: /^Status: rejected — .+$/,
}
/** Required `##` headings per lifecycle, beyond the universal `## Problem` opener. */
const REQUIRED: Record<string, string[]> = {
proposed: ['## Proposal', '## Acceptance criteria', '## Risks'],
implemented: ['## Decision', '## Consequences'],
rejected: ['## Proposal'],
}
scripts/verify-agent-note-format.ts, verified on 2026-08-13. Code blocks keep the original source text.Watch the rejected regex: the Status line must carry a one-line rejection reason — a bare rejected fails. A few lines below the rule table sits a BANNED_IMPLEMENTED regex (line 36): implemented notes may not use proposal-flavored headings like Proposal, Plan, Migration plan, or Acceptance criteria, because an implemented note describes present-tense facts — the plan should already have become a decision.
Another counterintuitive design: these 684 active and archived notes have no index. The tree itself is the inventory; retrieval is folders plus full-text search. Want an INDEX.md? The structure-check script walks the .agents/notes/ root watching for that filename and errors the moment it appears — the message is absolute: a centralized Agent Note index is forbidden; browse the lifecycle/class tree or search the whole repo. The same loop also nails the lifecycle set shut — any unknown top-level folder reports “unknown lifecycle,” because a note in the wrong place would vanish from traversal.
Source: the structure-check loop in scripts/agent-note-tree.ts lines 44–56, verified on 2026-08-13.
Why ban the index? A centralized index is the doc that rots fastest: every new note needs an update, and one miss starts the lying. Delete the index and the chance of rot drops to zero. The design rationale is itself a note at implemented/process/2026-07-19-remove-generated-agent-note-index.md.
Now the other half: the root AGENTS.md, 149 lines, loaded every AI session. Its Conventions section is worth copying line by line. Here are four of the most representative — all from the root AGENTS.md:
- Trust the type boundary (line 115). On typed same-process boundaries, trust TypeScript — don't add runtime checks and defensive tests for values the static interface already guarantees. Put validation only on real boundaries: config parsing, model-returned JSON, disk files, process and protocol edges.
- No hard-coded knobs in plugins (line 112). Choices that vary by deployment must be editable fields in config; a
DEFAULT_*constant does not count as configurable. Protocol constants and security invariants are exempt — those should be welded shut. - Misconfiguration fails loudly (line 113). If a mismatch can be found at load time, throw at load time; otherwise throw at the earliest moment you can parse it. Never silently skip a missing reference.
- Empty catch must be signed (line 118). Original: “An empty
catchnames what it swallows and why nothing else can reach it; keep thetryto one statement.” Name what exception was swallowed and why nothing else can reach here — and the try block may hold only one statement.
These items share one trait: each is checkable. Either a gate can verify it, or a reviewer can spot a violation in a glance. No slogans like “code should be elegant” that mean nothing once written.
Docs have gates too. Word budget: root AGENTS.md stays under 1600 words; over that verify-doc-budgets goes red — move content to the right layer or compress (docs/AGENTS.md line 57). One fact, one home: each rule has a single authoritative source; elsewhere only links (lines 15–17). Bilingual pairing: every doc is English, Chinese, plus an .i18n.yaml — three files; the record stores both sides' git blob hashes, and changing either side without reconfirming the pair turns the gate red (docs/i18n/README.md lines 10–11). All of this is driven by pnpm run doc-sync; the full list lives in scripts/run-gates.ts.
Claude Code: closed source; decision records scatter across blogs, release notes, and code comments. In the recovered source you find valuable notes — like the circuit-breaker comment with BigQuery production data at autoCompact.ts lines 67–70 (see the Compaction Dual Paths lesson). Those are mini decision records embedded in code, and the quality is solid. But they have no state, no format gate, no lifecycle retrieval — and rejected plans are basically nowhere to find.
Grok Build: based on the verified local snapshot, the repo has no equivalent design-notes directory; rationale lives mainly in module comments and commit history. Module comments are fine (each mod.rs opens with a one-line duty statement), but the rejected-plan dimension is missing. DSH's 11 rejected notes are unique among the three.
How should your team copy this? Three steps. 1) Create notes/ with four folders; filenames carry date and topic. 2) Lock the format: title, Status line, Problem opener, Alternatives considered required — write a ~15-line checker from the rule table above and hang it on CI, half a day of work. 3) Put three to five machine- or review-checkable hard contracts in your AGENTS.md, starting with which changes must ship a design note. Don't get greedy on count — DSH grew from a short rule set too.
Write a minimal AGENTS.md for your repo
Only five rules. Each under three lines; each either scriptable or judgeable by a reviewer in ten seconds; at least one must say which changes require a design note. Then test: show the five to a teammate and ask which ones can't be enforced. Delete and rewrite the ones that can't.
Walk through a noncompliant lifecycle
Someone git mvs a proposed note straight into implemented/, without editing the Status line or rewriting Proposal as Decision. Against the rule table at lines 22–33 above, list every error verify-agent-note-format would raise. Then one layer deeper: why does the gate require those two edits and the file move in the same change?