DecisionsGENERALSCALE-SPECIFICCONTESTED

Decision Records

Context, options, decision, why, consequences, revisit trigger. Short enough to write in fifteen minutes, and worth writing because in two years the code will not say what was known when it was chosen.

The requirement, the obvious build, and why it breaks

Every lesson starts where the work starts: someone asked for something, and the first implementation that comes to mind survives until the requirement changes.

The question

How do I make a design decision survivable by people who were not in the room, without producing documentation nobody reads?

The requirement

A new engineer asks why report state is in Postgres with a polling worker rather than in the queue system the rest of the company uses. Nobody remaining on the team was in that meeting.

The obvious build

The code documents itself. If you want to know why we use Postgres for this, read the worker; it is obvious.

Why it breaks

Code records what was decided and never why, and the why is the part that determines whether the decision still applies. The polling worker is visible; "we rejected the queue because at-most-once mattered more than latency" is not.

How it breaks as requirements change
  • Code records what was decided and never why, and the why is the part that determines whether the decision still applies. The polling worker is visible; "we rejected the queue because at-most-once mattered more than latency" is not.
  • Absent that, the new engineer has two indistinguishable hypotheses — considered and rejected, or never thought of — and the safest-looking action is to leave it alone, so bad decisions calcify exactly as well as good ones.
  • The alternative failure is equally common: the reasoning goes into a chat thread, and a search finds four contradictory threads and a person who has left (Knowledge Sharing).
  • And when the decision does get revisited, the argument restarts from zero, usually with a different set of participants and no memory that the same trade-off was already worked through (The Trade-off Matrix).
RequirementConstraintsInvariantsResponsibilitiesBoundariesInterfacesStateDependenciesFailureImplementationTestsFeedbackEvolution

What limits the solution, and what must never stop being true

This domain leads with these two. A design that ignores its constraints is not a design, and an invariant nobody named is one nothing is protecting.

Constraints
  • Any process heavier than fifteen minutes per decision will be abandoned within two quarters, which is the observed fate of most documentation practices.
  • The record has to live somewhere an engineer will find it while working, not in a wiki nobody opens.
  • Some decisions are made in a chat thread and never had a meeting at all.
Invariants
  • A record states what was known and believed at the time. It is never edited to reflect what turned out to be true — superseding is how it changes (Documentation Decay).

Who owns what, and where the seams fall

Responsibilities decide boundaries; boundaries decide what an interface has to say.

Responsibilities
  • Whoever makes the decision owns writing it down, in one pass, at the time. Delegating it produces a record of what someone else thinks happened.
  • The record owns the *reasoning*, not the design. What the code does belongs in the code; what was believed belongs here (Comments).
  • Whoever supersedes a record owns linking the new one to the old, so that the history reads as a sequence rather than as a contradiction.
Boundaries
  • A record covers one decision. "Our architecture" is not a decision; "job state in Postgres rather than the queue" is.
  • It lives with the code it constrains — in the module directory, in the repository — because that is where someone will be standing when the question occurs to them (Docs Close to Code).
  • It is not a design document. If it is longer than a page, the design belongs in a design document and the record should link to it (RFCs).

The whole record

Fifteen minutes, one page, six headings. Longer than this and it will not be written; shorter and it does not answer the question that makes someone go looking for it.

The Options and Revisit Trigger sections carry most of the value. Options is what tells a future reader that an alternative was considered rather than missed; Revisit Trigger is what turns the record from an explanation into something that can expire.

docs/decisions/0031-job-state-in-postgres.md
1# 0031 — Scheduled job state in Postgres, not the shared queue
2
3Status Accepted, 2025-03-11. Supersedes nothing.
4
5Context ~2k scheduled runs/day. Three part-time owners, on call for
6 three services. Postgres is already run and backed up here;
7 the shared queue is owned by the platform team.
8
9Options (a) Postgres table + polling worker
10 (b) Shared queue with delayed delivery
11 (c) Managed workflow service
12
13Decision (a). Claim a run with a transactional UPDATE ... RETURNING.
14
15Why At-most-once matters more than latency here: a duplicated
16 financial report is a customer-visible incident, a 15s delay
17 is not. (b) gives at-least-once and would need our own
18 dedupe table anywaywhich is (a) plus a queue. (c) was
19 rejected on migration cost, not capability.
20
21Conseq. We poll every 15s and burn a query per poll. Retry/backoff
22 is ours to write and to get right. Multi-step workflows
23 would be painful; we do not have any.
24
25Revisit >5k runs/day (dashboard: reports.scheduled_runs_daily),
26 OR any requirement for a multi-step schedule,
27 OR the platform team offers exactly-once delivery.

Three things are doing the work. "Rejected on migration cost, not capability" stops a future reader assuming the managed service could not do the job. The consequences are stated as accepted costs rather than benefits. And each revisit trigger names something observable — one of them is a dashboard metric, which means this record can expire without anyone remembering it exists (Revisit Triggers).

What each field is for, and what breaks without it

Every field in the template earns its place by preventing a specific failure that happens when it is missing. That is the test for adding a seventh: name the failure it prevents, or leave it out.

The most commonly omitted two are Options and Revisit Trigger, and they are the two that carry the decision forward in time. Without them a record explains the present and cannot survive it.

FieldThe question it answersWhat happens when it is missing
ContextWhat was true at the time — volume, team, stack, deadlineThe decision reads as arbitrary once the constraints change, and is either cargo-culted or overturned without anyone noticing what shifted (Constraints Are Part of the Design)
OptionsWhat else was on the tableA future engineer cannot tell "considered and rejected" from "never thought of", so the safe move is to change nothing
DecisionWhat we are actually doingRare, but records that describe a discussion and never state the outcome do exist, usually written by committee
WhyThe reason this option beat the others, on which axisThe record becomes a note that a meeting happened. This is the field that answers the question people ask (The Trade-off Matrix)
ConsequencesThe costs we accepted, and what is now harderThe costs are rediscovered as surprises, and the decision gets blamed for something it explicitly bought
Revisit TriggerWhat evidence would make this wrongThe decision becomes folklore — permanent, unexaminable, and defended by people who were not there (Revisit Triggers)

The practice failing while it looks healthy

A directory full of records is not evidence that decisions are being recorded. The recognisable failure is a set of well-formatted documents about choices nobody was going to argue with, written after the code was merged.

It is worth naming because it survives audits. The template is followed, the count goes up, and the one decision anybody will ask about in two years — the contentious one, made in a heated thread — is the one with no record.

smellRetrospective decision recordsThe archive that documents nothing

looks like Records whose Options section lists one real option and two that were never seriously considered; a Why field that restates the Decision in different words; dates that cluster on the day before an architecture review; and no record at all for the two most-argued choices in the codebase.

suggests The practice is being performed for an audience rather than used. Records are being reverse-engineered from merged code, which means they contain only what the code already says and none of what was believed at the time.

fix Attach the record to the moment of the decision rather than to a review: require one on any pull request that adds a dependency, a data store, or a public boundary, and accept a five-line record for a small decision. Then check the reverse direction — take the three most argued-about aspects of the codebase and ask which have records. That count is the one that matters (A Review Checklist Worth Reading).

when this is fine Writing a record after the fact is genuinely right in one case: an important decision was made informally months ago, the reasoning still exists in someone's head, and capturing it before that person leaves is strictly better than nothing. Mark it as reconstructed and date it honestly — the failure is not the retrospective record, it is the retrospective record pretending to be contemporaneous.

How to build it

Most important first.

  • Six fields, in this order: Context, Options, Decision, Why, Consequences, Revisit Trigger. The order matters — writing Why before Options tempts you to describe only the option you chose.
  • Write Options honestly, including the one that nearly won. A record with one option is a record of a preference, and it will not survive being challenged.
  • State Consequences as costs you have accepted, not as benefits. "We now poll every fifteen seconds and will need to revisit at higher volume" is useful; "this gives us reliability" is not.
  • Make the Revisit Trigger observable. "If volume exceeds five thousand runs a day" can be checked by a dashboard; "if this becomes a problem" cannot (Revisit Triggers).
  • Immutability by convention: records are superseded, never edited. The value of the archive is that it shows what was believed at each point, which is destroyed by tidying it (Architecture Decision Records).

What the next change costs

The field this whole domain exists for. A structure is only better if it makes the change after this one cheaper — and it is worth saying which changes it does not help.

Cost of the next change
  • With records: revisiting the job-state decision costs reading one page and checking whether the stated trigger has fired. The argument restarts from where it stopped rather than from zero.
  • Without: revisiting costs a fresh comparison, re-derivation of constraints that were once known, and a real chance of re-choosing the option that was already rejected for a reason nobody can now recall.
  • The cost of the practice is fifteen minutes per decision, paid by the person with the most context, at the moment they have the least patience for writing.
What the recommended approach costs
  • It is writing, at the moment when everyone wants to start building, done by the person whose time is most contested.
  • Written reasoning is auditable, which makes some engineers less candid: "we chose this because we already know it" is true and rarely written.
  • A record can freeze a decision that should have been reopened — a well-argued page is intimidating to contradict, and juniors especially will read one as settled law (Tone, Disagreement and Receiving Review).

What can go wrong

Failure modes
  • Records are written after the fact to satisfy an architecture review, reverse-engineered from the code, and contain no information that was not already in the code.
  • The template grows — stakeholders, risk ratings, sign-off — until writing one is a project, and then nobody writes them (Types of Documentation).
  • Records are written for trivial decisions and not for the expensive ones, because the expensive ones are contentious and nobody wants to put the reasoning in writing.
  • Records exist and are never read, because they live in a wiki with no link from the code they explain (Documentation Decay).
Dependencies, and their direction
  • The record depends on the constraints being written down. A decision without its constraints reads as arbitrary a year later, when the constraints have changed invisibly (Constraints Are Part of the Design).
  • Later decisions depend on earlier records, which is why superseding needs a link rather than a deletion.
  • The practice depends on records being cheap to write. Any template that takes an hour is a template that produces zero records.
Misreads
  • "This is architecture documentation." It is a log of decisions, not a description of the system. A description goes stale silently; a dated decision record is still true as a record of what was believed (Documentation Decay).
  • "Every commit needs one." Records are for decisions that are expensive to reverse and hard to infer from the code. Most changes are neither (Reversible and Irreversible Decisions).
  • "We use ADRs, so our decisions are documented." Only the ones anyone chose to write up. The decisions that most need recording are the contentious ones, and those are the ones people avoid putting in writing.
  • "Edit the record when we learn more." Then it stops being evidence of what was known at the time, which was its only durable value. Supersede it instead (Architecture Decision Records).

Testing it, and how it ages

What to test, and at which boundary
  • The record is not tested, but its trigger can be: an alert on the threshold named in the Revisit Trigger turns a document into a mechanism.
  • A useful check on a record is whether a new engineer can answer "why not the queue?" from it alone. If not, the Options section is thin.
How this design ages
  • The archive gets more valuable as the team turns over, which is precisely why its value is invisible at the moment it is written.
  • Superseded records stay. A decision reversed twice is a strong signal about the underlying constraint being unstable, and only the sequence shows it.
  • Practices that survive are the ones attached to something already happening — a record required on any pull request that adds a dependency or a new store, rather than a separate ritual (A Review Checklist Worth Reading).

Where this applies

This domain's advice is contested more than most. These labels say what each claim is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view rather than a caricature.

  • GENERALThe six fields are about reasoning rather than technology, so they transfer to infrastructure, data model and process decisions equally — though for decisions with an external contract, the record is a supplement to the versioning policy rather than a substitute for it.
  • SCALE-SPECIFICFor two people who talk every day, the record's value is almost entirely future-facing and it is reasonable to write very few. Above roughly fifteen engineers, or with any team turnover, it becomes the cheapest coordination artefact available, because it removes the meeting where three people re-derive a decision the fourth already made (Knowledge Sharing).
  • CONTESTEDThe strongest opposing case: decision records are where good intentions go to die — they are written for the first six months, never read, drift out of correspondence with a codebase that moved on, and their existence creates a false sense that the reasoning is captured when the important half of it was social and never written. Teams making that argument usually prefer that the reasoning be encoded in tests, types and module boundaries, where it cannot go stale. That is a genuinely better mechanism where it applies — but it cannot express "we considered the queue and rejected it", which is exactly the question that keeps being asked.

Where the depth lives

This domain teaches the codebase-level structure and hands the rest off.

DevOpstoil
Domains that do not exist yet
  • Testing & Reliability Engineering — an incident review and a decision record are the same artefact pointed in opposite directions, and a team that writes one and not the other usually learns nothing from the decisions that did not cause an outage.