The frontmatter exists to be machine-read. Two shapes this template
suggested do not parse, so any ADR that copied them dropped silently out of
every "what is still open?" query while looking correct:
supersedes: - a bare dash is a YAML sequence entry
deciders: pending: Mathias the unquoted second colon reads as a
nested mapping
Both were found live during an estate-wide ADR survey on 2026-09-05:
openbanking-api ADR-0001 (the one genuinely open decision in the estate --
invisible to the survey until fixed) and infra ADR-0019, each copied
verbatim from here.
Template corrected to `null` and a quoted `"pending: <name>"`, plus a short
section after the fence explaining why and giving a one-line yaml.safe_load
check to run before committing. The explanation is deliberately OUTSIDE the
fenced block so it is not copied into the ADR being written.
10 KiB
name, surfaces, summary, use_when, description
| name | surfaces | summary | use_when | description | ||
|---|---|---|---|---|---|---|
| adr |
|
Architecture Decision Records — house format, and how to record a decision you cannot make yet | Recording an architectural decision; revisiting one; or deciding between options where the right answer genuinely is not knowable yet | Write an Architecture Decision Record in this estate's format — YAML frontmatter, Status/Context/Decision/Consequences/Validation — including the case where the tradeoff is genuinely context-dependent and forcing a resolution would be worse than preserving the tension. Use when recording or superseding an architectural decision, or when a decision keeps getting re-litigated because nobody wrote down why. |
ADR
Overview
An ADR is the record of a decision — the artifact someone reads in eight months when they are about to undo it without knowing why it exists. It does not confer the decision; a person with authority does that, and this skill never is that person.
Two things make one useful, and neither is the prose quality: a format consistent enough to grep across repos, and an honest account of what was not decided.
Before you write
Both of these change what goes in the ADR, so they are not cross-references, they are steps:
lineage— was this decided before and reversed? The reason it was dropped is usually the most valuable sentence in your Context, and if an earlier ADR exists, yours supersedes it rather than ignoring it.grill-me(Full Grill) — find the hole now. It is cheaper here than in the Consequences section of the ADR that supersedes yours.
When to Use
- Any choice where a reasonable alternative existed and was not taken
- Superseding or amending an earlier ADR (never edit the old one — see Status)
- A decision that keeps getting re-litigated, which means the reasoning was never recorded
- A tradeoff that is genuinely context-dependent — see Preserving a productive tension
Do not use for: implementation choices with no alternative worth naming, or anything a code comment answers. An ADR per function is an ADR nobody reads.
The format
Where it goes: docs/adr/NNNN-kebab-slug.md, zero-padded to four digits, numbered
sequentially within the repo. Frontmatter you cannot locate is not greppable, and half
the value of the format is that docs/adr/* is a known path in every repo here.
The number is repo-local. cad ADR-0002 and infra ADR-0002 are different
documents. When referring across repos — including in supersedes — qualify it:
cad#0002. Within a repo the bare number is fine.
Frontmatter first. It exists so the corpus is machine-readable — "which ADRs does 0004
supersede", "what is still proposed" — and a bespoke header block, however well
written, cannot answer those.
---
adr: 0004
title: <the decision, as a sentence — not "database choice">
status: proposed | accepted | superseded | amended
date: 2026-08-29
deciders: <who accepted it, by name — or "pending: <name>" (QUOTED) while proposed>
supersedes: <adr number, or null>
---
# ADR-0004 — <same title>
## Status
**The frontmatter is authoritative for status and date.** This section exists for the one
thing the frontmatter cannot hold: *why* it moved — what superseded it and on what
evidence, or what accepting it was contingent on. If it would only restate the YAML,
delete it.
## Context
The forces. What is true that makes this a decision rather than a default — constraints,
the failure that prompted it, what was tried. Name the unknowns explicitly; a decision
made under uncertainty is not weaker for saying so, it is auditable.
## Decision
One sentence of what was decided, then why *this* option rather than the named
alternatives. Address the strongest case against, not the weakest.
## Consequences
**Positive:** what this buys.
**Negative / watch:** what it costs, and the specific thing to watch that would tell you
it was wrong. "Some added complexity" is not a consequence; "the event schema is now the
coupling surface between cad and every executor — version it deliberately" is.
## Validation
How you will know. A link to the experiment or the metric that proves or kills it, and
the condition that fires a revisit. An ADR with no validation section is a preference
with a template around it.
The frontmatter must actually parse
It exists to be machine-read, so a block that does not load is worse than no block at
all: it drops that ADR silently out of every "what is still open?" query, while looking
correct. Two shapes this template used to suggest are invalid YAML, and both were found
live in the estate on 2026-09-05 — openbanking-api ADR-0001 and infra ADR-0019, each
copied verbatim from here:
| Wrong | Why | Right |
|---|---|---|
supersedes: - |
a bare dash is a YAML sequence entry, not a string | supersedes: null |
deciders: pending: Mathias |
the second, unquoted colon reads as a nested mapping | deciders: "pending: Mathias" |
Check before committing:
python3 -c "import yaml,sys;yaml.safe_load(open(sys.argv[1]).read().split('---')[1]);print('ok')" docs/adr/0004-*.md
Preserving a productive tension
Some tradeoffs are not unresolved because nobody thought hard enough. They are context-dependent, the context is not settled, and picking now means picking blind. Forcing resolution there produces a confident record that is wrong half the time.
Record the tension instead, in the Decision section:
## Decision
Provisional: **<option A>**, on this reasoning: <what makes it safe to pick now>.
<The strongest case for the option you did NOT take, and why it does not win today. The
plain format demands this and a preserved tension does not get an exemption — the whole
point is that the loser is still live.>
### The tension, preserved
- **Option A** — <what it assumes, what it costs>
- **Option B** — <what it assumes, what it costs>
**What we do not know:** <the variable that decides it>
**Preservation strategy:** <what we build now so BOTH stay reachable. This is the
technical content of the ADR, not a one-liner — enumerate it. It is usually the part both
options need anyway, which is often most of the work: the schema, the interface seam, the
idempotency key. Name each piece and say which option it keeps alive.>
**Resolution triggers:** <plural, and one per unknown. Two independent unknowns do not
collapse into one condition, and a trigger may exist that neither unknown implies.>
Two rules keep this from becoming an excuse:
-
A preserved tension still ships a decision. "We will decide later" is not one. Prefer the option whose correctness does not depend on the unknown, say so, and mark it provisional.
When no such option exists — and often none does, because a tension is productive precisely when both options are hostage to the same unknown — do not manufacture a reason that one of them is. That reads as reasoned and will not be re-examined, which makes it worse than an honest arbitrary pick. Fall back, in order: the option that is cheaper to reverse; the one with the lower switching cost given your preservation strategy; the one already built. Then say that is why you picked it. "Chosen because it is the cheaper mistake" is a real justification and an auditable one.
-
The resolution trigger must be observable by someone who is not you. "When it becomes a problem" is not a trigger. "When executor count passes five and they are all long-lived services in one cluster" is. A trigger nobody can evaluate is a decision that quietly becomes permanent.
cad's ADR-0001 does this informally under "Known refinements (recorded, deliberately
not built yet)" — sovereignty deferred to a 3-class model "until a client actually opts
into an EU provider". That clause is the pattern: option, cost, and the event that
forces the choice.
Status discipline
- Never edit an accepted ADR to change its decision. Write a new one and set
supersedes. The record of what was believed, and when, is the whole value. - Amend for corrections that do not change the decision (a broken link, a clarified consequence). Note the amendment inline with its date.
proposedis not a parking space. An ADR that sits proposed for a month with no named decider is a decision nobody is making; either accept it provisionally or close it as declined. With a named decider it is a decision waiting on a person, which is a different thing and is chased rather than closed.
Anti-patterns
| Anti-pattern | Why it fails |
|---|---|
| Consequences that are all positive | Then it was not a decision, it was an announcement |
| "We chose X because it is better" | Names no alternative and no force; unfalsifiable |
| Written after the code shipped | Reverse-engineers a justification. Record the reasoning while it is still contested |
| One ADR amended repeatedly | Destroys the history the format exists to keep |
| No Validation section | Nothing distinguishes a good decision from a lucky one |
| A tension "resolved" by picking arbitrarily | The next person re-litigates it, having lost the reasoning |
Escalation
Writes a file, so it acts — but it decides nothing.
Stop and hand back when: the deciders are not you — which, for an agent, is always. An ADR records a decision someone with authority made; writing one to create that authority is how a preference acquires a document number.
So the deliverable is a draft marked proposed with the person who must accept it named
in deciders as pending: <name>, and that hand-back stated in the response, not left
implied. Naming the decider is the escalation, not a formality — an unnamed one is how a
proposal sits for a month.
That is the intended state for an agent-authored ADR, and it is not the parking-space
failure below: a proposed ADR with a named decider is waiting on someone; one without is
waiting on nobody.
Also stop when the Context cannot be written without inventing a constraint. A missing force means the decision has not been thought through yet, and an ADR built on an invented one is worse than none: it will be cited.
Cross-References
lineageandgrill-me— see Before you write; they change the content, so they are steps rather than references.spec-driven-dev/feature-spec— an ADR is the why; a spec is the what. If you are describing behaviour, you are writing a spec.