Using AI Agents on Legacy Code: Where It Helps and Where It Lies
By Maplecode
Legacy modernization has always been rate-limited by comprehension. Before you can safely change a twenty-year-old system, somebody has to understand what it does — and the people who knew have generally left.
Agents that read code are unusually well suited to that bottleneck. They are also unusually good at producing confident, plausible, wrong explanations. Both things are true, and the useful question is which tasks sit on which side of the line.
What they are genuinely good at
Comprehension work at a scale humans find unbearable:
- Tracing a value through a codebase. Where does this field get written, and what reads it? Mechanical, tedious, and exactly the kind of search that takes an engineer two days and an agent twenty minutes.
- Summarising a module's apparent responsibilities as a starting point for a human review. Not authoritative, but far better than a blank page.
- Finding structural duplication — the same logic implemented four times slightly differently, which is the standard shape of an accreted system.
- Drafting characterisation tests that pin current behaviour before you touch it. Here the agent's inability to judge correctness does not matter, because capturing what the code does is the entire goal.
What they cannot do, structurally
Distinguish a bug from a requirement.
Legacy systems are full of behaviour that looks wrong and is load-bearing. A discount applied in the wrong order because a major customer's contract was written that way in 2009. A retry that exists because a downstream system fails silently on Tuesdays. A field that must stay populated because a regulator's export reads it.
None of that is in the code. It is in decisions made by people, under pressure, recorded nowhere. An agent reading the source sees an anomaly and will helpfully offer to clean it up. That is the single most expensive thing it can do.
The rule we work to: agents may propose changes, never merge them, and any proposal that removes or normalises unusual behaviour needs a human who can say why the behaviour existed — or an explicit decision to find out the hard way, taken with eyes open.
Confident wrongness is the operational risk
A junior engineer who does not understand a module says so. An agent produces an explanation in the same tone whether it has understood the code or pattern-matched something that resembles it.
On legacy systems this is worse than usual, because the code often does not follow the conventions the model has seen most. Unusual idioms, dead code that is not dead, framework versions with different semantics — all produce plausible summaries that are wrong in ways only someone with system context will catch.
Treat every explanation as a hypothesis with a cheap test attached. Can you confirm it by running something, reading a log, or checking a database? If not, it is a lead, not a finding.
The migration this actually enables
Used well, agents change the economics of the incremental approach rather than enabling the big rewrite. Comprehension gets cheap enough that carving a system into slices — the thing that always made incremental migration correct but slow to start — becomes tractable in weeks instead of quarters.
That is the win, and it is a large one. It is not that the rewrite is now safe. The rewrite is unsafe for reasons agents do not touch: it defers all risk to one moment, and it depends on knowledge that was never written down.
Where we would advise against using them
Do not point an agent at a system where you cannot run the tests. The whole approach depends on cheap verification; without it you have accelerated the generation of unverified change, which is the opposite of what a fragile system needs.
Do not use them on code you cannot legally send to a third-party model — and check, because this is frequently the case for systems holding regulated data. Self-hosted options exist; assuming rather than confirming is how a modernization programme becomes a disclosure incident.
And do not use agent output as the basis for an estimate. Reading code tells you what is there, not what it will cost to change it safely, which is dominated by testing, coordination and the behaviour nobody documented.
Large codebases exceed the window, and summarising loses the thing you needed
A legacy monolith does not fit in a context window. The usual answer is to summarise, retrieve the relevant parts and reason over those — which works, with a specific weakness worth naming.
The behaviour that matters in old systems is often non-local. A payment path touches a helper written in a different decade, whose behaviour depends on a config flag set in a third place. Retrieval finds the code that looks relevant; the load-bearing oddity is somewhere that does not look relevant at all.
This makes agent output systematically more reliable on self-contained modules than on the tangled integration code where the real risk sits — the opposite of what you want. Weight your confidence accordingly, and reserve human review for the paths that cross boundaries.
There is a cost dimension too. Reading a large estate repeatedly is not cheap, and teams often discover the bill after pointing an agent at everything. Scope the reading to the slice being migrated rather than the whole system.
A workable pattern
Agents map and characterise. Humans decide and approve. Every claim about behaviour gets a test that would fail if the claim were wrong. Migration proceeds in reversible slices, each one small enough that a mistaken assumption costs a sprint rather than a programme.
We wrote about the shape of that migration in legacy migration without a big bang; the practice sits in cloud modernization.