Skip to content

Decision records

This is where the why behind non-obvious choices lives. It is historical context, not the product contract and not a backlog:

  • current behaviour lives in the topic pages linked from the documentation index;
  • planned work, bugs and delivery status live in GitHub issues;
  • completed user-visible work is summarised by releases and CHANGELOG.md.

One decision is one file, docs/decisions/D<n>-<slug>.md, and the list below is generated from those files. Reading one costs the one file, not the register it used to live in — the ten thematic registers this replaced held 197 records in 483 KB, so answering "why is it like this?" meant loading up to 101 KB to reach a single paragraph.

Referring to a decision

In code comments and in prose the reference is the bare D<n>, with no path:

ls docs/decisions/D47-*          # resolve it
rg -l 'D47' --type go            # who mentions it

It costs fewer tokens than a path, and it does not break when a title is reworded. In markdown, use a real link — the test suite checks that every one of them resolves.

Adding a decision

  1. Reserve the number. Survey open plan issues as well as the files: a plan title reserves its D<n> until it is implemented or abandoned.
make decisions-next                                   # highest on disk + 1
gh issue list --label plan --state all --limit 1000    # and reserved by a plan
  1. Write it at the end of implementation, not before. The plan issue is the draft; the decision file records what was actually built, including any deviation from the plan.
make decisions-new N=202 SLUG=my-choice TOPIC=control-plane
  1. Record the decision, the rationale, and the consequences — including the uncomfortable ones. Alternatives that were considered and rejected are the part a reader six months from now actually needs. Current behaviour goes in the corresponding topic page, future work in a GitHub issue.

  2. Regenerate the index and commit it: make decisions-index. CI fails if it is stale, so this is not optional. It also fails if the file still carries a placeholder from the template — a half-written record indexes as a blank entry, because <title> renders as an HTML tag.

Do not add status tables, milestone lists or duplicate entries. A decision has exactly one file; other pages link to it.

Numbers that have no file

Every D<n> written anywhere in the repository has to resolve, and make test checks it (D208). Three states are legitimate, and only the first is a file:

State Where it is declared Meaning
implemented docs/decisions/D<n>-<slug>.md the record
gap repodocs.GapDecisions, with a reason deliberately never written; the number must not be reused, and a file claiming it fails the build
reserved repodocs.ReservedDecisions, with the plan issue an open plan holds it; the record arrives with the implementation, and the entry is then removed

A plan abandoned without implementing does not stay reserved: either it gets a record saying it was not done — D130 is the worked example — or, if nothing depends on the number, it becomes a gap.

All decisions

208 records, one file each, grouped by the topic they belong to. In code and in prose the reference is the bare D<n>, with no path: resolve it with ls docs/decisions/D<n>-*. Add one with make decisions-new, then regenerate this list with make decisions-index.

Cross-cutting architecture

KB model and imports

MCP tools, search and lint

HTTP, stdio and authorization

Commits, synchronization and conflicts

Skills, services and secrets

Provisioned artifacts

CLI, TUI and providers

Configuration, deployment and releases

Repository and documentation policy