Companion guide: This document extends OpenSpec: Spec-Driven Development for AI Agents. Read that guide first. This companion is optional and assumes you are already comfortable with specs, proposals, and the Propose → Apply → Archive workflow.
OpenSpec ADRs: Capturing Architectural Decisions
How to use Architectural Decision Records alongside OpenSpec specs and proposals to preserve the reasoning behind design choices both for AI agents and future developers.
Reference: spec-driven-with-adr schema Source for the ADR schema extension · ADR documentation
Content Table
- Purpose
- Audience
- ADRs vs. Specs: Where Does the Why Go?
- Choosing a Schema for ADRs
- Two-Tier Artifact Model
- When to Write an ADR
- The ADR Format
- ADR Lifecycle
- Linking ADRs to Specs
- Tips and Pitfalls
- References
Purpose
When a change is merged, design.md gets archived, taking the reasoning behind every design decision with it. A future proposal that touches the same area starts designing in the dark, unaware that certain options were explicitly rejected and why.
ADRs solve this by living outside the change lifecycle: promoted from a proposal into openspec/decisions/, they persist as a permanent record alongside the specs. Specs answer what the system must do. ADRs answer why a particular architectural decision was made.
By the end of this companion you will have:
- A clear understanding of when an ADR adds value and when a spec's
## Purposeis enough - A working ADR format you can apply to any architectural decision
- A process for promoting design decisions from proposals into permanent records
Audience
Software developers already using OpenSpec who encounter recurring architectural decisions across multiple specs or features. Assumes you are comfortable with the core OpenSpec workflow specs, proposals, and the Propose → Apply → Archive cycle.
ADRs vs. Specs: Where Does the Why Go?
A spec's ## Purpose section already answers why a feature exists. An ADR answers something different: why a specific architectural path was chosen over the alternatives. These are not the same question.
Spec ## Purpose | ADR | |
|---|---|---|
| Answers | Why does this feature exist? | Why was this architectural approach chosen? |
| Audience | Anyone reading the spec | Future developers and agents who might revisit the decision |
| Lives in | openspec/specs/<name>/spec.md | openspec/decisions/NNN-title.md |
| Changes when | The feature's goal changes | The decision is superseded or deprecated |
When specs alone are enough
Keep the reasoning inside the spec when:
- The requirement explains itself: "The system SHALL expire sessions after 30 minutes of inactivity" needs no ADR; the reason is obvious from the domain.
- The decision affects only one feature and would not surprise a future developer.
- The constraint is simple, reversible, or standard practice in the domain.
When you need an ADR
Write a separate ADR when:
- Alternatives were explicitly rejected. When a reasonable developer could look at the spec and ask "why not use JWTs instead?" If the answer matters, it belongs in an ADR, not a comment in a spec.
- The constraint is invisible from the code. Compliance requirements, infrastructure limits, legal restrictions, or team agreements that shaped the architecture but are not visible anywhere in the implementation.
- The decision crosses multiple specs. If the same architectural constraint appears in
auth-session,api-rate-limiting, anduser-profile, an ADR is one source of truth rather than three copies of the same note. - The reasoning would rot if left in
design.md. Proposals get archived, when a decision matters beyond this change, it needs a permanent home.
A useful test: put yourself in the position of a developer two years from now who looks at a requirement and thinks "this seems overly restrictive so I'll simplify it." If removing the constraint would silently break a compliance rule or reopen a settled debate, write an ADR. If they'd just be wrong about the feature, the spec's Purpose is enough.
Choosing a Schema for ADRs
For a full overview of all available schemas and how configuration works, see the Schemas section of the main guide.
Two schemas support ADRs. Which one to pick depends on whether you are adding ADRs to an existing project or starting fresh:
| Schema | When to use |
|---|---|
spec-driven-with-adr | You are already on the default schema and want to add ADR support without changing the rest of your workflow |
intent-driven | You are starting a new project and want ADRs built into the workflow from the beginning alongside proposals, design, and tasks |
Enable the schema in openspec/config.yaml:
schema: spec-driven-with-adrSwitching schemas is a team decision as it changes what every future proposal is expected to produce. Agree on it explicitly rather than one person enabling it unilaterally.
The silent failure to watch for
If a project already has ADRs in openspec/decisions/ but the config still points to default, the validator passes without checking them resulting in no error, the ADRs are simply invisible to the tooling. When inheriting a project, check openspec/config.yaml first to confirm which schema is active.
Two-Tier Artifact Model
The spec-driven-with-adr schema treats artifacts in two tiers:
Proposals, designs, and tasks are scaffolding, meaning they do their job during the change, then get archived. Specs and ADRs are load-bearing, meaning they remain the durable record that every future proposal reads before designing anything new.
When to Write an ADR
Write an ADR when:
- A non-obvious architectural option was chosen over a simpler one.
- A decision affects multiple specs or features.
- The reasoning behind a constraint would not be obvious from reading the spec or code.
- A decision was explicitly not taken, and future agents should know why.
A practical test: if someone reading this spec two years from now could reasonably ask "why don't we just use X instead?"→ write an ADR explaining why X was not chosen.
The ADR Format
ADRs live in openspec/decisions/ and use a sequential numbering scheme:
openspec/
└── decisions/
├── 001-session-storage-backend.md
├── 002-api-versioning-strategy.md
└── 003-client-side-rendering-scope.mdEach ADR is a single flat markdown file (no subdirectory needed):
# ADR 001: Session Storage Backend
## Status
Accepted
## Date
2024-03-15
## Context
We need to store session state for authenticated users. The primary candidates
were: (1) server-side sessions in Redis, (2) signed JWTs stored client-side,
and (3) database-backed sessions.
The application runs behind a load balancer with multiple instances. Sessions
must be invalidatable server-side (compliance requirement for the financial
sector).
## Decision
We will use server-side sessions stored in Redis with a 30-minute idle timeout.
## Consequences
- Sessions can be invalidated immediately on logout or suspicious activity.
- Redis becomes a required infrastructure dependency, teams must provision it.
- Horizontal scaling requires a shared Redis instance or sticky sessions.
- Session payloads are never exposed to the client.
- JWT-based stateless auth is off the table unless the compliance requirement changes.Format rules at a glance:
| Field | Content |
|---|---|
# ADR NNN: [Title] | Sequential number + a short title describing the decision |
## Status | Proposed · Accepted · Deprecated · Superseded by ADR NNN |
## Date | ISO date (YYYY-MM-DD) the decision was accepted |
## Context | What forces, constraints, or options led to this decision? |
## Decision | The specific choice made and its key parameters |
## Consequences | What becomes true, including trade-offs and things that are now ruled out |
ADR Lifecycle
When a proposal makes an architectural decision, draft the ADR alongside it in the change folder:
openspec/changes/add-remember-me-sessions/
├── proposal.md
├── design.md
├── tasks.md
├── decisions/
│ └── 004-persistent-session-token-storage.md ← drafted here
└── specs/
└── auth-session/
└── spec.mdAfter the branch is merged, move the ADR to openspec/decisions/ as the canonical record. From this point on, every future proposal that reads the specs will also read the ADR.
Linking ADRs to Specs
Reference an ADR from within a spec when the decision directly constrains the requirements:
## Purpose
Users need to stay authenticated across page loads without logging in repeatedly.
Sessions must expire automatically to protect accounts on shared or unattended devices.
> Architectural constraint: session storage backend is Redis with server-side
> invalidation. See [ADR 001](../../decisions/001-session-storage-backend.md).This gives AI agents the full chain: what the feature does (spec) → why certain options are off the table (ADR).
Tips and Pitfalls
Do
- Write ADRs during the proposal phase, not after the reasoning is freshest while you are still designing. An ADR written three months later captures what you remember, not what actually mattered.
- Link the ADR from the spec when it directly constrains requirements give AI agents the full chain from what to why. A requirement with no visible rationale will eventually be "simplified" by someone who doesn't know the history.
- Keep ADR titles descriptive and decision-specific
001-session-storage-backend.mdis good;001-sessions.mdis not. The filename is the first thing anyone reads. - Record rejected alternatives explicitly the value of an ADR is not just the decision made, but the options ruled out. If a future developer can't see why alternatives were rejected, they will re-open the discussion.
- Agree on the schema switch as a team switching from
defaulttospec-driven-with-adrorintent-drivenchanges what every future proposal is expected to produce. Unilateral changes create inconsistent project history.
Avoid
- Writing an ADR for every requirement if the reason is obvious from the domain ("sessions expire after 30 minutes for security"), it doesn't need an ADR. Overusing ADRs degrades their signal value.
- Keeping design decisions only in
design.mdproposals get archived. Any decision worth revisiting belongs inopenspec/decisions/, not in a file that will be moved out of the active path after merge. - Mixing multiple decisions in one ADR each ADR should record a single decision. Bundling "we chose Redis AND we chose JWT-less auth AND we chose sticky sessions" makes it impossible to supersede just one.
- Using vague status values
Acceptedis clear.In progress,TBD, or leaving Status blank all create uncertainty about whether the decision is live. Only valid statuses:Proposed,Accepted,Deprecated,Superseded by ADR NNN. - Forgetting to move ADRs from the change folder after merge an ADR drafted in
openspec/changes/<id>/decisions/is not permanent. The promote step toopenspec/decisions/is where it becomes readable by future proposals.
References
ADR Schema
- spec-driven-with-adr schema Source and documentation for the ADR schema extension
- ADR documentation General background on Architectural Decision Records
OpenSpec Guide
- OpenSpec Guide Core guide covering specs, proposals, and the Propose → Apply → Archive workflow