{"slug": "too-many-adrs-made-our-ai-follow-outdated-decisions-the-fix-keep-them-all-let-an", "title": "Too many ADRs made our AI follow outdated decisions. The fix: keep them all, let an index name the current ones", "summary": "A developer rebuilt the ADR index in ELN workflow, an in-house plugin suite for Claude Code, after a naive parser that read ADR Supersedes fields reported live decisions as superseded — in a 270-ADR repository, all 5 records it flagged were false alarms. The new index applies strict header rules, only marking a record superseded when the header is written in a strict machine-readable form, and raised the count of visible live decisions from 126 to 155 by recognizing bold status lines. Across 38 sessions, the agent never cat-ed every ADR at once, instead listing the folder 36 times and reading files one at a time as the index directed.", "body_md": "The obvious fix for \"my AI agent keeps relying on superseded architecture decisions\" is a small parser: read each ADR's `Supersedes` field and mark the old records as replaced. We built exactly that. Then we ran it on two other repositories of ours, with 270 and 93 ADRs, and it reported live decisions as dead. In the larger repository, every one of the 5 records it called \"superseded\" was a false alarm.\n\nA parser that just picks up ADR numbers does not just miss things. It creates misreadings of its own, and an AI agent will trust them. This is a write-up of how we rebuilt the ADR index in ELN workflow, our in-house plugin suite for Claude Code, so that \"which decisions are current?\" is answered by one deterministic set of rules instead of by the model reading ADR bodies and guessing. It follows an earlier article, [The real reason AI ignores existing decisions: designing an ADR context auto-injection hook](https://dev.to/uehara/the-real-reason-ai-ignores-existing-decisions-designing-an-adr-context-auto-injection-hook-34e2), which ended with two known gaps: the injected list stopped at 30 entries, and 29 records written in another format were not read at all.\n\nThe hooks are Claude Code specific. The problem is not: any coding agent, Claude Code or Codex or another, that works in a repository with a few hundred ADRs has to find out which ones still hold.\n\n`status:` label made an accepted record unreadable. Another repository's ADR number pointed at an unrelated local record as the replacement. Now the tool says \"superseded\" only when the header is written in a strict form.`ls docs/adr/` for the rest, and 29 live decisions were invisible because their status line was bold. Only 3 records declared their replacement in a machine-readable header; 76 mentioned it in prose.`cat`, and it never blocks), a CI check on the header lines a pull request changes, and one command that updates the old ADR, the new ADR and the index together.\nThe first version picked up every ADR number in the `Supersedes` and `Superseded by` fields. Checked against the real files and run on the two other repositories, it got four kinds of header wrong.\n\n`Supersedes: (none — amends ADR-xxxx)`: nothing was replaced, this record amends another one. Picking up the number made the amended record look superseded. In our plugin's own repository, two live decisions would have been reported as replaced. The other repository had 7 records written as \"none (does not overturn ADR-xxxx)\".`- status: Accepted` was not read, so an accepted record counted as unknown.\nThe rules that came out of this are short, and they are mostly about what the parser refuses to infer:\n\n`ADR-NNNN` numbers. Any prose in the value makes it `none` (or the Japanese \"なし\") creates One suggestion was to write reading rules into a README. I did not trust a note the agent may or may not read without a mechanical guard, so I measured first. At that point the repository had 167 ADRs, 2,171,690 bytes in total, the largest 68,777 bytes.\n\n`- Status: Accepted` but not `- **Status**: Accepted`. 30 records used the bold form and 29 of them were live. The index saw 126 live decisions; the real number was 155.`sed`, `head` or `tail`. The existing large-file warning watched only the Read tool, and only above 1,000 lines. The longest ADR was 648 lines. It never fired.\nThe behavior I was worried about, the agent `cat`-ing every ADR at once, happened zero times in those 38 sessions. What it did was list the folder (36 times), pick files by name and read them one at a time, which is exactly what the index told it to do.\n\nADRs exist so the reasoning behind today's design can be traced; deleting old ones loses that. So the files stay as they are: nothing deleted, moved or split. The plugin's skills, hooks and scripts refer to ADRs by number and path, and at 167 records only one ADR was not referenced from anywhere. Moving files would break those references.\n\n\"Is this decision current?\" is answered by an index that one parser builds from the header fields (status, date, supersedes, superseded by) every time. There is no committed `index.json` and no cache, because a stored index drifts away from the files the same way the hand-written table did.\n\nThe human-readable table in `docs/adr/README.md` is still there, but it is generated: `eln-adr` rewrites a marked block, and CI fails if the block differs from a fresh run. The brief and the read warning never use that table; they use the parse of the files.\n\n| Effective status | Meaning | Counted as current | \n|---|---|---|\n| live | Accepted or Amended, not replaced | yes | \n| partial | Partly replaced by a newer record | yes, with the replacement shown | \n| superseded | Replaced by a newer record | no | \n| proposed | Not yet accepted | no | \n| rejected | An option we did not take | no | \n| unknown | Status not in the fixed vocabulary | no, and shown as a count | \n\nAn index nobody reads changes nothing. There are four paths.\n\nWhen Claude Code starts, a hook (a script Claude Code runs automatically at fixed moments) puts this into the agent's context.\n\nThe order is fixed: counts, how to use the CLI, the superseded table, the 10 newest records by revision date, the proposed count. The budget is 1,200 bytes, and the trimming order is fixed too (proposed down to a count, shorter titles, newest 8, then 6, the superseded table down to a count, even shorter titles, and finally no newest list). The counts line, the CLI line and the number of superseded records always stay. The brief never sends the agent to `ls docs/adr/`. After a compact or a resume, only a pointer of 300 bytes or less is injected: pushing a large block back in right after the context was shrunk would undo the shrink, and the index can be derived again at any time with the CLI.\n\nA second hook runs before the agent reads an ADR, looks up that record's effective status, and speaks up only when it matters.\n\n`awk`, `bat`, `less` or `more` has an ADR path in its arguments.`show`. Three or more files, or a glob, is called out as a bulk read.\nEvery warning includes the CLI's path. Subagents that Claude Code starts internally never receive the session-start brief, so for them this warning is the only pointer.\n\n`eln-adr show`: the key points first\n`show` returns one record's essentials in 2.5 KB or less.\n\nHere a 25,216-byte record becomes 878 bytes. Status, replacement, amendments, heading line numbers and the opening of the decision are enough to decide whether to read more, and if so, which line range.\n\n`show` also puts ⚠️ on a partly superseded record and marks the replacement \"(partial)\", telling the agent to read the replacement for that part. The record itself still counts as current; the status line says \"effective: partial\".\n\nA correct index is undone by the next badly written header. CI checks the ADRs a pull request adds or changes, and only the header lines that changed: the status word, a `- Date: YYYY-MM-DD` line, the format of the supersede fields, a supersede declared on only one side, new duplicate numbers. Each failure has a reason code. Untouched old records are never blocked, so the 167 existing files did not have to be rewritten at once.\n\nAccepting, amending and superseding are one command each. `eln-adr supersede <old> --by <new>` writes the old record's \"Superseded by\", the new record's \"Supersedes\" and the index table together. It is a dry run by default and writes only with `--write`. Before writing, it parses the result again and writes nothing at all if any of these fails: lines outside the intended fields are unchanged, the CI check has zero blocking findings, running the same command again changes nothing, and every declaration that was already in the field is still there.\n\nMeasured with the same commands, before at 167 records and after at 168 (the ADR for this change itself was added):\n\n|  | Before | After | \n|---|---|---|\n| Text the agent receives at startup | 3,511 B (30 oldest) | 1,140 B (counts, superseded table, newest 10) | \n| Live decisions visible in the index | 126 | 155 | \n| Superseded table | 0 rows | 5 rows (plus 2 pending) | \n| \"Check the rest with `ls docs/adr/` \" | yes | no | \n\nAnd again later, at 179 records:\n\nKeeping every ADR was the right call. The problem was leaving the model to infer \"is this one current?\" from the bodies. As long as it does, the startup list leans further toward old decisions as the count grows, the agent opens bodies one by one to check, and it spends tokens doing it. We moved the answer into an index built every time from one set of rules, delivered it at startup and at the moment an old record is about to be read, and kept new headers strict through CI and one-step commands. The rule that mattered most was deciding up front what the parser must not infer; without it, the parser itself reports live decisions as replaced.\n\n`docs/adr/`. The header carries a status (Accepted, Amended, Proposed, Superseded, Rejected) and fields for what it supersedes and what superseded it.`eln-adr` in our plugin's repository, laid out as a terminal. The tool prints in Japanese, so the labels are translated in this English version; numbers, record numbers and byte counts are unchanged. Other changes: the CLI path, which contains my home folder, is shortened to `<plugin>/scripts/eln-adr.py`; the design record number of this mechanism itself, in the tag the hooks print, is shown as `ADR-xxxx`; and the titles in the brief's \"newest\" list, plus the title and text of the example record in figures 2 and 3, are masked with ■ because they name or describe internal systems. The record numbers are the real ones.\nArticles on ELN workflow — our in-house plugin suite that enforces AI development quality — will continue as a series. If you are interested, please like and follow.\n\nOur products: [https://www.eln.ne.jp/products](https://www.eln.ne.jp/products)\n\n**EarthLink Network** builds its own products to run the whole company on AI — each one born from a need along the way. See the full index of what we build:\n\n→ [The 18 products EarthLink Network builds in-house](https://dev.to/uehara/the-18-products-earthlink-network-builds-in-house-hb1)\n\nFor more about the company and each product, visit [www.eln.ne.jp](https://www.eln.ne.jp).", "url": "https://wpnews.pro/news/too-many-adrs-made-our-ai-follow-outdated-decisions-the-fix-keep-them-all-let-an", "canonical_source": "https://dev.to/uehara/too-many-adrs-made-our-ai-follow-outdated-decisions-the-fix-keep-them-all-let-an-index-name-the-4hk9", "published_at": "2026-10-29 03:09:34+00:00", "updated_at": "2026-10-29 03:11:13.739676+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "large-language-models"], "entities": ["ELN workflow", "Claude Code", "Codex"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/too-many-adrs-made-our-ai-follow-outdated-decisions-the-fix-keep-them-all-let-an", "markdown": "https://wpnews.pro/news/too-many-adrs-made-our-ai-follow-outdated-decisions-the-fix-keep-them-all-let-an.md", "text": "https://wpnews.pro/news/too-many-adrs-made-our-ai-follow-outdated-decisions-the-fix-keep-them-all-let-an.txt", "jsonld": "https://wpnews.pro/news/too-many-adrs-made-our-ai-follow-outdated-decisions-the-fix-keep-them-all-let-an.jsonld"}}