Where your team writes down its decisions
Decisions live in chat threads and in people's heads until they vanish. A one paragraph record next to the code keeps the reasons where the work is.
Somebody asks in a review why the retry stops after three attempts. Nobody in the room knows. The choice was made a year and a half ago, it was discussed for ten minutes in a chat thread that has since been archived, and the person who made it has moved on. So the team does what teams do: they guess it was arbitrary, raise it to ten, and rediscover the reason at the end of the month when the bill comes.
Where decisions actually live
Ask a team where its decisions are written and the honest answers are a chat thread, a pull request comment, a meeting nobody minuted, and a person. Each of those has an expiry date. Chat search fails because you cannot remember the words that were used. Pull request comments survive but nobody browses them looking for reasons. The meeting is gone. The person leaves, or stays and forgets, which is the same thing eighteen months later.
What remains is the code, and the code is only half the story. It shows the value of the retry limit. It cannot show that the limit exists because attempts are billed, or that it was picked after an incident, or that an alternative was tried and abandoned. The reasoning is exactly the part that does not survive, and it is the part you need in order to change anything safely.
One paragraph, next to the code
The fix I have settled on is small enough that it actually gets done. A folder in the repository, one numbered file per decision, each file about a paragraph:
# 0007: Park a message after three delivery attempts
Date: 2026-03-11
Status: accepted
Decision: after three failed delivery attempts we stop retrying and move the
message to a parked queue that a person reviews.
Why: each attempt is billed by the upstream, and in the last two quarters
a failure that survived three attempts almost never cleared later.
Rejected: unlimited retry with backoff, because cost grows with no ceiling.
Dropping the message, because we lose the record and the customer
asks about it a week later.
Revisit: if attempts stop being billed individually, or if parked volume goes
above roughly fifty a day.
Owner: the team that runs the queue.Six short fields. It took four minutes to write and it answers the question that started this post in about ten seconds.
Where the folder sits is a decision in itself. One service can keep everything in docs/decisions/, while a monorepo wants a folder per package, because the person you want to find the record is the person working in that package. Name the files with a number and three or four words: the number makes them easy to reference, and the words turn the output of ls into a readable table of contents. Put a short README in the folder with the template in it, so the second person to write a record does not have to invent the format.
The important line is Revisit. A decision without a trigger for reopening it becomes a rule, and rules outlive their reasons. Writing the condition down gives the next person permission to change the value when the condition is met, and equally clear grounds to leave it alone when it is not. Retry policy is a good example of a decision that looks arbitrary and is not, in the same way that treating a rate limit as a permanent failure looks defensive until you have paid for it once.
The why outranks the what
If you only have the energy for one field, write the why. The what is already in the code, and if it is not, the code is the thing to fix.
A useful why names the constraint, not the preference. "We chose this because it is cleaner" ages into nothing. "We chose this because the upstream charges per attempt" stays useful, because in two years somebody can check whether the upstream still does that. A constraint can be tested for expiry. A preference cannot, so it turns into folklore and then into a rule nobody dares to touch.
Sometimes the honest reason is nothing to be proud of: the choice was made on release night, with half the information, in two hours. Write that down as it was. "Chosen under release pressure, not measured" is far more useful than a tidy rationale invented afterwards, because it tells the next person how solid the decision is. False confidence costs more than a wrong call.
The other field people skip is Rejected, and it is the one that saves the most time. Without it, every new engineer with good instincts proposes the obvious alternative, and the team spends an afternoon rediscovering why it does not work. One line of rejected options turns that afternoon into a sentence.
Keeping it short enough to survive
Every team I know has tried a documentation system that died. They die from weight. The defences that work for me:
- One paragraph is the limit. If a decision needs three pages, the three pages are a design document and the record links to it.
- It gets written in the pull request that makes the change, not afterwards. Afterwards never arrives.
- A review rule makes it automatic: if a change touches a limit, a timeout, a default, a schema or a third party boundary, the reviewer asks for the record. Not as a gate, as a question.
- Records are never edited into a new opinion. A decision that changes gets a new file, and the old one gets
Status: superseded by 0019. The history is the value. - Nobody signs off on a record. It is a note, not a contract. The moment approval is required, writing stops.
The first record is the hardest because the folder is empty and the format feels ceremonial. I usually seed it with the answers from the first client meeting, since the five questions I ask at the start produce exactly this shape of information: what was agreed, what was ruled out, who decides.
What it pays back
Two moments make the whole habit worth it.
The first is an incident at an unreasonable hour. Something is failing at a limit and the fastest way out looks like raising the limit. One search tells you whether that number is a guess or a guard:
grep -ril "parked\|retry" docs/decisions/
# docs/decisions/0007-park-after-three-attempts.mdTen seconds later you know the value was deliberate and what it protects, which stops the most expensive incident move there is: disabling a safeguard you do not understand at the exact moment it is doing its job. Decision records sit alongside good logs here, and the two together answer most of what you want to know at three in the morning, which is why three well chosen log fields and a decisions folder tend to arrive in the same codebases.
The second is onboarding. A new engineer can read twenty short records in an hour and come out with two years of context, including the shape of the problems the team has already hit. That is not something a walkthrough delivers, because the person giving the walkthrough no longer remembers which parts are surprising.
I also use it to give a new joiner their first task: note the first three things that confuse you, ask someone, and write the answers up as records. It gets their first commit into the repository on day two, and they are the only person on the team who can still see the blind spots, because in a week the same blindness will have settled on them.
What to watch out for
- Recording everything. A folder with four hundred files is a folder nobody reads. Record what is expensive to reverse, what looks wrong from the outside, and what depends on an invisible constraint.
- Letting records drift from the code. When behaviour changes and the record does not, you have created confident misinformation. The review rule is what keeps them attached.
- Writing them as justification. A record that only argues for the choice is an advertisement. The rejected options and the revisit trigger are what make it honest.
- Putting them in a place that requires a login and a search that does not work. If they are not in the repository, they are not next to the work, and they will be read once.
A team's real memory is whatever survives the people who were there. Chat does not survive, meetings do not survive, and the code survives without its reasons. A paragraph written at the moment of the decision, kept beside the code it explains, is the cheapest way I know to keep the reasoning attached to the thing it explains. The test is simple enough to run on your own project this week: pick the strangest number in your configuration and see whether anyone can say why it has that value.
Questions and answers
- What is a decision record?
- A short dated note that states what was decided, why, what was rejected and when it should be looked at again. It lives in the repository next to the code it explains, usually in a docs folder with numbered files. It is not a design document and not a specification, and it works best when it stays around one paragraph.
- Which decisions are worth recording?
- The ones that are expensive to reverse, the ones that look wrong to a newcomer, and the ones taken because of a constraint that is invisible in the code. A retry limit chosen because of a per attempt charge qualifies. The name of a variable does not. If you would have to reconstruct the reasoning from scratch in six months, write it down.
- Why keep decision records in the repository instead of a wiki?
- Because they move with the code. A record next to the module is found by the person editing that module, it travels through branches and forks with the work, and it can be reviewed in the same pull request that changes the behaviour. Wikis drift from the code they describe, and drifted documentation is worse than none because people still believe it.
- How does this help during an incident?
- It turns a question about intent into a search. When a limit, a timeout or a fallback behaves in a way nobody expects at two in the morning, one grep over the decisions folder tells you whether that value was deliberate and what it was protecting. Knowing a number was chosen for a reason prevents the most expensive incident move, which is changing a safeguard you do not understand.