Agents can finally squash decades of technical debt, if you stop treating the old code as the source of truth.

Agentic coding is at its best inside a legacy system: rewrite a service into a better technology fit, swap the database engine, carve up the monolith while the contracts hold. But there is a pivot point most teams miss. Anchor the agent on the bloated codebase and it drifts into a slightly better clone of the old mistakes. The spec has to be the source of truth.

Legacy Code Is Not the Spec

Image: METAHEURISTIC

Alexander Myasoedov

+Alexander Myasoedov Alexander writes about the operational side of shipping production AI - agents, retrieval, evals, and the guardrails that keep them from going sideways.

Nobody designed the system you are maintaining. It accreted. The service is on its third generation of team: the first team picked the database because it was the one they knew, the second team wrapped it in an ORM to survive the first team’s schema, and the third team, yours, inherits both decisions plus the workarounds each layer grew to tolerate the one below it. None of these choices was crazy at the time. Aggregated across ten years and three handoffs, they add up to an architecture no one would choose on purpose, defended by nothing except the cost of replacing it.

That cost is the part that just changed. Rewrites were always the right answer and never the affordable one: every senior engineer has a service they know should be rebuilt, and a backlog that says it never will be. Agentic coding moved the line, because the expensive half of a rewrite was never the decision, it was the months of hands typing the replacement. When generation gets cheap, a whole class of rewrites that used to be fantasy becomes a quarter’s work. Which makes this the right moment to talk about the mistake that ruins most of them.

What just became cheap

Rewrite
Better tech fit
Move a service onto the stack it should have been built on, instead of the one its first team happened to know.
Swap
Engine change
Eliminate a bottleneck at the root - replace the database engine or driver instead of caching around it forever.
Carve
Monolith refactor
Split the monolith while holding the contract surface fixed, so consumers never notice the surgery.

These are the three moves I keep seeing land. The first is the full rewrite: a service built in the wrong technology for reasons that stopped mattering years ago, regenerated into a stack that actually fits the workload. The second is narrower and often pays faster: the bottleneck lives in one layer, a database driver that serializes everything or an engine that was wrong for the access pattern, and the agent rebuilds the storage layer against the same interface. The third is the monolith carve-up, where the overall structure of contracts stays fixed and the inside gets reorganized module by module.

None of this means the human leaves the loop. I am not a proponent of shipping LLM code to production unreviewed, and a rewrite is the worst possible place to start: the diff is the whole service. The point is different. Review capacity used to be spent defending the legacy system’s status quo; now it can be spent auditing its replacement. The economics moved, and the bottleneck moved with them, from writing the new system to verifying it.

The clone trap

Here is the pivot point, and the mistake I have seen often enough to call a pattern. The team decides to rewrite. They point the agent at the legacy repository and say: build this, but better. It feels safe, the old code is the most complete description of the system anyone has. And the result is poison, because the model treats that codebase as ground truth. It reproduces the accidental data model, the coupling that only existed because two modules once shared a deploy, the retry logic that papers over a bug nobody diagnosed. The agent drifts from the implementation just enough to feel new, and produces a slightly better codebase still grounded on the old mistakes.

Figure - Where you anchor the rewrite is where it lands
Anchor:
Press Run rewrite. Each dot is a generated candidate implementation, pulled toward whatever the agent was told to treat as ground truth. Anchored on legacy code, the candidates settle a polite distance from the old system - slightly better, still inside the basin, still on top of the old mistakes. Switch the anchor to spec and watch the same candidates converge on the properties instead, leaving the mistakes behind.

The failure is quiet, which is what makes it expensive. Every individual file looks improved: cleaner names, modern idioms, tighter functions. But the shape of the system came from the anchor, and the anchor was the thing you were trying to escape. You pay the full cost of a rewrite - the migration risk, the review burden, the customer exposure - and receive a fresh coat of paint on the architecture you already had. Six months later someone is proposing the same rewrite again, and this time there are two legacy systems to read.

The pivot point: Bloated legacy must not be the baseline of the new implementation. Whatever you hand the agent as ground truth is what it will converge on - and it converges on the shape, mistakes included, not just the features.

Start from the spec

So I start from scratch, given a spec. Not a vague product brief: a spec for the design, the behavior, the functional tests, and the properties the system is supposed to hold. What are the contracts consumers depend on. What must be true of every write. Which latencies are promises and which are accidents. That document becomes the source of truth, and the agent navigates from it, generating an implementation whose shape comes from the requirements instead of from ten years of sediment.

Legacy as baseline

  • Old codebase is ground truth
  • Model converges on the existing shape
  • Mistakes reproduced with cleaner names
  • Output: a slightly better clone

Spec as baseline

  • Requirements are ground truth
  • Model converges on stated properties
  • Old code consulted as evidence, not copied
  • Output: the system you meant to build

The legacy code still matters, but its role changes: it is a witness, not a template. You mine it for the requirements nobody wrote down - the undocumented endpoint two customers still call, the invariant that only exists as a WHERE clause, the timeout that turns out to be load-bearing - and each discovery goes into the spec as an explicit requirement. Then the spec, not the repo, is what the agent builds against. The difference sounds procedural and is actually the whole game: one workflow launders old mistakes into new code, the other forces every inherited decision to justify itself in writing before it survives.

Debt is not just cognitive load

There is a second reason to prefer the clean-room path, and it is one the comprehension framing misses. We talk about legacy as if the cost were all conceptual: code that is hard to understand, abstractions that frustrate. But some of the worst debt is operational. The CI build that takes forty minutes. The environment that takes a new hire a week to stand up. The test suite that is slow enough that nobody runs it locally, so everything is discovered in CI, forty minutes at a time.

In the agent era that operational debt compounds, because the feedback loop is now the unit of engineering. An agent iterating against a forty-minute build is an agent you are paying to wait. A rewrite anchored on the legacy repo inherits this too - the build system, the test architecture, the environment assumptions all come along with the shape. A rewrite anchored on a spec gets to state loop economics as requirements: builds under five minutes, tests runnable locally, an environment that stands up in one command. Those belong in the spec with the same standing as any functional contract.

The risks you still own

I want to be honest about the hard parts, because they do not disappear just because generation got cheap. Security review gets harder, not easier: a rewrite is thousands of new lines with no production history, and the subtle vulnerability the old system did not have is now a real possibility. Testing carries more weight than it ever did, since the test suite is the executable half of your spec. And a human still reads the code before it ships. Review does not scale the way generation does, which means review capacity, not generation speed, sets the pace of the cutover.

Then there is production itself. Replacing a live system is where rewrites have always gone to die, and the agent does nothing to soften data migration or customer impact. You still run both systems side by side, still diff their outputs on shadow traffic, still migrate data with a rollback path, still cut over one consumer at a time. The spec helps here too - the functional tests and properties you wrote become the acceptance gate for the cutover - but the risk is real and the caution is earned.

Against all that, weigh the alternative honestly: the permanent cognitive tax of maintaining the legacy system, paid by every engineer, every on-call shift, every quarter, forever. The rewrite’s risks are front-loaded and finite. The debt’s costs are small and infinite. For years the finite number was so large the comparison was academic. It is not academic anymore.

The spec is the asset

Agents did not remove the judgment from rewrites; they removed the excuse. The typing was never the valuable part, and now that it is cheap, what remains is exactly the work teams used to skip: writing down what the system is actually supposed to do, as design, behavior, tests, and properties. Do that, and an agent can squash a decade of accumulated decisions in a quarter. Skip it, and the same agent will hand you your own legacy system back, freshly painted, with the mistakes lovingly preserved.

The old code is one witness to the requirements. It was never the requirements. Write the spec, make it the source of truth, and let the legacy system testify - then retire it.

Work with us

Production AI, with guardrails.

Start with a fixed-scope AI Workflow Audit. We map the opportunity and quote a build.

Start a Discovery Sprint →