Je hebt een prachtige CLAUDE.md geschreven. Een hele middag erover gedaan. Mappenstructuur gedocumenteerd, je regel dat DTOs bij elkaar staan, je "nooit default exports" voorkeur, je testconventies, alles erop en eraan.
De agent las het. Een minuut of tien gedroeg hij zich keurig.
Ergens rond tool call dertig maakte hij stilletjes een default export aan, dropte een Zod-schema inline naast een route handler, en gooide er voor de zekerheid nog een console.log bij. Je merkte het pas in code review, en tegen die tijd had het patroon zich al over vier files verspreid.
Dit is geen falen van de agent. Dit is precies het gedrag dat je zou moeten verwachten. En de oplossing is niet een beter geschreven CLAUDE.md.
Het waarschijnlijkheidsprobleem
Alles wat je in een prompt zet, inclusief CLAUDE.md en AGENTS.md, is een probabilistische instructie. Het model leest het, weegt het, en volgt het meestal op. Meestal is niet altijd. En op een lange taak met tientallen tool calls werkt "meestal" de verkeerde kant op.
Ik schreef daar eerder over. Het hooks-stuk maakte het punt dat voor dingen die elke keer moeten gebeuren, je het niet in een prompt zet. Je zet het in een hook.
Maar hooks zijn de muur aan het einde van de gang. Ze blokkeren gevaarlijke commando's, draaien formatters, killen slechte merges. Ze leren je niets. Ze brengen geen intentie over. Ze zeggen "nee" zonder ooit te zeggen "waarom."
Tussen de prompt (probabilistisch) en de hook (eindmuur) zit een laag die de meeste teams niet hebben gebouwd. Twee tools wonen in die laag. Architecture Decision Records en custom lint-regels.
ADRs: de waarom-laag
Een CLAUDE.md probeert alles te zijn. Stack, conventies, woordenlijst, do's en don'ts, projectstructuur. Het groeit naar 400 regels, en de agent leest het één keer aan het begin van de sessie en kan het middenstuk al niet meer ophalen tegen de tijd dat het ertoe doet.
Een Architecture Decision Record is het tegenovergestelde. Eén beslissing. Twee pagina's. Context, de alternatieven die je hebt overwogen, de beslissing, de gevolgen. Opgeslagen als markdown in docs/adr/, genummerd, onveranderlijk.
Het format is gepopulariseerd door Michael Nygard rond 2011, ruim voor al deze AI-onzin. Het overleeft omdat het één eigenschap heeft die niets anders heeft: het legt de redenering vast op het moment dat de beslissing genomen werd. Zes maanden later, als iemand (of iets) zich afvraagt waarom alle DTOs in src/dtos/users/ staan en niet naast de route, ligt het antwoord twee klikken verderop.
Dit is wat verandert als je een agent ADRs geeft. In plaats van te raden naar je conventies op basis van de code (waar hij het nieuwe patroon én de oude legacy ziet en beide ongeveer even zwaar weegt), kan hij de beslissing lezen. Hij kan docs/adr/ doorzoeken, het record vinden dat zegt "we plaatsen DTOs bij elkaar omdat X, Y, Z," en dat respecteren. Niet als een regel die hij kan vergeten, maar als gedocumenteerde context die hij on-demand opnieuw kan ophalen.
Het andere onderschatte aspect: agents zijn uitstekend in het schrijven van ADRs. Richt Claude op je bestaande codebase en zeg "scan dit op beslissingen die niet gedocumenteerd zijn en schrijf ADRs op basis van de patronen die je vindt." Hij produceert een eerste versie die voor 80% klopt. Jij corrigeert. Nu is je tribal knowledge een file, geen gevoel.
Dit is ook een verdediging tegen de lavalaag. Code versteent wanneer niemand zich nog herinnert waarom hij er staat. Een ADR is een thermometer in het gesteente.
Lint-regels: de hoe-laag
ADRs leggen het waarom uit. Lint-regels handhaven het hoe.
Generieke ESLint dekt syntax en de gebruikelijke valkuilen. Hij weet niet dat in jouw project Zod-schema's in src/dtos/ wonen en nooit inline in routes. Hij weet niet dat de presentation-laag nooit uit infrastructure mag importeren. Hij weet niet dat je console.log verbiedt omdat je een structured logger gebruikt.
Je kunt dat allemaal documenteren. De agent doet het toch verkeerd, want documentatie is een suggestie.
Custom lint-regels zijn anders. Ze laten de build falen.
De interessante kanteling, uit een recent Just Understanding Data-stuk, is dat voor AI-agents foutmeldingen ophouden fouten te zijn en onderwijsprompts worden. Als de agent een Zod-schema inline plaatst en de linter terugschiet:
DTO moet in src/dtos/<feature>/ staan. Zie ADR-0014.
Voorbeeld: zie src/dtos/users/create-user.dto.tsDe agent leest die melding, fixt de overtreding, en de volgende keer dat hij vergelijkbare code genereert wordt het patroon versterkt. Na twee of drie iteraties stopt hij met de fout. Niet omdat hij iets in diepe zin geleerd heeft, maar omdat de gestructureerde feedback de volgende generatie vormgeeft.
Eén cruciaal detail: zet inline-disable uit. Als de agent // eslint-disable-next-line kan schrijven om de regel te ontwijken, doet hij dat. Configureer je linter zo dat custom architecturele regels niet vanuit de file zelf het zwijgen opgelegd kunnen worden. Het hele punt is dat de regel niet onderhandelbaar is.
Alvin Sng van Factory.ai zegt het bot: agents schrijven de code; linters schrijven de wet. Lint draait bij opslaan, in pre-commit, in CI, en in de eigen tool-loop van de agent. "Lint groen" wordt de definitie van klaar. De agent corrigeert zichzelf tegen de regels, zonder dat er een mens nodig is voor het saaie werk.
De handhavingsstack
Dit is de hiërarchie die ik elk team dat met agents werkt zou aanraden te bouwen, van zacht naar hard:
| Laag | Mechanisme | Garantie | Kosten |
|---|---|---|---|
| Prompt | CLAUDE.md, AGENTS.md | Probabilistisch | Goedkoop |
| ADR | docs/adr/*.md | Semantische context, doorzoekbaar | Gemiddeld |
| Linter | Custom ESLint-regels | Deterministisch, blokkeert build | Gemiddeld |
| Hook | PreToolUse, PostToolUse | Absoluut, blokkeert tool call | Hoog |
Elke laag vangt wat de bovenliggende laag heeft laten passeren. Prompts dekken het normale geval. ADRs geven de agent iets om te raadplegen als hij redenering nodig heeft. Linters blokkeren patronen die door de prompt heen kwamen. Hooks stoppen het zeldzame drama.
De meeste teams hebben laag één. Een handvol heeft laag vier. Bijna niemand heeft de twee in het midden. In dat gat rot je codebase stilletjes weg.
Wat je nu moet doen
Probeer dit niet allemaal tegelijk te bouwen.
- Begin met één ADR. Pak de conventie waar je de agent deze week het vaakst op hebt gecorrigeerd. Schrijf hem als ADR. Twee pagina's, niet meer. Verwijs ernaar in CLAUDE.md zodat de agent weet dat de map bestaat.
- Schrijf één custom lint-regel. Pak de volgende meest geschonden conventie. Codeer hem als ESLint-regel met een gestructureerde foutmelding die de ADR noemt. Severity op
error. Inline-disable uit. - Kijk wat er breekt. De eerste keer dat de agent tegen de regel aanloopt, probeert hij eromheen te werken. Kijk hoe. Dat vertelt je of je foutmelding lesgeeft of alleen blokkeert.
- Aanhalen. Elke keer dat je de agent twee keer op hetzelfde corrigeert, is dat een kandidaat voor een ADR of een lint-regel. Schrijf geen derde correctie. Codeer hem.
De prompt is niet de spec. Dat is CLAUDE.md ook niet. De spec is de verzameling constraints die het systeem daadwerkelijk afdwingt. Alles daarbuiten is hoop.
De agent is niet lui en ook niet kwaadaardig. Hij gokt, heel snel, tegen een model van hoe je code "waarschijnlijk" eruitziet. Als je niet wilt dat hij gokt, stop dan met hopen dat hij je conventies onthoudt. Codeer ze ergens waar hij wel moet kijken.
Maak het onmogelijk om verkeerd te doen.