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
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.
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.



