cd /news/ai-agents/dont-ask-agents-to-migrate · home › topics › ai-agents › article
[ARTICLE · art-157893] src=blog.stackademic.com ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

Don’t ask agents to migrate!

A team migrating hundreds of forms off a homegrown form hook found that asking an AI agent to convert files one by one produced inconsistent results, so it instead had the agent write deterministic migration scripts and a shared test harness. The harness recorded legacy behavior against fixed data with frozen time and no real network, making "don't change behavior" a diff against a recording rather than a judgment call, and each form's coverage cost a fixture rather than a new test. The team then inventoried form variants, reshaped the legacy code with small syntax-tree scripts until it fit one uniform shape, and ran an all-or-nothing converter that refused anything it didn't understand, keeping a handful of bespoke forms for human handling.

by read5 min views1 publishedOct 26, 2026

If your first instinct is to write a beautiful plan and then ask an agent to do the whole thing all at once, buddy, you are volunteering for pain you do not need. We had hundreds of forms on a homegrown form hook. This reusable form we maintained has its own dirty-state tracking, imperative validators, submit handler, and all the bells and whistles. The rest of the app had already moved to a standard form library with schema-based validation. We wanted off the old hook for whatever reasons.

The obvious way to do that with an agent is you write a careful plan, then say “go convert this file, then this one, and don’t change behavior.” That works for three files. At hundreds, it falls apart. Not because the agent is dumb. It’s because that job is “stare at a form, invent a translation, hope the agent does the same tomorrow.” Agents are fine at that kind of reasoning, but not just good at repeating it. Same plan, next batch, different result. You spend the rest of the month arguing about whether it “followed the plan.”

The actual trick is to stop treating the agent as the migrator. Make it write the migrator and do the migration one step at a time instead of one sweep.

First create a harness. Then worry about the variants.

We started with tests, before we even thought about what the conversions would look like. An important thing to note is: don’t make the mistake of asking “agent, write tests for these forms” because that just moves the repeatability problem into the test suite. Instead, build a harness which is one piece of machinery that can put any form through the same interrogation.

Building the harness forced the design conversation you’d otherwise skip. What is the behavior of a form, concretely enough to assert? We landed on a few questions the harness asks every form:

We had two rules that made the harness trustworthy. The answers were recorded against the legacy code, not written by hand, so “don’t change behavior” stopped being a vibe and became a diff against a recording. And every run had to be deterministic with fixed data, frozen time, no real network. At the end we had one harness, one recording per form, and adding the next form costs a fixture, not a test.

With that in place, we could move to the actual problem. The old forms were not one shape. Some declared validators. Some didn’t. Some customized how server errors displayed and so on. If you ask an agent to “migrate the forms,” you’re asking it to solve numerous problems at once, differently, hundreds of times.

So we didn’t. We listed the variants first and created an inventory, then transformed the legacy code until they were boring enough to fit into one uniform shape that the next step could consume.

Then a rough conversion.

Even the “rough conversion” wasn’t one step. It was a chain of small scripted reshapings, each one preparing the ground the next could assume. One pass exposed components so the harness could reach them. One tagged them with stable handles the tests could find. One flattened the clever indirections into plain code a converter could read. Every reshaping was a script over the syntax tree, small enough to review as a diff, deterministic enough to rerun on the next batch. The legacy code walked to the starting line one boring step at a time.

Then the converter itself: all-or-nothing per file, and it refused anything it didn’t understand. Refusals were expected. A handful of genuinely bespoke forms we kept for ourselves. Human hands touched code in exactly one place: review. Anything that changed text at scale was a script.

The converted forms were not production-ready. They weren’t supposed to be. The old logic ran behind an adapter. Submits still went through a thin legacy client. It was a byte-adjacent conversion but the same behavior, still wearing the old clothes. The parity recordings matched. That was the whole bar for that step.

Production-ready came after, in smaller moves you can actually check like real schemas instead of wrapped validators. The loose types tightened until the compiler listed the real fallout, then the shim unwrapped and dropped old helpers. Each a step with a name, a test, a refusal list. One sweep is “make it good.” Five steps is “this step did one thing, and we can tell what happened.”

Did behavior stay identical all the way through? No, and we stopped pretending. Errors re-validate as you type. A couple of messages got honest. Those were listed and done in a later sweep and not mixed into file 37 by an agent that “thought it was fine.”

To our advantage, we also had end-to-end tests. This end-to-end suite caught regressions the harness was blind to. The harness filled every field, so it couldn’t see a save that branched on which fields the user had touched. A harness has a shape. You know what it can’t see. An agent doing it by hand has a shape too. You just find out later.

Why this is faster, even though it looks slower.

Writing a harness, flattening variants, shipping a sloppy conversion, then iterating does not feel like shipping. Asking the agent to convert the next form feels like you made progress. Then you do it a hundred times, each one a little different, and you can’t tell if the difference is load-bearing.

The toolchain way front-loads one design conversation: what does a converted file look like, how would you know you didn’t break one, what can you script so you never care which file it is. After that, “the next wave” is run the scripts, read the refusals, and take the next small step. Same input, same output. That’s what deterministic actually means, and not because “the agent promised.”

So if you’re about to do a large migration with an agent, don’t paste files into the chat and ask for a production-ready conversion of the whole set. Get yourself a harness and migrator.

Don’t ask agents to migrate! was originally published in Stackademic on Medium, where people are continuing the conversation by highlighting and responding to this story.

── more in #ai-agents 4 stories · sorted by recency
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/dont-ask-agents-to-m…] indexed:0 read:5min 2026-10-26 · —