Skip to content

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

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 ## Purpose is 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 ## PurposeADR
AnswersWhy does this feature exist?Why was this architectural approach chosen?
AudienceAnyone reading the specFuture developers and agents who might revisit the decision
Lives inopenspec/specs/<name>/spec.mdopenspec/decisions/NNN-title.md
Changes whenThe feature's goal changesThe 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, and user-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:

SchemaWhen to use
spec-driven-with-adrYou are already on the default schema and want to add ADR support without changing the rest of your workflow
intent-drivenYou 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:

yaml
schema: spec-driven-with-adr

Switching 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.md

Each ADR is a single flat markdown file (no subdirectory needed):

markdown
# 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:

FieldContent
# ADR NNN: [Title]Sequential number + a short title describing the decision
## StatusProposed · Accepted · Deprecated · Superseded by ADR NNN
## DateISO date (YYYY-MM-DD) the decision was accepted
## ContextWhat forces, constraints, or options led to this decision?
## DecisionThe specific choice made and its key parameters
## ConsequencesWhat 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.md

After 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:

markdown
## 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.md is good; 001-sessions.md is 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 default to spec-driven-with-adr or intent-driven changes 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.md proposals get archived. Any decision worth revisiting belongs in openspec/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 Accepted is 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 to openspec/decisions/ is where it becomes readable by future proposals.

References

ADR Schema

OpenSpec Guide

  • OpenSpec Guide Core guide covering specs, proposals, and the Propose → Apply → Archive workflow

Copyright © MSG Systems Romania AI Labs