DEV Community

Andre Burgstahler
Andre Burgstahler

Posted on Originally published at Medium

Put Your Decisions in the Repo: ADRs as the Backbone of an AI Coding Workflow

This article first appeared on Medium: https://medium.com/@r3dtuxedo/put-your-decisions-in-the-repo-adrs-as-the-backbone-of-an-ai-coding-workflow-2a86fad560aa

Specs and decision logs that live in a chat project drift away from the code. Move them into the repository, wire them into AGENTS.md, and your planner, builder and reviewer agents finally read the same page.

A few days ago someone on r/ClaudeCode posted their full Claude Code loop and asked which step to change. It was a careful setup: a "planner" chat in a claude.ai Project holding the brief, a decisions log with 26 entries and a status file; plan mode with three Plan subagents; lint and typecheck hooks; CI; a Claude GitHub Action review; /ultrareview on the riskiest PR.

The cost list was the honest part. Three review layers per feature. "Pasting plans between Cursor and claude.ai by hand." And at the end of every feature: squash-merge, "then log decisions and status back in the planner project."

One of the questions was the one I want to answer here:

Is a separate planner (claude.ai Project) worth it, or do you get the same from specs and reviews inside Claude Code with a docs folder?

My answer is the docs folder. Not because chat projects are bad, but because a decision that lives outside the repo is one your builder and reviewer can't see unless you carry it over by hand.

People keep asking versions of this question: the Claude Code loop thread itself, a six-month personal harness built to juggle Claude Code sessions across many repos, an agent loop that wrote most of its own PRs, and a Codex user stuck in two-hour iterations. Here is the long answer, with real files.

The copy-paste tax

The planner chat holds the truth. The builder (Claude Code in the editor) holds the code. The reviewer sees a diff. You are the bus between them.

Every hop costs something:

  • Attention. You paste the plan into one tool, the result into another, the review back into the first.
  • Drift. The decisions log is updated after the merge, if you remember. Until then the two disagree, and nobody notices until an agent confidently follows the stale version.
  • Blind reviewers. A reviewer that only sees the diff can tell you whether the code is reasonable. It cannot tell you whether the code is what you decided, because the decision isn't in its context.

The same pattern shows up outside Claude. In the r/ChatGPTPro Codex thread, the author describes a loop where each round takes one to two hours and they "paste the errors/output back." The most useful replies were not about models. One person spends two to three times longer planning and says "I almost never let the AI choose packages, DB, languages, security." Another suggests "one task, one test, one recorded result," because "long sessions start carrying old assumptions."

Those are decisions and specs. The only question is where they live.

What goes in a spec, and what goes in an ADR

I use two kinds of files, and the split matters more than the format.

A spec is per feature and temporary. It says what we are building now: the goal, acceptance checks, the scope boundary, open questions, and the current status. When the feature ships, the spec is done. I keep it, but nobody needs to read it next month.

An ADR is per decision and durable. An Architecture Decision Record captures one decision that shapes everything after it: how tenants are isolated, which queue we use, why migrations are append-only. The format comes from Michael Nygard's 2011 post Documenting Architecture Decisions. It has five parts: Title, Context, Decision, Status, Consequences. Nygard's rules are still the right ones: keep each record to one or two pages, number them sequentially and never reuse numbers, and when a decision is reversed, keep the old record and mark it superseded.

That last rule is what makes ADRs useful to agents. An agent reading 0007 marked "Superseded by 0011" knows both the history and the current rule.

The test I use to decide where something goes:

  • Will this still constrain the code after this feature merges? It is an ADR.
  • Is it about this feature's scope, steps or acceptance? It goes in the spec.
  • Is it a command, a convention, or a pointer? It goes in AGENTS.md.

You don't need a tool to write ADRs, but two are worth knowing. adr-tools is a small CLI: adr init doc/architecture/decisions creates the directory and a first record, adr new "Use Postgres RLS for tenant isolation" adds a numbered one, and adr new -s 9 "..." writes a record that supersedes ADR 9 and updates the old one's status. Archgate goes further: ADRs live in .archgate/adrs/ with YAML frontmatter, and an ADR can have a companion .rules.ts file with checks that archgate check runs against the codebase, exiting with code 1 on a violation so CI can block the merge. Its editor plugins for Claude Code and Cursor are in beta at the time of writing.

Wiring them into AGENTS.md and CLAUDE.md

Files in docs/ do nothing until an agent is told when to read them. That is the job of AGENTS.md.

AGENTS.md is an open format: plain Markdown at the repo root that coding agents read for build steps, conventions and instructions. Codex reads it, and so do many other tools. Claude Code reads CLAUDE.md, and the current docs describe how the two interact. Recent versions read AGENTS.md directly, but only when there is no CLAUDE.md on the path. If you keep both, the documented way to share one source is an import at the top of CLAUDE.md:

@AGENTS.md

## Claude Code
Use plan mode for anything under `supabase/migrations/`.
Enter fullscreen mode Exit fullscreen mode

One detail from the same page caught me out. A CLAUDE.md that tells Claude in words to read AGENTS.md only works if Claude decides to open the file. The @ import loads it at launch. I had the words version in my own repo; more on that below.

Anthropic recommends keeping each CLAUDE.md under 200 lines, and imported files load at launch, so imports don't save context. So do not import every ADR. Put an index and a reading rule in AGENTS.md, and let the agent open the two or three records the current spec links to.

Reviewing against decisions

This is the part that changed my reviews the most.

A generic AI review gets the diff and "review this." It returns plausible observations: naming, a missing null check, more tests. Some are useful. Many are noise, and it can't catch the expensive mistake: code that works but breaks a decision.

A decision-aware review gets three things: the diff, the spec for this feature, and the ADRs that spec links to. Its question is narrower: does this diff satisfy the spec's acceptance checks, and does it violate any linked ADR? That is a smaller context than a long chat history, and a sharper question than "review this." I won't put a number on the savings, because I haven't measured them carefully. What I can say is that the findings got more specific, and they became checkable: "this query bypasses the RLS rule in ADR 0007" is either true or it isn't.

It also fixes the "log decisions back" chore. If the PR template asks for it, and the reviewer flags new patterns that arrive without an ADR, recording the decision becomes part of shipping the change: reviewed in the same diff, merged in the same commit.

Two cautions from people running these loops:

  • In the r/ClaudeCode thread, one commenter found that per-task reviewer subagents caught real defects, but a final whole-branch review on a larger build "still found three important issues the task reviews had missed, all of them interactions between tasks." Decision-aware review per task doesn't replace one review of the whole branch.
  • In the r/ClaudeAI thread about bunker, a tool where sandboxed Claude agents implement GitHub issues and a second Claude session reviews them, anything touching CI, its own config or agent instructions waits for a human. A commenter asked whether that also covers the reviewer's prompt, "the one place it could quietly lower its own bar." The same applies to ADRs. An agent can draft one. A human accepts it.

A small worked example

Here is the layout I use, on a made-up multi-tenant order service close to the one in the Reddit thread.

.
├── AGENTS.md
├── CLAUDE.md
├── .claude/
│   └── agents/
│       └── adr-reviewer.md
├── .github/
│   └── pull_request_template.md
└── docs/
    ├── decisions/
    │   ├── README.md                     # one line per ADR, with status
    │   ├── 0001-record-architecture-decisions.md
    │   ├── 0007-tenant-isolation-with-rls.md
    │   └── 0008-migrations-are-append-only.md
    └── specs/
        ├── 0012-bulk-order-import.md     # status: in progress
        └── done/
Enter fullscreen mode Exit fullscreen mode

The ADR

# 0007. Enforce tenant isolation with Postgres row-level security

Status: Accepted
Date: 2026-10-12

## Context
Every table that holds customer data has a tenant_id. Application-level
filters have been missed twice in review. Supabase exposes Postgres
row-level security (RLS) directly, and the anon key reaches the database.

## Decision
We will enable RLS on every table with a tenant_id and scope policies to
the tenant claim in the JWT. Application code will not add its own
tenant_id filters as the only line of defence. Service-role access is
allowed only in server code under `src/server/jobs/`.

## Consequences
- New tables need a policy in the same migration that creates them.
- Tests must run as a tenant user, not as service role.
- Bulk jobs run as service role and must set tenant_id explicitly.
Enter fullscreen mode Exit fullscreen mode

The spec

# Spec 0012: Bulk order import (CSV)

Status: In progress
Branch: feat/bulk-import
Decisions: ADR 0007, ADR 0008

## Goal
A merchant uploads a CSV of up to 5,000 orders and sees a per-row result.

## Acceptance checks
- [ ] Rows for another tenant are rejected, tested as a tenant user
- [ ] Partial failure: valid rows import, invalid rows are reported
- [ ] New table `import_jobs` ships with an RLS policy (ADR 0007)
- [ ] No edits to existing migrations (ADR 0008)

## Out of scope
Excel files. Scheduled imports.

## Log
- 2026-10-19: plan approved; job runs in src/server/jobs/import.ts
Enter fullscreen mode Exit fullscreen mode

The AGENTS.md snippet

## Specs and decisions

- Before planning, read `docs/decisions/README.md` (the ADR index).
- Each feature has a spec in `docs/specs/`. Read it before writing code.
  Open every ADR listed under "Decisions" in that spec.
- Accepted ADRs are constraints. If a task requires breaking one, stop
  and propose a new ADR that supersedes it. Do not work around it.
- If you introduce a pattern other code will follow (a new table
  convention, a new dependency, a new auth path), draft an ADR with
  status "Proposed" in the same PR. A human changes it to "Accepted".
- At handoff, update the spec's checkboxes and Log. Do not keep status
  anywhere else.
- Never edit an Accepted ADR's Decision section. Supersede it.
Enter fullscreen mode Exit fullscreen mode

The reviewer

A Claude Code subagent in .claude/agents/adr-reviewer.md:

---
name: adr-reviewer
description: "Reviews a branch diff against its spec and linked ADRs."
tools: Read, Grep, Glob, Bash
---
You review one feature branch. Read the spec named in the PR, then every
ADR it lists. Run `git diff main...HEAD`. Report only:
1. Acceptance checks the diff does not satisfy.
2. Lines that violate a linked ADR, with file:line and the ADR number.
3. New patterns introduced without a Proposed ADR.
If none apply, say so in one line. No style comments.
Enter fullscreen mode Exit fullscreen mode

And the PR template line that turns the chore into a checkbox:

- [ ] Spec updated (checkboxes and Log)
- [ ] Decisions: new/superseded ADRs linked, or "none"
Enter fullscreen mode Exit fullscreen mode

Where I'm using this

ai-catapult is a small MIT-licensed CLI with Claude Code, Codex and OpenCode plugins. npx ai-catapult init . writes a deterministic scaffold with no LLM involved: AGENTS.md, CLAUDE.md and GEMINI.md, a .ai/ governance tree, and a .ai/handoff/NEXT-STEPS.md that lists what is left. The judgment calls, including ADRs, are left to the plugin running inside the agent.

The repo dogfoods the layout. Specs sit in docs/specifications/ACTIVE/, decisions in docs/architecture/adr/, and AGENTS.md calls itself "the single source of truth for agent-facing operating contracts." ADR-0001 also shows the log-it-back loop working: when a review round overturned part of the decision, the fix went into the ADR as a dated errata note rather than into a chat.

It also has the mistake I mentioned. Its CLAUDE.md points to AGENTS.md with a Markdown link, which is the "in words" version the Claude Code docs warn about. It should be @AGENTS.md. And ADR-0001 is longer than Nygard's one or two pages. Both are on my list.

Start with one decision

You don't need to migrate a whole chat project. Write the next decision that would hurt if an agent ignored it as a one-page ADR in docs/decisions/, and the next feature's spec as a file instead of a message. Add the six lines above to AGENTS.md, import it from CLAUDE.md, and point your reviewer at the spec's linked ADRs instead of the whole repo.

The first time a reviewer says "this violates ADR 0007" and it's right, you'll stop pasting plans between windows.


If this was useful, subscribe to Agents in Practice for one practical note on AI coding agents a week. Helping your team adopt Claude Code or Codex safely and without the surprise bill? I consult on exactly that: https://andre-burgstahler-9n5.pages.dev/.

This article was written with AI assistance and reviewed by the author.

Top comments (0)