Mushroom, cloud, and code logo
Published on

A Decision Nobody Signed Is a Suggestion: adrkit Grows a Review Loop

Authors
adrkit social card: machine-readable architecture decision records, git-native decision memory for human and agent-authored plans

In August I wrote that an unenforceable ADR is a wish. adrkit's answer was one field, affects, that lets a tool resolve which decisions govern a file and put them on the pull request where the next change happens.

That solved the reading half of decision memory. Twenty releases later, most of the work has been the other half: who gets to make a decision binding, when it was binding, and what agents are allowed to do with it.

Signing a decision now has rules

adrkit has recorded who ratified a decision since its first schema, and adr lint has refused an agent-drafted decision with no named ratifier since July. What it didn't record was the review behind the signature: who approved, who objected, and whether those objections were settled before anyone signed.

adr queue now shows every proposed record with its SLA state, approvals against quorum, and open objections. In a terminal it ends each item with the next step: the command to accept it, or the reason it can't be accepted yet. Piped, it is still the same deterministic Markdown report the CI Action posts to a managed issue.

The review itself is recorded the same way, as data in the record rather than a thread somewhere else:

adr approve 0044 --by @octocat
adr object 0044 --by @hubot --summary "Needs a rollback plan"
adr resolve 0044 --objection 1 --by @hubot
adr accept 0044 --by @octocat

Each command edits one list in the record's review block and nothing else; a re-parse guard refuses any change that would touch another field (ADR-0051). Only the person who raised an objection can resolve it. adr accept sets status, provenance.ratifiedBy, and review.decidedAt, and refuses when quorum isn't met, an objection is open, or the result wouldn't be a valid accepted record. None of them commits. You review the diff and open a pull request like any other change.

Two choices here were deliberate (ADR-0044). --by is required and never inferred from git config, because naming who ratified the decision is the point. And no agent surface runs these commands on a model's initiative. An agent can tell you acceptance is the next step; accepting stays a human act.

Asking what governed the code back then

Incident reviews and audits keep asking a question the present tense can't answer: which decisions applied to this file when this code was written?

adr explain src/auth/session.ts --as-of 2026-03-01
adr explain src/auth/session.ts --as-of a1b2c3d

The answer needed no schema change (ADR-0039). Every record has a date, and a superseded record names its successor, so each record has a half-open window in which it was in force. A git ref resolves to its committer date. A deprecated record reports undetermined rather than a guess, because the corpus genuinely doesn't record when it stopped governing.

The output also says what it did not re-date: matchers and markers still come from today's corpus and working tree. That note is there because a time-travel answer that silently mixes tenses is worse than no answer.

Files can point back at their decisions

affects lets a record reach out to the files it governs, but a pattern broad enough to cover a subsystem is too broad to be useful. The fix came from the community, the first outside feature adrkit shipped: a file can declare the decision it lives under with a dedicated comment line.

// @adr 0012
export function syncOnce() {
  /* ... */
}

The interesting part was making a marker an honest claim. It must be the first content of a dedicated comment line, so prose that merely mentions a decision doesn't bind. A marker inside a fenced code block is an example, not a use. In Markdown, # is a heading and * is a bullet, so only real comments count. Markers show up in adr check and the CI comment as "declared by," and they never change an exit code (ADR-0022).

Thanks to Antoine Balliet and David Sheffer for the pull requests that got it there.

Review it where you work: GitHub Copilot

The MCP server still has exactly four read-only tools, including list_superseded, which hands an agent the rejected and superseded decisions before it plans. The portable agent plugin packages that loop as skills and commands (load context, check a plan, draft a proposal, read the queue, backfill decisions a codebase made but never recorded), and in GitHub Copilot it now goes further. Everything below is advisory by construction.

adr-review is a Copilot CLI dynamic workflow (ADR-0045). It collects the changed files, runs adr check and adr lint deterministically, and only then spends model credits: one judgment per governing decision, not per file. A missing judgment is reported as unverified, never dropped, because a silently missing verdict looks exactly like a clean one. The result is data for you to read, not a gate; the governing-decisions Action stays the CI authority.

The decision-review canvas (ADR-0046) is the same data for a person. In the GitHub Copilot app it opens beside the conversation and shows which decisions govern the change, which are history, and how each was judged. Refresh is free. Run review spends credits, and the button says how many decision-checker calls it will make before you press it (ADR-0047). It's a loopback server with a per-panel token and a strict CSP, and it never renders repository text as HTML, because ADR titles are untrusted input too.

The decision board (ADR-0050) maps the whole corpus: the supersession graph and the review queue's raw facts. It can record an approval or an objection, and that is where I was most careful (ADR-0052). A click prepares the action, a second click sends it, and nothing is written unless Copilot's own confirmation dialog comes back with a literal yes within two minutes. In autopilot, or when the session mode can't be read, it refuses without asking. The board never calls a proposal ready, and neither canvas has an Accept button. Ratification stays one human command away, not one click away from a model-written page.

Copilot also gets three read-only tools (adr_check, adr_explain, adr_lint) and two advisory hooks (ADR-0048, ADR-0049). The hooks tell the agent which decisions govern its change at session start, and again right after it edits a governed file. I originally wanted that second note before the edit. Measurement killed the idea: on the Copilot CLI version I tested, a pre-tool hook that hangs holds the tool call unexecuted. An advisory plugin that can stall the agent whenever its own process is slow isn't advisory, so the note comes after the edit and says plainly that it blocked nothing.

Install

In Copilot CLI: copilot plugin marketplace add mbeacom/adrkit, then copilot plugin install adrkit@adrkit. The same plugin installs in Claude Code (/plugin marketplace add mbeacom/adrkit) and through Microsoft's Agent Package Manager. The workflow, canvases, tools, and hooks are Copilot-only; the skills and commands work everywhere.

Decisions anyone can browse

A corpus in docs/adr is great for the person changing the code and invisible to everyone else. Two read-only surfaces fix that, both built on a new @adrkit/sdk so they can't disagree with the CLI (ADR-0053).

The decision portal is a static site for GitHub Pages (ADR-0054): records, the supersession graph, the review queue, and which decisions govern each path. It has no forms and changes nothing. adrkit's own is at adrkit.dev/decisions, rebuilt on every deploy.

The REST API is a server selector in the existing container image (ADR-0055). It only answers GET, names the commit in every response, and returns byte-identical bodies for the same commit and parameters. It also authenticates no one, so it binds localhost unless you tell it otherwise and logs a warning when you do. Put a gateway in front of it before it leaves your machine.

Generated documentation is welcome next to all of this, on one condition (ADR-0037): a generated wiki is a downstream read model. It can cite and explain a decision; it can't accept, supersede, or rewrite one.

The honest ledger

adrkit has 22 stars, six forks, outside contributors whose work shipped, and a growing set of projects beyond mine that run it. The project grades every capability on a three-rung evidence ladder: unit and contract coverage, then a maintainer-owned reference repository, then external validation. The records still mark rung three open, because that rung counts evidence written down in the repository, and most of that usage hasn't been.

Some of what's above is measured less than the rest. The decision board's Approve was exercised in the Copilot app; Object and Resolve were not. The canvas's call-count label and proposal queue have not been rendered in an app session yet. The records say so, and so should this post.

Not built, and labelled that way in the decision index:

  • The probabilistic evaluator passes. Only the deterministic, model-free Pass 0 exists.
  • The Backstage catalog integration. The adapter boundaries exist; the release does not.
  • GitHub Projects sync (ADR-0058) and read-only federation across an organization's repositories (ADR-0059). Both are accepted decisions with no code yet.
The rule I broke

adrkit's whole position is that humans ratify before anything is binding. Six of its own records (ADR-0047 through ADR-0052), dated 8 and 9 October, describe features that shipped that week, and they sat at proposed until I ratified them on 30 October. Nothing enforced the order, so I didn't keep it. The records were right; my sequencing wasn't. A governance tool that skips its own gate is making the argument against itself.

Try it

npx @adrkit/cli explain <path>
npx @adrkit/cli migrate --from madr --dry-run

If you already run adrkit, I'd like to hear what it caught and what got in your way; a note in Discussions is the fastest route to the roadmap. If you run an architecture review board or have agents writing plans against a real codebase, start with adr explain on a file you know has history. The decisions that built adrkit are in its own corpus, rejected alternatives included.


Sources. Command behavior comes from the command reference and the plugin README. Design choices, measurements, and evidence rungs come from the linked ADRs; the pre-tool hook measurement is in ADR-0049. Ratification dates come from each record's decidedAt. Star and fork counts come from the GitHub repository page, and the release count from the npm registry, both checked on 5 November 2026.