Architecture Erosion
One agent session rarely ruins an architecture. Twenty sessions do: each adds a locally reasonable pattern that contradicts the last. The codebase accumulates parallel abstractions, duplicated domain logic, and docs that describe a system that no longer exists. Agents accelerate erosion because they have no taste continuity across sessions and will copy whatever precedent is nearest in the files they opened. Consistency is a human responsibility enforced through repo docs and review — it does not emerge from prompting alone.
1. Ignores existing architecture
Symptoms
- New
services/billing2/besidemodules/billing/. - Direct DB access from a route handler in a codebase that mandates repository layers.
- New event bus where the system already uses an outbox table.
- "Clean" greenfield patterns dropped into a legacy hexagonal app.
Root cause
Agent reasons locally from the files in context, not from ARCHITECTURE.md. Unwritten boundaries might as well not exist. See Constraints and Validation and Docs as Memory.
Early detection
- Plan review: "which existing module do you extend?" — "I created a new one" is a red flag unless the spec said so.
- Diff path list vs documented package map.
- Import-direction violations against declared dependency rules.
Prevention
- Load-bearing boundaries in
CLAUDE.md/ARCHITECTURE.mdwith examples of forbidden imports. - Spec section: "Integration points — extend X, do not create Y."
- Reject PRs that introduce a second pattern for the same concern.
Recovery prompt
You introduced a parallel structure that ignores existing
architecture. Read ARCHITECTURE.md and <existing module paths>.
Produce a relocation plan: move the new behavior into the
existing modules, delete the parallel tree, preserve behavior
via tests. Do not invent a third pattern. Stop if a real
architecture change is required — escalate for an ADR.Review checklist
- [ ] Extends documented modules; no surprise top-level packages
- [ ] Import rules respected
- [ ] Same concern uses the same mechanism as the rest of the system
- [ ] ADR exists if the architecture intentionally changed
2. Duplicates logic
Symptoms
- Same validation or pricing formula copied into API and worker.
- Near-identical helpers with different names (
calculateTotal/computeSum). - Bug fixed in one copy; the other keeps the bug.
Root cause
Agent finds a local place to put code faster than it finds the canonical function. Search is shallow; duplication is fluent. Navigability debt makes the right helper hard to find — see Navigability.
Early detection
- Reviewer prompt: "list duplicated domain rules in this diff."
- Semantic grep for distinctive constants/strings from the new logic.
- PR size large with few new abstractions touching shared packages.
Prevention
CLAUDE.md: "search for existing helpers before writing new ones; prefer extend over copy."- Canonical locations documented (
src/domain/money.tsis the only money math). - Characterization tests on the canonical module so duplicates are unnecessary for "safety."
Recovery prompt
Identify duplicated domain logic introduced or worsened in
git diff main...<branch>. For each duplicate: keep the
canonical location (prefer existing module), delete the copy,
update call sites. Add a test on the canonical function that
would have caught the split-brain bug.Review checklist
- [ ] No second copy of domain rules already in
src/domain(or equivalent) - [ ] Shared constants not redefined
- [ ] Fix applied once at the canonical site
3. Unnecessary abstractions
Symptoms
- Factory/strategy/DI container for a single implementation.
- Premature
Base*/Abstract*with one child. - Indirection that exists to look "enterprise" in a 3k-LOC service.
- Future-proofing for a second payment provider you do not have.
Root cause
Training data over-represents large-system patterns. Agents pattern-match "serious codebase" to ceremony. Ambiguous "make it clean/extensible" prompts invite this. Opposite of shallow demos — same ambiguity, different median.
Early detection
- Count implementations per interface; one is suspicious unless required by a boundary you already have.
- Ask the agent to justify each new type with a current call site need, not a hypothetical.
- Cognitive load: can a new engineer find the behavior in one hop?
Prevention
- Spec: "no new abstractions unless needed for a second implementation that exists in this PR or an approved ADR."
- Prefer duplication of two lines over a wrong abstraction (then dedupe when a third appears — human rule).
- Architect review on structure-only PRs.
Recovery prompt
Collapse unnecessary abstractions in <paths>. Inline single-
implementation interfaces/factories. Preserve behavior; tests
must stay green without rewriting assertions to match new
indirection. List each removal and why it was safe.Review checklist
- [ ] Every new interface has ≥2 real implementations or a documented boundary need
- [ ] No Base/Abstract with a single child added in this PR
- [ ] Indirection does not obscure the domain rule under review
4. Inconsistent naming
Symptoms
invoiceId/invoice_id/InvoiceIDacross layers without a convention map.- Same concept:
Customer,Account,Orgin different modules. - Files named for intent the code no longer matches.
Root cause
Each session picks locally common names from nearby files or from training priors. No cross-session style memory unless the repo defines a glossary.
Early detection
- Glossary diff: new terms not in
docs/glossaryor spec. - Mixed casing conventions in a single PR.
- Public API names that disagree with existing endpoints.
Prevention
- Ubiquitous language section in the spec /
CLAUDE.md. - Lint rules for API/schema naming where possible.
- Forbid renaming at a distance in feature PRs — renames are their own PR.
Recovery prompt
Align naming in this diff with CLAUDE.md glossary and existing
<module> conventions. Do not rename unrelated stable public APIs
without explicit approval. Produce a before/after term map.Review checklist
- [ ] New terms match glossary or are added to it deliberately
- [ ] No drive-by renames outside task scope
- [ ] Public API consistency with existing endpoints
5. Documentation no longer matches code
Symptoms
ARCHITECTURE.mddescribes a queue the code replaced with cron.- README setup steps fail on a clean clone.
- Spec still lists endpoints the agent removed.
CLAUDE.mdinvariants violated by current main.
Root cause
Agents update code freely and docs only when asked. Docs that lie are worse than no docs: future agents treat them as truth and compound the error. See Keeping Specs Alive.
Early detection
- Require doc touch in the PR template when behavior or setup changes.
- Doc-agent role after merge for user-facing and agent-facing docs (Agent Roles).
- CI link check / "run README commands" smoke on docs PRs where feasible.
Prevention
- Definition of done includes doc sync for agent-facing files.
- Prefer executable truth (tests, OpenAPI generated from code) over prose where possible.
- Delete stale sections rather than leaving "aspirational" docs.
Recovery prompt
Compare docs/ARCHITECTURE.md, docs/specs/<feature>.md, and
CLAUDE.md to the current code for <area>. List mismatches.
Update the docs to match the code OR revert the code to match
the docs — ask which when they conflict on a product/architecture
decision. Do not leave contradictions.Review checklist
- [ ] Spec/architecture/
CLAUDE.mdupdated or explicitly unchanged with reason - [ ] No aspirational docs describing unbuilt behavior as fact
- [ ] Setup instructions still work
Related
- Failure Catalog — index of all modes.
- Constraints and Validation — encoding boundaries agents will respect.
- Decision Records — intentional architecture change vs erosion.
- Docs as Memory — keeping the map honest.
- Case Study: Architecture Recovery — rebuilding after erosion.
- What Not to Delegate — structure decisions stay human.