DecisionsGENERALTEAM-SPECIFICPRODUCT-SPECIFIC

Decision Records

A short record written after the call — what was decided, what was rejected and why, and what would reopen it — so the reasoning survives the people who made it.

What is really going onHow to explain it

The ask, the obvious response, and how it goes wrong

Every lesson starts where the work starts: someone asked for something, and the first response that comes to mind has a problem.

The question

Eight months from now, when someone asks why it works this way, where will the answer be?

The ask

A new engineer, in a PR review: "Why do order confirmation emails go through a queue with a two-minute delay? It looks like a bug. Can I just send them synchronously?"

The obvious response

Ask around. Someone who was there will remember. If nobody remembers, the reason probably did not matter — ship the simpler version.

How it goes wrong

The person who remembers left in March. The second person remembers "something about the payment provider" but not what. The PR gets approved.

How it goes wrong in a real team
  • The person who remembers left in March. The second person remembers "something about the payment provider" but not what. The PR gets approved.
  • The two-minute delay existed because the payment provider occasionally reversed an authorisation within the first minute, and customers were getting confirmation emails for orders that were then cancelled. Support spent a quarter answering "you said my order was confirmed". Removing the delay brings all of that back.
  • Even when someone remembers, memory drifts toward the decision that won. The rejected options and why they lost are the first thing forgotten, and they are exactly what the new engineer is about to re-propose.
  • The absence of a record makes every past decision look arbitrary, which pushes new people toward re-deciding everything — slowly, and sometimes into the same wall.
Problem→Users→Options→Decision→Explain→Ship→Measure→Own

What is really going on

  • Reasoning lives in people and conversations; code only keeps the result. The queue with a delay is visible in the code; "because authorisations reverse in the first minute" is not, and no amount of reading the code will recover it.
  • The half-life of a reason is the tenure of the people who held it. Teams turn over; Slack threads scroll away; meeting notes live in someone's personal drive. A decision without a record has an expiry date nobody can see.
  • A decision record is written after the call, and that matters. A proposal argues for an option; a record states what was decided, by whom, and what was rejected, in the past tense. It is not a persuasion document (The One-Pager is), and writing it after means it records the real reasons, including the uncomfortable ones.
  • The rejected options are the most valuable part. The chosen option is visible in the system. What nobody can see is that "send synchronously" and "send on shipment instead" were both considered, and what killed each of them.

The reasoning dies before the code does

The confirmation-email delay has been in the codebase for two years. It looks like a bug. The reason it exists was in a Slack thread, a support escalation, and the head of the engineer who built it — who has since left. The code survived; the reasoning did not.

A decision record exists for exactly one reader: the person who, months later, is about to undo the decision without knowing why it was made. Everything about its format should serve that reader.

Where a decision's reasoning goes if nobody writes it down
implementedremembereddecayswritten afterlinked fromDecision made in a meetingPeople: the reasonsRecord in the repoPeople leave, threads scrollCode: the result only
UserLLMAgentToolDataDecisionHumanGuardrail

The template

One screen, past tense, with the rejected options given as much care as the chosen one. The status line and the "revisit if" line are what keep it honest over time.

docs/decisions/0014-delay-order-confirmation-email.md
1# 0014 — Delay order confirmation emails by two minutes
2
3Status: Decided (2026-03-11) · Supersedes: none · Superseded by: —
4Decided by: checkout team lead, support lead · Written by: checkout engineer
5
6## Context
7Some card authorisations are reversed by the payment provider within the
8first minute. Customers got "your order is confirmed" and then a cancellation.
9Support handled these by hand for most of Q1.
10
11## Decision
12Confirmation emails go through the email queue with a 120-second delay and
13are dropped if the order is cancelled before they send.
14
15## Options we rejected
16- Send synchronously at checkout — the current bug; confirms orders that
17 are then reversed.
18- Send only when the order ships — removes the problem, but customers wait
19 up to two days for any confirmation; support expected more "did my order
20 go through?" tickets than the reversals cause.
21- Hold the order page in "pending" until the provider settles — needs a
22 provider feature we do not have.
23
24## Consequences we accept
25- Every customer waits up to two minutes for the email.
26- One more moving part in the queue; if it stalls, emails stall.
27
28## Revisit if
29- The provider guarantees authorisations are final at checkout, or
30- "Where is my confirmation?" tickets exceed the reversal tickets we removed.

The rejected options section is longer than the decision, and that is correct: the decision is visible in the code, the rejected options are not. "Revisit if" turns a future argument into a check.

A record that helps, and one that does not

Most bad records are not missing information; they are written for the wrong reader. They persuade the people who already decided instead of informing the person who will want to undo it.

The same decision, recorded twice

The checkout team has just agreed to delay confirmation emails. The engineer writes it up before logging off.

Weak

"Confirmation emails: we implemented a robust queue-based approach with a delay to improve reliability and customer experience. This aligns with best practices for asynchronous messaging."

Strong

"We delay confirmation emails by two minutes and drop them if the order is cancelled first, because the provider reverses some authorisations within a minute and customers were being told cancelled orders were confirmed. We rejected sending on shipment because customers would wait days for any confirmation. Revisit if the provider makes authorisations final at checkout."

WhyThe weak version gives the future reader nothing to check: no problem, no rejected option, no condition. The strong version lets the new engineer answer their own PR question in thirty seconds — and tells them exactly what would make removing the delay safe.

Which decisions get a record

TEAM-SPECIFICTeams with high turnover or many contractors should lower the bar and record more; a stable team of long-tenured engineers can record less, until the first of them leaves.

Not every decision deserves one. The test is simple: would someone plausibly want to undo this, and would undoing it without the reason cause harm? If yes, write it. Most tickets fail the test, and that is fine.

  • Anything that looks like a bug but is deliberate — the delay, the extra check, the "unnecessary" retry.
  • Anything where a reasonable person would pick the rejected option first.
  • Anything that constrains another team or another product surface.
  • Anything hard to undo: data shape, public URLs, what customers were promised (Reversible vs Irreversible Decisions).
  • Not: naming, formatting, library choices with no user-visible consequence, anything a revert fixes in five minutes.

How to do it

Most important first.

  • Write it within a day of the decision, while the reasons are still sharp — and keep it to one screen. A record nobody finishes reading records nothing.
  • Always include the options you rejected, each with the one-line reason it lost. This is the part the future reader needs most and the part people most often skip.
  • Include a "revisit if" line: the condition under which this decision should be reopened. "If the provider guarantees authorisations cannot be reversed" turns a mystery into a checkable fact.
  • Store it next to the thing it decides — in the repository, linked from the code comment or the config — not in a wiki space nobody searches. Link it from the PR that implements it.
  • Never edit a decided record to change its decision. Write a new one that supersedes it and link both ways, so the history of why stays readable.

How to explain the decision

The sentences, the order, and what to lead with — for someone who did not make the call.

  • When proposing the practice: "When someone asks why checkout works a certain way, I want the answer to be a link, not a person. Five minutes after a decision, one screen, in the repo."
  • When answering a question with a record: "There is a record for that — the delay is deliberate, because of authorisation reversals. The record lists what would make it safe to remove, and I think one of those conditions might now be true."
  • When writing the rejected options: "We considered sending synchronously and rejected it because customers received confirmations for orders that were then cancelled; support handled those for a quarter."
  • When superseding: "This replaces the March decision. What changed is that the provider now holds authorisations for us, so the reason for the delay no longer applies."
Pushback you will hear, and the honest answer
  • "Nobody reads documentation." Nobody reads a wiki. People read the link someone pastes when they ask "why?" — that is the only reading a record needs.
  • "This is bureaucracy." One screen, after the decision is already made, with no approval step. The bureaucratic version is a meeting to re-decide it in eight months.

What can go wrong

Failure modes
  • Records written as proposals: persuasive, forward-looking, listing only the chosen option's strengths. Six months later nobody can tell what was actually decided or whether it was ever approved.
  • Records for everything: a record per ticket becomes noise nobody reads, and the three records that matter drown. Write them for decisions someone will plausibly want to undo.
  • Records nobody can find. A perfect record in a personal notes app has the same effect as no record.
  • Records that are never superseded. The system changed, the record did not, and now the documentation is confidently wrong — which is worse than missing.
Misreads
  • "Decision records are architecture documents." They are for any decision someone might want to undo — including product ones like "we do not show estimated delivery dates" — not only for choosing a database.
  • "The commit message is the record." A commit says what changed. It rarely says what was rejected, and it is found only by someone already looking at that line.
  • "We will write it up properly later." Later, the rejected options have been forgotten and the record becomes a description of the code.

Knowing whether it worked

Signals
  • Questions of the form "why is it like this?" get answered with a link in the same thread, and the asker stops there.
  • A new engineer proposes removing something, reads the record, and either drops the PR or writes "the revisit condition is now met because…" — both are wins.
  • Old records get superseded, not silently contradicted. You can trace the delay from its introduction to its removal.
  • Nobody needs to message someone who left to understand a past decision.
What changes at 10x
  • With three engineers, everyone was in the room and the record feels redundant. Its value arrives with the fourth hire, and grows with every person who joins after the decision.
  • At 10x team size, decisions made in one team constrain another. The record is how the other team finds out the constraint exists before building on the wrong assumption.
  • At 10x records, you need an index and a status field (proposed, decided, superseded). Without them the collection becomes an archive nobody trusts.
What this costs
  • It is a small, recurring tax: ten to fifteen minutes per significant decision, paid by the person who least needs it at that moment.
  • It fixes the reasoning in writing, which makes it harder to quietly change your mind — useful for the team, occasionally uncomfortable for the author.
  • Records go stale. Every record is a promise to supersede it when the world changes, and some of those promises will be broken.

Where this applies

Product advice is context-sensitive. These labels say what each claim is specific to, and where a different stage, team or product would differ.

  • GENERALAny team where people leave or join and systems outlive decisions. It differs in form, not need: a solo founder can keep one running file; a regulated shop may need records with named approvers.
  • TEAM-SPECIFICOn a small co-located team a short record in the repo is enough; across several teams the records need a shared index and a status field, or they stop being found.
  • PRODUCT-SPECIFICIn a regulated product (payments, health) some decisions must be recorded with an approver for audit, and the lightweight format here is the floor, not the ceiling.

Where the depth lives

This domain teaches the product-side judgement and hands the mechanism off.