---
name: adr-writer
description: Author an Architecture Decision Record that captures a non-obvious technical choice — its context, the decision, the consequences, the rejected alternatives, and the reversibility cost. Use whenever a non-trivial dependency is picked, a framework is chosen, a one-way door is opened, two specialists disagree and the orchestrator must pick, or anyone in a future session would ask "why did we do this?" An ADR exists so the answer is on disk, not in someone's head. Its lifecycle status lives in frontmatter in the schema's one vocabulary (proposed, accepted, open, deferred, hotfix, rejected, superseded, closed); superseding is a new ADR plus a status flip on the old — never an edit of its body.
id: skill.adr-writer
tier: 2
kind: skill
origin: seed
title: adr-writer — record non-obvious technical choices as short, superseded-never-edited ADRs
owns:
  - adr-writer.method
  - adr-writer.reversibility
  - adr-writer.numbering
requires:
peers:
  - skill.grill-planner
  - agent.architect
  - skill.humanizer
load_when:
  - "write an ADR"
  - "record an architecture decision"
  - "why did we choose this dependency or design"
  - "supersede an existing decision record, mark an ADR superseded"
  - "which ADRs are still open, hotfix, or deferred"
  - "ADR status: proposed, accepted, superseded_by"
  - "opening a one-way door decision"
  - "accepted risk, declined fix, or an owner-declared no-go area"
artifacts:
  - templates/adr.template.md
  - decisions/README.md
prevents: Choices whose reasoning exists only in the session that made them, so a later reader sees what was decided but not what was rejected or what reversing it would cost.
est_tokens: 2760
---

# adr-writer

ADRs (Architecture Decision Records) live in `docs/graph/decisions/` and
record every non-obvious technical choice. Each is a short, recorded
answer to "why did we pick this?"; a design doc is a different artifact.

This skill encodes the discipline of writing them well.

## When to apply this skill

- A new dependency is being committed to (the wiki page handles the
  *what*; the ADR handles the *why*).
- A framework, language, or platform is being chosen.
- A one-way door is being opened (data model, public API contract,
  vendor lock-in).
- A boundary between two services or modules is being drawn.
- Two specialists disagreed and the orchestrator picked an option.
- A bug post-mortem revealed an implicit decision that should have been
  explicit.
- A **T2 contained-lane** change made a real choice among options and
  owes its why-record (`tiers.contained-lane`). Write the short form:
  Context, Decision, Consequences, Reversibility, the sections that
  carry the why. Skip Alternatives when none were weighed, and say so in
  one line. A contained change whose why needs every section was
  misclassified: hand it back for T3.
- Anyone asks "why did we do it this way?" and the answer isn't in an
  existing ADR.

## ADR numbering and status

ADRs are numbered monotonically: `adr-NNNN-short-slug.md`. Find the next
free number in `docs/graph/decisions/` and in the index, because a
number can be taken without a file. Each number names one decision for
good, because citations outlive files.

A number allocated to a decision that is owed but not yet written (a
spec reserves it, or the owner has not ruled yet) gets an index row
marked reserved or owed, naming who owes the body. The index row is the
whole reservation, because a stub ADR file is fiction that satisfies a
linter. The reservation holds even when the decision is abandoned. Where
a program keeps more than one ADR series (one per repository, say), a
citation names the series as well as the number, so two ADRs with the
same number cannot be confused.

Status lives in **frontmatter**, in the schema's lifecycle vocabulary
(`docs/graph/_schema.md` §"Lifecycle status" is the home; read it for
the meanings):
`status: proposed | accepted | open | deferred | hotfix | rejected | superseded | closed`,
always with `status_date`, plus the companion the value requires:
`owner` while `open | hotfix | deferred`, `reopen_when` when `deferred`,
`status_evidence` when `closed`, `superseded_by` when `superseded`. The
body's `## Status` section is a pointer to the frontmatter; a body value
that disagrees is a lint failure
(`python3 docs/graph/status-register.py --root docs/graph`), the drift
the register exists to catch. An ADR is born `proposed` and becomes
`accepted` when the owner ratifies it (the grill.md §6 row); the base
values apply as the schema defines them: a "do nothing now" decision
parked behind a named trigger is `deferred` with `reopen_when`, a choice
taken under pressure that owes a proper decision is `hotfix` with an
`owner`.

To replace a decision, write a new ADR with a new number and
`status: accepted`, and set the old ADR's frontmatter to
`status: superseded` + `superseded_by: ADR-NNNN` + a fresh
`status_date`. That flip is the **only** edit a ratified ADR ever
receives; its body is never rewritten (the append-only exception in
`holistic-editing`). To see what is still owed:
`python3 docs/graph/status-register.py --by-kind adr --open --hotfix`
(add `--deferred` for parked decisions). The register is the query
surface; the index at `docs/graph/decisions/README.md` is the human
table.

## The template (4 sections that matter most)

Use `docs/graph/templates/adr.template.md`. Its frontmatter is the
status home (above); the four body sections that earn their keep:

### Context

What is the situation that forces a decision? Include the constraint
that makes "do nothing" not viable. Cross-link to grill.md and the
relevant spec.

Bad: "We need a database."

Good: "Spec SPEC-0003 requires submissions to persist across restarts.
Grill.md §4 caps p95 latency at 200ms and cost at $X/month. The current
implementation uses an in-memory map, which loses state on restart. We
need to choose a persistent store."

When the owner states how an external arrangement works (a partner
handles a class of requests, a contract covers a duty), record it as a
declaration attributed to them, in their words where they matter, and
name what it leaves open. Conclusions drawn from it, legal ones above
all, are `agent.legal`'s to reach against its corpus; until then the ADR
records the question as owed.

### Decision

What we have decided, in **one sentence**. Optionally a short paragraph
naming the central tradeoff. A sentence that holds several decisions is
several ADRs: split it.

Bad: "We'll use PostgreSQL."

Good: "We will persist submissions in PostgreSQL 16, using the managed
instance on platform X, accepting an additional ~$45/mo in exchange for
ACID guarantees and ecosystem maturity over the in-memory alternative."

### Consequences

What changes downstream. Be concrete:
- New constraints (e.g. "migrations now belong in `migrations/` and run
  on deploy").
- Migration or rewrite cost if reversed.
- Effect on the verification plan (new gates, integration tests against
  a test database).
- Effect on the wiki (new library to wikify: the database driver and the
  migration tool).

### Alternatives considered

For each rejected alternative: one paragraph naming the alternative and
the **concrete** reason it lost, because "not as good" tells the next
agent nothing.

Bad: "SQLite — not as good for our use case."

Good: "SQLite — rejected because grill.md §4 requires concurrent writes
from multiple workers; SQLite serializes them and would violate the
200ms p95 budget at the projected request rate."

## Reversibility tag

Every ADR tags reversibility as one of:

- **`reversible`**: can be changed in a single session without data
  migration.
- **`expensive`**: can be changed but requires a multi-day project.
- **`one-way`**: changing it later requires a rewrite or a migration on
  live data.

The tag tells the next agent how much weight the decision carries.
`one-way` ADRs get extra scrutiny. They are the ones where the
"alternatives considered" section earns its keep: the next agent needs
to understand why the alternative was rejected, not just that it was.

## What counts as a decision

An ADR records a *choice that was made*. An implementation detail
reconstructed from source is an **observation**: record it where
observations live (a node, a runbook) with its rationale marked "not
recorded". A survey that turns up no genuine decisions leaves the index
empty and says so, because the next agent trusts every ADR it finds, and
a fabricated one is worse than a missing one.

Four decisions people forget to record, because they feel like inaction:

- **"Do nothing now" is a decision.** Ratifying a destination while
  taking no code yet, deferring the first increment behind a named,
  checkable trigger, is an ADR. Separate the *destination* (which
  end-state is correct) from the *timing* (what licenses starting), and
  state what makes waiting safe ("the drift is now tested, not
  invisible"). Its frontmatter is `status: deferred` with `reopen_when:`
  naming that trigger, so the register lists it among what is parked.
- **The asymmetric cost of being wrong** is often the whole rationale.
  Record what being wrong costs *in each direction* ("wrong on X risks
  an irreversible incident; wrong on Y costs a bounded, recoverable
  delay") and let the asymmetry decide, rather than arguing which option
  is abstractly "best".
- **Declining a fix is a decision.** When the owner accepts a residual
  risk instead of remediating it, the ADR says plainly that nothing is
  remediated by it; what it changes is the finding's standing, not the
  measurement. It separates exactly what is declined from what is not.
  An implemented-but-unverified control is a different finding from an
  unremediated one; name which each item is, because a record that blurs
  the two misleads both ways. It names the residual in plain terms and
  its register row, lists any compensating control with what that
  control does not cover, and quotes the owner's constraints and the
  attesting person. It states the revisit trigger, or states that there
  is none; a qualifier like "for now" in the acceptance is kept, because
  it makes the standing expire. What a declined fix is in the register,
  and when a standing acceptance becomes a `deviation` node, is
  `method.incident-posture` §6, its one home; link it.
- **A declared no-go area is a decision.** When the owner puts a
  repository, system, or environment off limits, the ADR states the
  boundary exactly: which commands are barred (read-only ones included,
  if the owner said so), and which nearby configuration that lives
  elsewhere stays in scope. A contradiction seen inside the boundary
  before it closed is recorded `not recorded`; the owner's boundary
  outranks settling it with a probe. The consequences say that the
  graph's facts about the area are frozen at their last pin and must be
  presented as historical. Its standing form is a `deviation` node
  (`docs/graph/nodes/_deviation.template.md`).

## Workflow

1. Find the next free number. Read the most recent few ADRs to match the
   project's tone.
2. Copy `docs/graph/templates/adr.template.md` to
   `docs/graph/decisions/adr-NNNN-<slug>.md`, and fill the frontmatter:
   `status: proposed` (or `accepted` when the owner has already
   ratified), `status_date`, and the companion the value requires. Leave
   the body `## Status` as the pointer it is.
3. Fill **Context** first. If you can't write the context, you don't yet
   know what decision you're making.
4. Fill **Decision** in one sentence. If you can't, the decision isn't
   yet made; back up to research or brainstorm.
5. Fill **Consequences** concretely, with file paths and budget impacts
   where applicable.
6. Fill **Alternatives considered**: at least one alternative, usually
   two or three, each with a concrete rejection reason.
7. Fill **Reversibility** and, if `expensive` or `one-way`, name the
   cost in concrete terms.
8. Cross-link: spec, grill.md, wiki pages, external sources.
9. If this ADR replaces one: set the old ADR's frontmatter to
   `status: superseded` + `superseded_by: ADR-NNNN` + `status_date`, and
   nothing else in it.
10. Add a row to `docs/graph/decisions/README.md` (the index).
11. Add a row to grill.md §6 with the ADR's identifier; when the owner
    ratifies, flip the frontmatter to `accepted` with a fresh
    `status_date`. If what shipped departs from a `proposed` ADR, the
    as-written text is not what gets flipped to `accepted`: record the
    difference between as-written and as-shipped next to it (its index
    row) and owe the amendment to the ADR's author. The close-out leaves
    the body as written.
12. Before the status flips to `accepted`: the body is prose a person
    reads in a year. Apply `docs/graph/skills/humanizer.md` in file mode
    and run
    `python3 docs/graph/prose-lint.py --file <adr> --against HEAD`; a
    strong tell or a dropped number, heading, or code span blocks the
    flip.

## Reference files

- `docs/graph/templates/adr.template.md`: the template.
- `docs/graph/_schema.md` §"Lifecycle status": the vocabulary and its
  companions; `docs/graph/status-register.py`: its lint and query.
- `docs/graph/agents/01-architect.md`: the agent that primarily writes
  ADRs.
- `docs/graph/templates/docs/` + `decisions/README.md`: the index
  template installed at `docs/graph/decisions/README.md`.
