Deterministic Tools Unlock Agentic Migrations
One of the areas where agents hold most promise is migration work, like upgrading to the latest version of a key dependency, or reworking the implementation of a key subsystem. This work has always been challenging to prioritise. It’s risky, hard and often very boring. The value is hard to estimate, and doesn’t accrue until some unknown future date. Teams kick the can down the road. This compounds: the older the dependency, the more changes to APIs, to patterns, the wider the gap and the more challenging the migration. Eventually it becomes impossible to avoid, perhaps when a long-term support version expires, and security patches are no longer forthcoming.
Agents drop the cost of a migration dramatically. Not because they are smart, but because the outcome is easy to encode and verify, allowing them to work for longer periods without supervision. Essentially no external properties of the system should change. It should behave the same way, in happy and error paths. It should perform similarly. Not a pixel should move on the home page. A system should already encode these properties as tests. If not, agents can help with that too.
Agents can get lost when we try to steer them with huge context. We give them conflicting directions which can’t be reconciled. They struggle with the lost-in-the-middle problem, forgetting some instructions as the context grows. Encoding success criteria as deterministic tools gives agents a value function they can’t ignore. The tool can be a simple linter: there are no uses of the old library or framework, there are no lines of code which use outdated patterns.
I tasked Claude with migrating Groove, my web DAW side project, to Solid 2, and it immediately built such a tool. It’s basically just grep, but it allows Claude to objectively measure the progress of the migration, and it prevents future engineers, agent or human, from accidentally introducing regressions. The migration made 2,500 changes across 91 files, ran overnight unsupervised and the app still worked correctly the next day. This was a simple migration, with clear rules - kudos to Ryan and the Solid team for creating a simple migration path and a tight guide. A real migration would throw up many more thorny challenges, but the canonical report on this technique, Bun’s migration from Zig to Rust, shows what can be done.
More generally, tools can help agents assess their output objectively. Other tools might check code is free of secrets, that bundle sizes are reasonable, that the code doesn’t contain classic footguns (does this useEffect have the right dependency array?) or that tests didn’t change during implementation. Tools could also use smaller models to assert more complex properties, for example, that comments are up-to-date. Uncle Bob and Matt Pocock explore using tools to guide agents towards quality code in a great interview.
You may think I’m oversimplifying the scope of a migration. Real-world migrations often introduce new functionality, new failure modes, and many small changes, both intentional and unintentional. The cause of this scope creep is that migrations take months, and other work gets pulled into the gravity well of the project. An engineer spots a bug, and fixes it while they’re in the file, etc. A pure migration shouldn’t fix issues, it should open the door to fix those issues more easily later. If the migration is fast and cheap, we can implement that pure slice, then move on to the valuable work.