I estimated a legacy migration at 100 man-days.
We completed it in a few days.
That does not mean AI made the migration cheap because it wrote code faster.
The difficult part was not typing a new implementation. The difficult part was finding out what the old one actually did.
The application had been running for more than 20 years, serving thousands of users across hundreds of locations. It worked, which was why nobody had a compelling reason to touch it until the old architecture began blocking the changes we needed to make.
A system like that contains more than code. It contains the contract the business has accumulated over years.
The Real Contract Is Distributed
Part of the contract is visible in source files, schemas, and interfaces.
Part of it is not.
It sits in business rules that were never written down because people learned them while doing the work. It sits in the order a downstream system expects fields to arrive. It sits in a file format that looks odd until a particular integration explains it. It sits in exception paths that only appear at the end of a month, for one customer, or after a partial failure.
A legacy migration is therefore not simply a translation from one programming language or framework to another.
It is a process of deciding which observed behaviours are business requirements, which are compatibility requirements, and which are historical accidents that should not survive the move.
That is an evidence problem before it is a coding problem.
The Running System Can Be an Oracle
We did not begin by asking an agent to rewrite the application.
We began by asking it to understand the system.
It explored the codebase, extracted candidate business rules and undocumented assumptions, mapped dependencies, and helped phase the migration. Those findings gave us a useful map. They were not enough on their own to define the replacement.
The old system was still running.
Whenever a behaviour was unclear, we could reproduce a scenario, observe the result, and turn that observation into an executable specification. The legacy system became our oracle.
An oracle does not mean the old implementation is beautiful, secure, or worth preserving unchanged. It means that, for a defined scenario, it provides evidence of the behaviour a replacement must either reproduce or deliberately change.
That changes the migration conversation.
Instead of asking a developer to guess what a module was supposed to do, the team can ask a more precise question: what does the system do when it receives this input, in this state, with these surrounding dependencies?
known scenario
→ observed behaviour
→ executable test
→ new implementation
→ matching result or an explicit approved difference
The test does not settle every business question. It does give the team a durable object to inspect, rerun, and discuss. An undocumented behaviour becomes evidence rather than a memory held by one person.
Byte Compatibility Is a Business Fact When an Interface Requires It
One component used a binary serialization protocol.
At first glance, the requirement looked simple: produce an equivalent business object. In practice, functional similarity was not enough. The new implementation had to generate byte-compatible output for the interface around it.
That is the sort of detail that makes a migration appear successful in a review and fail when it meets the systems that depend on it.
The contract can be a binary layout, field order, date convention, rounding rule, identifier form, null handling, retry timing, file name, or error behaviour. It may look like technical trivia until an external process stops working.
The harness generated around 500 tests across variants, combinations, edge cases, and exceptions. The count is not the important measure. The useful result was that the relevant behaviours had somewhere explicit to live.
AI helped create and extend those tests quickly. Deterministic test execution decided whether the new implementation was acceptable.
Where the team wanted to change a behaviour, the difference could be named, reviewed, and accepted. It did not have to arrive as a surprise after cutover.
Discovery Can Surface Improvement Work as Well as Compatibility Work
Once the system had a disciplined discovery and test loop, it revealed more than a migration sequence.
During end-to-end testing, the agent noticed that one page was abnormally slow. The same problem could be reproduced in the legacy application during cold starts. It traced the issue to an O(n^2) algorithm, proposed an O(n) implementation, wrote it, and tested it against the observed behaviour.
The important part was not that the agent found an optimisation.
It was that the performance problem could be reproduced and investigated within the context of a system whose behaviour was being made explicit. A local improvement had a path to evidence rather than being an unreviewed suggestion hidden in a large rewrite.
The same was true for deployment preparation. The agent suggested replacing part of the IP-addressing scheme with DNS aliases. I was skeptical. A cleaner diagram is not a sufficient reason to alter a working deployment boundary.
So we built an emulated environment and tested whether legacy and migrated components could coexist. The result supported a simpler rollout path with minimal interruption.
The proposal became useful only after it was tested against the actual transition problem.
Modernization Also Identifies What Must Not Be Preserved
The review surfaced security weaknesses that might once have seemed normal, including predictable identifiers exposed in URLs.
That observation did not automatically prescribe a particular fix. It did make one thing clear: a running system can be an oracle without being an ideal.
A migration team needs a deliberate way to classify observed behaviour.
Preserve Required business or compatibility behaviour
Change Behaviour that is valid but needs an approved new contract
Retire Historical coupling, workaround, or weakness that should end
Investigate Behaviour whose owner, purpose, or consequence is still unclear
The categories prevent two opposite errors.
One is treating every old behaviour as sacred because it exists in production.
The other is treating every awkward old behaviour as disposable because a new implementation looks cleaner.
Neither is an architecture decision. Both are shortcuts around one.
The evidence tells the team what is happening. An accountable owner decides what the future contract should be.
The Architecture Decision Still Has an Owner
In this migration, AI investigated, proposed, coded, and tested. I retained responsibility for the architecture: what to inspect, which assumptions to challenge, which alternatives to consider, what evidence was sufficient, and when a change was ready to move toward production.
That is not a ceremonial role.
A model can help reveal the consequences of a byte-compatibility requirement. It has no delegated authority to decide whether a commercial interface should remain byte-compatible, be versioned, or be retired. It can show a coexistence option. It cannot accept the customer, operational, or recovery risk of using it.
Those decisions belong to the people who own the system and its consequences.
The point is not to place humans in the loop as a ritual. The point is to make sure the person deciding a contract change can see the evidence, the alternatives, and the operational consequence before the change becomes production reality.
Start With an Evidence Backlog
A rewrite backlog asks which modules to replace.
An evidence backlog asks what the business cannot afford to misunderstand.
Start with scenarios where a wrong compatibility assumption would stop work, damage a customer relationship, produce a financial error, create a security exposure, or make recovery difficult. Capture the inputs, outputs, interfaces, data changes, exceptions, and the person who can say whether the observed result is still intended.
Then make the stable requirements executable.
AI is useful for exploring unfamiliar code, finding cross-references, extracting candidate rules, generating test cases, reading logs, identifying suspicious complexity, and explaining differences between old and new behaviour.
The observed controls in this work were the executable test suite, byte-level output comparison where the interface required it, and the emulated coexistence test before the transition path was accepted.
discover the behaviour
→ make the important behaviour testable
→ replace one bounded component
→ compare outcomes
→ test coexistence where the transition requires it
→ move the boundary deliberately
The breakthrough was not simply that most of the new code could be generated quickly.
It was that the work of understanding, testing, validating, and de-risking a complex system became fast enough to make a migration that once looked like 100 man-days a much smaller, more testable piece of work.
Risk did not disappear.
We could see it earlier, turn more of it into evidence, and decide what to do with it before it reached production.