Skip to main content

Command Palette

Search for a command to run...

Why your AI coding agent keeps writing legacy code

Updated
5 min read
A
ArchSteer – stop AI agents from cloning your legacy code

You're three months into migrating off Express. New endpoints are supposed to be Next.js route handlers. You ask Cursor to "add a refund endpoint," and it cheerfully hands you… a new Express router, with raw SQL inlined in the handler, exactly like the file next to it.

You didn't ask for legacy. You got legacy anyway. And if you're not reading every diff (you're not — that's the whole point of agents), it merges.

I started calling this the agent copy-paste defect, and once you see the mechanism it's hard to unsee.

Why agents do this

An LLM coding agent's strongest signal is proximity. When it edits controllers/payments.ts, the most relevant context it has is the surrounding files: the adjacent controllers, the imports they use, the patterns they repeat. So it does the locally-reasonable thing — it matches them.

That's great when your codebase is consistent. It's a disaster mid-migration, because the thing that dominates the local context is the old pattern. There's more legacy than target, so the legacy wins. The agent isn't being dumb; it's being a very good pattern-matcher pointed at the wrong patterns.

This is the part teams underestimate: agents don't just fail to help your migration — they actively fight it. Every feature shipped during the migration adds more legacy-shaped examples, which makes the next agent run more likely to produce legacy. Migrations that should take a quarter take forever because the codebase keeps voting for its own past.

"Just write better docs" doesn't work

The instinct is to write an ADR or a CONTRIBUTING note: "use the repository pattern, no raw SQL in controllers." Two problems:

  1. Docs rot. The doc was true the day you wrote it and drifts every day after. Nobody trusts the wiki, including the agent.
  2. The agent never reads it at the right moment. A static rule buried in /docs competes with hundreds of lines of contradicting code sitting right next to the file being edited. Proximity beats prose.

There's a name for the failure mode when an agent loses the plot as nearby context overwhelms its instructions: context degradation. Stale or distant guidance simply loses to the strong local signal.

The reframe: stop authoring, start emitting

Here's the shift that actually helped me. Documentation written by humans is always behind reality. Documentation derived from the code itself is reality. So instead of maintaining an architecture doc, derive one — continuously — from the code, and use it to feed the agent the specific target pattern for the specific file it's about to touch.

I built a small CLI around this idea (ArchSteer, pipx install archsteer, MIT). It's local-first and reads your code statically — it never executes it. Three things it does that map directly onto the defect above:

1. See what your architecture actually is

pipx install archsteer      # needs Python 3.10+
cd your-repo
archsteer xray

No config. It parses the repo, builds a model (components, layers, dependencies, data-access points, external calls), and writes a living map plus an evolution feed — what structurally changed since last time. The first time you run this on a system nobody fully understands, it's genuinely uncomfortable in a good way.

2. Steer the agent toward the target — at the right moment

archsteer steer -f src/controllers/payments.ts -t "add refund endpoint"

This writes a sharp, file-scoped directive into CLAUDE.md / AGENTS.md / .cursor/rules — only the rules relevant to the files in play, each pointing at the governing ADR. Not a wall of standards the agent will ignore; the two or three invariants that matter for this edit, in the place the agent actually looks. Now when you re-run the same prompt, it builds toward the target instead of cloning the neighbor.

The important design choice: it doesn't dump your whole architecture into the prompt (that just burns context and money). It injects the delta.

3. Stop the bleeding without freezing features

The bit that makes this usable on a real team is the ratchet:

archsteer baseline    # accept today's debt as the starting line
archsteer check       # in CI: fail only on NET-NEW violations

You don't have to fix all the legacy to start enforcing the target. You snapshot existing debt and block only regressions. Architecture can get better but not worse, while features keep shipping. That's the difference between a linter people turn off and a gate people keep on.

Two more things fall out of having a real model:

  • Drift Index — conformance against your declared target, as a single number that trends over time. Put it in the weekly review and it stops being a vibe.
  • Auto-drafted ADRs — when an agent adds a new dependency, datastore, or boundary, that's a real architectural decision usually recorded nowhere. The tool drafts an ADR for it (conservatively — only boundary-altering changes, never internal reshuffles) and leaves ratification to a human. "No architectural change without a decision record" becomes enforceable.

Where this is honest about its limits

  • It's static analysis. The model is heuristic — path conventions and import/call graphs, not a theorem prover. Architects annotate what the heuristics can't infer.
  • Decision detection is deliberately conservative to avoid drowning you in noise; it drafts, it never auto-commits.
  • Today it covers TypeScript/JavaScript and Python. The engine is language-agnostic underneath, but those are what's wired up.

The takeaway

The mental model is the useful part, with or without my tool: in the AI-development era, documentation is no longer paperwork for humans — it's infrastructure for agents. If it's stale, your agents are steered wrong by default. So derive it from the code, keep it current, and feed the agent the specific target pattern for the specific thing it's editing.

If you want to poke at it, there's a zero-backend interactive demo at archsteer.com/demo and the CLI is pipx install archsteer.

What patterns have your agents stubbornly cloned? I'm collecting war stories.