Skip to content

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/ beside modules/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.md with 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 ​

text
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.ts is the only money math).
  • Characterization tests on the canonical module so duplicates are unnecessary for "safety."

Recovery prompt ​

text
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 ​

text
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 / InvoiceID across layers without a convention map.
  • Same concept: Customer, Account, Org in 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/glossary or 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 ​

text
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.md describes a queue the code replaced with cron.
  • README setup steps fail on a clean clone.
  • Spec still lists endpoints the agent removed.
  • CLAUDE.md invariants 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 ​

text
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.md updated or explicitly unchanged with reason
  • [ ] No aspirational docs describing unbuilt behavior as fact
  • [ ] Setup instructions still work

A field manual for AI-native software engineering.