I Shipped Automated Code Migration Twice, 24 Years Apart
by DeeDee Walsh, on Jul 25, 2026 8:49:13 PM
Here's What Changed
In 2002, a tool I helped bring to market started rewriting other people's code. The VB6 Upgrade Wizard shipped inside Visual Studio .NET, built on technology from a company in Costa Rica called ArtinSoft. I'd been a product manager on Visual Basic 1.0 a decade earlier. I was on the Microsoft side of the deal that brought that engine into the box.
In 2026 I'm on the other side of the same problem, at the company that ArtinSoft* eventually became, watching LLM agents do the same job.
Almost everything written about AI code modernization right now is written by people encountering the problem for the first time. I'm not trying to be critical but it's just a fact about a young market. But it means the discourse is missing a control group. There was a prior generation of automated migration technology. It shipped, it ran on real production codebases, and it failed in specific, well-documented ways.
Agents are better than rule engines. They're better on the axis that matters most. And some failures of rule engines go away... And some stay but have slightly different ways of failing...
What the Upgrade Wizard couldn't do
The Wizard was a deterministic transformation engine. It parsed a VB6 project, walked the syntax tree, applied authored rewrite rules, and emitted VB.NET. Where it couldn't establish equivalence, it did something that turns out to matter enormously: it left a comment in the source. UPGRADE_WARNING. UPGRADE_ISSUE. UPGRADE_TODO. A machine-generated confession, inline, at the exact line where the machine stopped being sure.
It broke down in four places.
Missing target semantics. Some VB6 constructs have no .NET equivalent, full stop. Control arrays. Default properties. Fixed-length strings. Variant coercion rules that were themselves undocumented folklore. On Error Goto mapped onto structured exception handling only approximately. A rule can translate a construct into another construct. It cannot invent a semantic the target language doesn't have.
The binary boundary. COM interop, third-party OCX controls, Crystal Reports, whichever data access layer the app had accreted. The engine could see the call site and nothing behind it. A compiled control was a closed box, and rules don't speculate about closed boxes.
Intent versus implementation. This was the deepest one. The Wizard preserved implementation with total fidelity, which meant it faithfully reproduced fifteen years of accumulated bad architecture in a new language. Customers got compilable, functional, non-idiomatic output. VB6 written in .NET. The tool had no concept of what the code was for, so it had nothing to re-express.
The last mile. Automation percentages were high, and they were also misleading, because the residual work concentrated in the hardest constructs and consumed most of the project calendar. The honest predictor of effort was never lines converted. It was warnings per thousand lines, weighted by which warnings.
What agents broke through
Two of those four have moved. The intent problem is the big one. An agent can read a 400-line procedure with three levels of nested GoTo and tell you what business rule it implements. It reads the comments, the naming conventions, the dead branches nobody removed, and reconstruct the purpose underneath the implementation. A rule engine operating on a syntax tree can't do this in principle, not just in practice. This is the difference between transliteration and translation, and it's why agentic modernization produces idiomatic target code where the Wizard structurally couldn't.
The missing-semantics problem largely follows from it. Where the Wizard emitted a TODO for a control array, an agent can look at how the array is used and write a collection with a shared handler that does the right thing in context. It's not applying a rule. There is no rule. It's making a judgment.
There's a third gain that gets less attention: target surface. Every rule in the Wizard had to be hand-authored for a specific source-target construct pair. That's why the tooling ecosystem for legacy migration stayed narrow for two decades. The economics of rule authoring only worked for the largest source-target pairs. Agents generalize to targets nobody wrote rules for. That's most of why PowerBuilder, Clarion, and Delphi are commercially addressable in 2026 and weren't in 2010.
Where the wall is the same as where it was
The binary boundary hasn't moved an inch. An agent can't see inside a third-party OCX either. What it can do, and will do, unless you engineer against it, is produce a confident, plausible, syntactically perfect implementation of what it guesses is behind that boundary. The Wizard's failure mode was a warning comment. The agent's failure mode is working-looking code that's wrong. This isn't an improvement. On this specific axis it's a regression, and it's the substance of what we call the hallucination tax.
There is still no correctness oracle. Neither generation of technology can prove behavioral equivalence between source and target. The 2002 answer was to admit it in a comment and hand the problem to a human. The 2026 answer too often is to generate a test suite from the same model that generated the code, which validates the model's understanding rather than the application's behavior. The only oracle I've seen hold up under real load is boring: run both systems against the same inputs and diff the resulting database state.
The last mile is still the last mile. The number moved. We talk about a 70% wall now rather than a 5% residual but the shape of the curve is identical, and the residual concentrates in the same places it did in 2002: interop, reporting, data access idioms, and UI semantics. Anyone quoting you a conversion percentage without telling you where the remainder lives is quoting you the easy part.
The inversion
Here's the finding that surprised me, because it runs opposite to the narrative.
Rule engines scaled linearly and were indifferent to codebase size. A deterministic transformer doesn't care whether your application is 50,000 lines or 5 million. It applies rule 847 to occurrence 12,000 exactly as it applied it to occurrence one.
Agents degrade with context. Long-running modernization work on large codebases hits context rot, coherence decays across a session, decisions made in hour two are forgotten by hour nine, and the eleven-thousandth conversion is not reliably the same as the first. The old technology was worse at meaning and better at scale. The new technology is dramatically better at meaning and structurally worse at scale.
Which reframes what modernization engineering is in 2026. It's way more than prompting. It's architecture designed to hold agents to consistent decisions across a codebase too large to fit in any context window, decomposition into bounded contexts that can be reasoned about independently, specifications as the durable artifact rather than the generated code, documentation structured for machine retrieval rather than human reading.
What the warning comment was really worth
If I could carry one thing forward from 2002, it wouldn't be the rules. It would be the confession.
UPGRADE_WARNING was a machine telling you, at a specific line, that it had reached the edge of what it could establish. It made the residual work visible and localized before anyone spent a dollar on it. You could count the comments and forecast the project.
We gave that up. Modern agentic output is uniformly confident, which means the residual risk is invisible and distributed rather than visible and localized and invisible risk doesn't get budgeted, it gets discovered in UAT.
Getting that honesty back is a real engineering problem, and I'd argue it's the one that separates modernization tooling that works at enterprise scale from modernization tooling that demos well.
Five questions worth asking any vendor in this space
Drawn from watching both generations fail:
- What's your correctness oracle, and does it depend on the same model that wrote the code?
- What happens at the binary boundary: do you flag it, or do you generate through it?
- Where does the unconverted remainder concentrate, by construct type?
- How do you hold decisions consistent across a codebase larger than a context window?
- What's the durable artifact: the code, or the specification the code was generated from?
Want to talk with an expert modernization engineer, reach out and we'll be happy to hook you up.
*Artinsoft morphed to Mobilize.Net and Growth Acceleration Partners (GAP) acquired the application modernization assets of Mobilize.Net in 2023. The GAPVelocity AI crew has been modernizing Microsoft and enterprise code for more than 35 years.


