RFCs
One kind of design doc lives here. An RFC records a decision or proposal that shapes this codebase — the why and what we gave up, the parts code and docs can't carry.
Layout and naming
Every RFC has two axes, both encoded in its path — {lifecycle}/{class}/yyyy-mm-dd-topic-title.md:
- Lifecycle (the top-level folder) is the RFC's status, and an RFC moves between folders as that status changes:
proposed/ — proposals reviewed before implementation; not yet built (or only partly).
implemented/ — the decision shipped. The file records what was decided and what was rejected, and is kept current with what actually shipped: when the code later moves a file, renames a package, or changes a key/default, the RFC is updated in the same change to match (facts only — paths, names, structure — not the decision itself). See implemented/AGENTS.md.
rejected/ — the proposal was considered and declined. Kept for the record so the rejection isn't re-litigated.
- Class (the nested folder) is the kind of decision — see Classification below.
The date in the filename is when the topic was first proposed (per git history). Cross-references between RFCs use relative markdown links ([topic](../../implemented/architecture/2026-…-….md)) — never bare prose or numbers — so they are mechanically checkable and survive moves between folders.
Classification
Each RFC is filed under exactly one class — the kind of decision it records. The class is encoded in the path (the folder is the label, so a file's location declares its class) and the set is closed: scripts/verify-rfc-classification.ts rejects any folder outside the set and asserts this index lists every RFC under the heading matching its path. Adding a new class means amending that gate and this section, not just dropping a new folder. See the classification RFC for why the taxonomy is path-encoded and gated.
| Class |
What it covers |
feature |
A new user- or model-facing capability. |
bug-fix |
Corrects a defect or closes a gap a postmortem surfaced. |
simplification |
Removes code, behavior, or surface area without adding a capability. |
architecture |
A structural decision about the shipped source — how packages relate, what the runtime vocabulary is. |
process |
Tooling, policy, or workflow around the code — gates, the package manager, vendoring — not runtime behavior. |
testing |
Test infrastructure and strategy. |
The architecture / process line: architecture is about the source we ship; process is the surrounding tooling and workflow. (refactor is deliberately absent — it overlaps simplification, whose discriminator, "does observable behavior change?", already covers it.)
When to write one
Write an RFC when a decision is durable (it shapes the codebase beyond a single function or package), contested (there was a real alternative a reasonable engineer might have chosen), and surprising (a future reader would otherwise ask "why on earth is it done this way?"). A proposal for substantial future work starts in proposed/; a decision already made starts in implemented/. Pick the class folder that matches the decision (see Classification).
Do NOT write one for a mechanical or local choice (a variable name, a one-file refactor), for anything already enforced and explained by a gate or a convention in AGENTS.md, or for a still-provisional decision tagged TODO(...) in the code — record those as TODOs and promote to an RFC only once they settle. An RFC is never edited into a different decision: supersede it with a new one and cross-link. (Editing an implemented/ RFC to track where its already-made decision now lives — a moved file, a renamed package — is not a different decision and is required, not forbidden; see implemented/AGENTS.md.)
Proposed
Feature
Simplification
Architecture
Process
Testing
Implemented
Feature
Simplification
Architecture
Process
Testing
Rejected
Simplification
Architecture