Concepts

Decisions and ADRs

A decision is a file with a status. Proposed, then accepted, then, when it changes, superseded by a newer accepted one. The trail is never edited away.

What deserves an ADR

Not every change. A small fix inside an already-decided area just gets built. An architecture decision record is for the decisions that other work depends on and that an agent must not quietly contradict later:

  • Architecture style and module boundaries.
  • Dependencies.
  • Auth, secrets, storage, networking, telemetry, or file-write behavior.
  • Security posture.
  • Runtime integrations such as MCP servers.

Persist OS does not judge the content. Supabase, Firebase, Postgres, or an internal system are all equally valid once the repository has accepted them. Drift means mismatch with what the repository accepted, not mismatch with a tool's preference.

Files and naming

docs/adrs/ADR-####-<slug>.md
A numbered ADR. persist adr create writes one with Status Proposed; persist adr accept flips it to Accepted. Accepted ADRs sit at the top level of the directory; there is no accepted/ subfolder.
docs/adrs/proposed/ADR-PROPOSED-<slug>.md
An unnumbered proposal, as written by persist adopt and persist mcp add. Accepting it assigns the next number, writes the numbered file, and removes the proposal.
docs/adrs/README.md
The index the agent starts from.

The template

Every ADR from persist adr create starts as:

# ADR-0001: Use PostgreSQL For Primary Storage

## Status

Proposed

## Context

What decision needs to be made, and why now?

## Decision

What option is proposed?

## Alternatives Considered

What other options were considered?

## Consequences

What improves, what worsens, and what risks remain?

## Related Documents

- PRD:
- Architecture:
- Security:
- Feature:

The doctor reads the Consequences and Alternatives Considered sections. If either is empty or still the prompt, that is a warning on a proposed ADR and an error on an accepted one: an accepted decision with no recorded consequences, or no alternatives, is not finished. When the Decision itself is about auth, secrets, or similar, the Security: line under Related Documents must point at something, or the doctor warns.

The lifecycle

Proposed

Someone writes the decision down. That can be a person running persist adr create, an agent recording a decision it just made, persist adopt inferring a framework from a manifest, or persist mcp add proposing an integration. In every case the status is Proposed and nothing else in the memory may treat it as settled. The doctor warns when a feature or module cites an ADR that is not yet accepted.

Accepted

A human runs persist adr accept <slug>. From then on the ADR is source of truth, ranked first in the source-of-truth order, and the generated agent rules say never to contradict it.

$ persist adr accept use-postgresql-for-primary-storage
Persist OS ADR accept complete.
Accepted: docs/adrs/ADR-0001-use-postgresql-for-primary-storage.md
Overwritten:
- docs/adrs/ADR-0001-use-postgresql-for-primary-storage.md

Next steps:
- docs/adrs/ADR-0001-use-postgresql-for-primary-storage.md is now Accepted and is repository source of truth.
- Drift checks now treat references to it as resolved.

Superseded

Decisions change. The wrong way to record that is to edit the accepted file, which erases the fact that the repository once decided otherwise. persist adr supersede <old> <new-title> does it the other way: a new numbered ADR is written as Accepted with a ## Supersedes link, and the old ADR's status line is rewritten to say what replaced it. Only an accepted ADR can be superseded; the command refuses otherwise. It also refuses a replacement title that another live decision or proposal already holds, naming the existing file — two accepted decisions under one title is the drift the doctor reports as an error.

$ persist adr supersede use-postgresql-for-primary-storage "Use SQLite for primary storage"

docs/adrs/ADR-0001-use-postgresql-for-primary-storage.md
 ## Status
-Accepted
+Accepted — superseded by ADR-0002-use-sqlite-for-primary-storage

docs/adrs/ADR-0002-use-sqlite-for-primary-storage.md  (new file)
+# ADR-0002: Use SQLite For Primary Storage
+
+## Status
+
+Accepted
+
+## Supersedes
+
+- ADR-0001-use-postgresql-for-primary-storage
+
+## Context
+
+What changed, and why the previous decision no longer holds?

The old status keeps the word "Accepted" on purpose. Memory that cites ADR-0001 is still citing a real, once-accepted decision, so it is not a broken reference. It is a reference that needs a look, and that is exactly what the doctor reports:

Next steps:
- Fill docs/adrs/ADR-0002-use-sqlite-for-primary-storage.md: Context (what changed), Decision, Alternatives, Consequences.
- ADR-0001-use-postgresql-for-primary-storage stays in history as superseded; update any memory that still relies on it.
- Run persist doctor — it flags memory that still references the superseded decision.

How the doctor treats decisions

Situation Result
Feature or module memory cites an ADR that does not existerror
Memory cites an ADR that is still Proposedwarning
Memory cites an ADR that has been supersededwarning
Two accepted ADRs share one titleerror
A proposal shares the title of a live accepted ADRwarning
An accepted ADR has no substance in Consequences or Alternatives Considerederror
A proposed ADR has no substance in Consequences or Alternatives Consideredwarning
A decision written in security-sensitive terms has no security document linked under Related Documentswarning
An ADR is missing a required sectionerror
One of the six required documents links to a doc path that does not exist, or cites an ADR that does not existerror

What agents are told

The generated AGENTS.md and Cursor rule carry the commands inline so the agent runs them itself: a real decision means persist adr create then persist adr accept; changing one means confirming with a human and running persist adr supersede, never overwriting an accepted ADR. The SessionStart hook lists the accepted ADRs at the start of every Claude Code session, so a fresh agent knows they exist before it starts work.

What this does not catch

The doctor checks structure and references, not meaning. If an accepted ADR says PostgreSQL and the code quietly moved to MySQL, the file is still there, the references still resolve, and the doctor passes. Catching that needs reading, so it is left to the people and agents doing the work. The gate stays deterministic; the model does the reading.