· 8 min read ·

Architecture Decision Records and the Memory Problem That Code Cannot Solve

Source: martinfowler

The scenario is familiar. A new developer on the team asks why authentication tokens are stored in Redis instead of the database. Nobody on the current team was there when that decision was made. The commit message says “add Redis session storage.” The PR has no description. The Slack thread is long gone. The developer who made the decision left a year ago.

This is the documentation problem that Architecture Decision Records address, and Martin Fowler’s bliki entry is a clean articulation of what they are: short documents that capture a single architectural decision, its context, and its consequences. A couple of pages. Markdown. Stored in the repository alongside the code.

The concept sounds simple, but the details of how ADRs work and where they tend to break down are worth spelling out. Most teams who adopt them encounter the same failure modes, and some are avoidable.

The Nygard Format

Michael Nygard coined the term and formalized the structure in a 2011 blog post that described what he wanted to avoid: architectural drift that becomes invisible over time. His original format has five fields.

  • Title: a short noun phrase
  • Status: Proposed, Accepted, Deprecated, or Superseded
  • Context: the forces at play when the decision was made
  • Decision: the chosen approach
  • Consequences: the trade-offs and outcomes

A concrete example in this format:

# ADR-0004: Use Redis for Session Storage

**Status**: Accepted

**Context**: Our application needs to store user sessions. We deploy
across multiple app servers behind a load balancer, and sessions must
be accessible from any server without sticky sessions. The Postgres
team flagged concerns about high-frequency reads on session data
under load.

**Decision**: We will use Redis as a session store. Session data does
not require relational features, Redis is already provisioned for
caching, and its performance profile is well-suited to high-frequency
short-lived reads.

**Consequences**: All session data is now stored outside the primary
database, which simplifies connection pool pressure but introduces a
dependency on Redis availability. Backups must include Redis data for
session persistence across restarts.

The most important rule Nygard introduced is that ADRs are never modified, only superseded. If the team later decides to store sessions in the database after all, a new ADR is written that references and supersedes the old one. The original stays in the repository unchanged. This immutability is what makes ADRs trustworthy as historical records: the document accurately reflects what was decided at that time, under those constraints, and modifying it would erase that context entirely.

MADR and the Extended Format

Nygard’s format covers the essentials but omits something teams consistently find useful: a record of the alternatives considered and why they were rejected. Without that, you can read why Redis was chosen but you cannot tell whether PostgreSQL session extensions were evaluated and dismissed, or never considered at all.

The Markdown Architecture Decision Records (MADR) format, maintained by the ADR GitHub organization, extends the original structure with explicit sections for considered options and their trade-offs:

# ADR-0004: Use Redis for Session Storage

**Status**: Accepted
**Date**: 2024-01-15

## Context and Problem Statement

We need consistent session storage across multiple app servers
without sticky sessions.

## Considered Options

- **Redis**: External in-memory store, already provisioned.
- **Database-backed sessions (Postgres)**: Centralized but adds load.
- **JWT stateless sessions**: No server-side storage required.

## Decision Outcome

Chosen option: **Redis**

### Pros
- Fast reads; already available in infrastructure.

### Cons
- Another failure point; requires separate backup strategy.

### Why Not JWT
Stateless sessions require token revocation infrastructure we
do not have, and logout semantics become complicated without it.

This is more verbose but produces a better artifact for future decisions. When the next architect considers stateless sessions, they can see the option was evaluated and understand why it was dismissed at the time. That context may still be relevant, or the constraints may have changed, but either way the reader starts from a position of understanding rather than guesswork.

The Tooling Ecosystem

The simplest way to manage ADRs is adr-tools, the CLI written by Nygard himself. It handles file naming, sequential numbering, and supersession links:

# Initialize ADR directory in doc/adr/
adr init

# Create a new ADR
adr new "Use Redis for Session Storage"
# Creates: doc/adr/0004-use-redis-for-session-storage.md

# Mark an ADR as superseded by a new one
adr supersede 4 "Migrate to database-backed sessions"
# Creates: doc/adr/0005-migrate-to-database-backed-sessions.md
# Updates: doc/adr/0004-... (status changed to Superseded)

The sequential numbering matters for the same reason that commit hashes matter: references to “see ADR-0004” in code comments, PR descriptions, or runbooks remain stable over time, even after the decision is superseded. The number is a stable identifier for a historical event.

For teams that want a web interface and full-text search across their decision records, log4brains generates a static site from the same markdown files. It supports multiple ADR formats, works with monorepos, and runs as a local server during development:

npx log4brains preview
# Serves ADR site at http://localhost:4004

The static output can be published to GitHub Pages or any static host, which makes the decision history accessible to stakeholders who prefer not to navigate markdown files directly. The underlying files remain in the repository; log4brains adds a presentation layer on top.

ADRs Versus the RFC Process

Larger organizations often use Request for Comments processes rather than ADRs, and understanding the difference clarifies when each is appropriate. Amazon’s Working Backwards approach and Google’s design doc culture are both more heavyweight: they happen before implementation, involve a defined review period with explicit stakeholders, and require a designated approver.

ADRs are retrospective or concurrent with implementation. They capture decisions rather than drive the process of making them. An RFC is a proposal; an ADR is a record. The RFC happens in the review thread or working session; the ADR lives in the repository.

Some teams use both. The RFC drives the decision-making process among stakeholders. Once a decision is reached, a short ADR is committed to the repository as the authoritative, findable record of what was decided. This separates the conversation from the artifact, and the artifact is what survives.

The IETF’s RFC process, which dates to 1969, was always intended to invite discussion rather than impose decisions from authority. The same spirit is visible in Nygard’s inclusion of a “Proposed” status: an ADR can be written as a proposal, gather feedback, and be marked Accepted once the team agrees. This gives ADRs a lightweight RFC workflow without requiring a separate process or tooling.

Where ADRs Fail

The failure modes are consistent across teams that adopt ADRs.

The first is writing ADRs that document what, not why. An ADR that says “we use React” with no context provides no value; anyone can see you use React by looking at package.json. The value is in documenting the constraint, the alternative considered, and the trade-off made. If the context section is empty or generic, the ADR is not doing its job.

The second is not writing them for the decisions that matter most. Teams often start with ADRs for major architectural choices, such as database selection or framework choice, and then stop. The decisions most in need of documentation are often the smaller ones that constrain future decisions in non-obvious ways: why a particular cache invalidation strategy was chosen, why certain endpoints break the REST conventions, why the test database is seeded in a specific order. By the time someone needs to understand these choices, the reasoning is usually gone.

The third is storing ADRs outside the repository. An ADR in a wiki or a Notion page loses its connection to the code. When the code changes and a decision is superseded, nobody updates the external document. Fowler’s guidance on this is direct: keep decision records in the source repository of the codebase to which they apply, in a lightweight markup language, so they can be read and diffed just like any code. The canonical location is doc/adr/, where ADRs are reviewed in pull requests alongside the code changes they describe.

The fourth is failing to link back from the code. An ADR that exists but is never referenced from code comments, PR descriptions, or onboarding documentation will not be found by the people who need it. Linking from a code comment to the relevant ADR number is low overhead and ensures the reasoning is accessible to whoever is reading that code:

// Session storage uses Redis rather than Postgres.
// See doc/adr/0004-use-redis-for-session-storage.md for context.
export const sessionStore = new RedisSessionStore(redis);

The comment adds nothing about what the code does. It adds the information about why it does it that way, which the code itself cannot convey.

The Writing Act as Team Practice

Fowler’s bliki makes a point worth emphasizing: the act of writing an ADR serves a purpose beyond the record itself. Writing a document of consequence with a group of people surfaces disagreements that were previously implicit. A team that cannot agree on the context section of an ADR may not have actually reached consensus on the decision. The document forces the question into the open.

This connects to a broader pattern of making tacit architectural knowledge explicit. In his writing on knowledge priming for AI tools, Fowler observes that the constraints governing a codebase exist whether or not they are written down, and writing them in a form that survives team turnover and time is what turns individual understanding into collective, persistent knowledge. ADRs are one form of that writing, focused specifically on the moments when a consequential choice was made.

The enforced structure of an ADR, with its context, decision, and consequences sections, requires you to articulate things that are often left implicit in verbal decisions: what constraints existed, what was tried and discarded, what the known downsides are. Teams that cannot fill in those sections clearly may discover that the decision was less settled than they thought. That is useful information.

Fowler recommends an inverted pyramid structure, starting with the decision itself rather than the background. A reader who already knows the context stops after the first paragraph. A reader who needs the full picture reads on. The most important information is always first, regardless of how much detail follows.

For a practice that amounts to writing short markdown files and committing them alongside the code they explain, the return is disproportionate. The cost of not doing it compounds quietly until the day someone asks why the codebase is the way it is, and the honest answer is that nobody knows.

Was this interesting?