Agent-Driven Codebase Migration From REST to GraphQL

Agents prove behavior compatibility before building the schema.

Cover illustration for “Agent-Driven Codebase Migration From REST to GraphQL”

Sponsored by UFO

Written by
Working Agents Editors
Published
October 29, 2026
Reading time
8 min read

An agent-driven REST-to-GraphQL migration succeeds when the agent can prove its changes preserve behavior. The strongest workflow builds that proof before the schema, then uses repeated test-and-repair cycles to reach parity. Load testing and field-level authorization checks complete the job, turning an automated rewrite into a migration with clear exit conditions.

Why REST-to-GraphQL migration keeps stalling without an agent

Schema design is not usually the hardest obstacle. Proving that subscription.status behaves identically for every legacy plan tier and every caller is harder, because a working resolver can still return subtly different data. Those differences are what existing clients encounter. Manual refactoring stalls when checking every change takes more work than the team can keep up with in its verification loop.

The 40-endpoint migration described in the brief exposed that problem directly. Its blocker was proving that the new API returned the same data. Production behavior also contradicted the declared contract. The 2026 project analysis found that a field documented as non-nullable was null in 11 percent of real responses. A schema derived from types alone would have turned that discrepancy into a breaking change.

Traffic also changed what needed to be migrated. Three endpoints were deleted instead of rebuilt after the audit showed that no one used them. That discovery came before any migration code was written. Both findings point to the same weakness in a design-first approach, which treats the documented API as the real one before gathering enough evidence to know.

Why agents are structurally suited to the migration's hardest sub-tasks

The migration’s hardest tasks have a shared structure. Schema inference, differential testing, resolver generation, and N+1 detection each involve gathering evidence, making a change, executing something, and inspecting the result. Agents fit this work because they can keep that loop running until an external check confirms that the generated code meets the migration’s requirements.

Production responses reveal where types misstate the contract, while differential tests expose where a resolver returns the wrong value. Existing service calls constrain how the agent implements a correction. Query traces then reveal inefficient fetching that correct output alone cannot expose.

One-pass translation offers a useful contrast to this approach. Repository-level translation research found that GPT-4 resolved only a small fraction of full-project translations in one pass, while every other tested model scored zero. That finding supports iteration. The practical workflow therefore begins with evidence and a scoring mechanism, rather than an instruction to translate the repository.

Phase one: building the inventory before touching the schema

The agent’s first assignment should be an audit. It should produce a machine-readable inventory of what the API actually does in production, using route definitions, available TypeScript or equivalent response types, and production access logs aggregated by endpoint and query-parameter signature. Routes establish what the code exposes. Types describe its intended responses, while traffic and sampled responses reveal how that contract is used and where it diverges from reality.

One JSON file per endpoint makes those findings usable in later phases. The route identifies the implementation being replaced, and call volume helps establish whether it belongs in scope. Distinct parameter signatures show which variations of a request the tests need to cover. Response fields describe the data that comes back, and nullable-in-practice fields show where the declared types may be wrong. A caller list identifies integration dependencies that a route definition cannot show.

The traffic evidence makes scope much more precise. In the 40-endpoint case, the inventory separated 31 active endpoints, 6 single-integration endpoints, and 3 deletable endpoints. An endpoint that one integration uses still has a caller, even if it gets as little space in the documentation as an unused endpoint. This phase gives the agent per-endpoint contracts and a numbered workload, rather than an open-ended request to modernize an API.

Phase two: the differential test harness as the migration's only source of truth

The harness must be built before the schema. It replays recorded request signatures against the old REST endpoint and the new GraphQL server, brings both responses into a canonical form, and deep-diffs them. At the start, zero passing tests is an honest baseline. There is no replacement implementation yet, so the failures establish the work still required.

Canonicalization separates meaningful differences from incidental ones. Before responses can be compared, dates need to use the same format, object keys need to be sorted, and server-generated request IDs need to be removed. Without those changes, the agent spends its time chasing noise. The target is identical data after those agreed transformations.

Production requests provide the harness with its reach. The migration used the top 500 recorded request signatures and weighted them by actual call volume. The inventory supplies those signatures and connects failures to known callers and response contracts. This makes the harness infrastructure for autonomous work, much like compiler errors, used throughout the migration. Its command belongs in the agent’s instruction file so a later session can score its work without fresh directions.

The agent must not control the scoring rules. On day three of the migration, it proposed adding a nullable field to the canonicalizer’s ignore list, which would have hidden a real discrepancy. The guardrail therefore requires resolver fixes while keeping the harness’s scoring rules unchanged. Human approval is no longer needed for every repair because the agent has an independent check, but that independence disappears if it can weaken the check.

Phase three: schema design constrained by the inventory, not by preference

The inventory turns schema design into a constrained derivation. Field names such as createdAt and created_at may describe the same underlying data, but choosing between them is a compatibility decision. It cannot remain an unnoticed resolver detail. The agent should surface such inconsistencies during schema design, default to compatibility, and record proposed normalization as a deliberate deviation requiring human approval.

Bug-for-bug compatibility is legitimate when live clients depend on a particular field shape, even if a cleaner schema looks attractive. The same restraint applies below the schema. Resolvers should call the existing service layer, reusing its business rules and authorization checks. Keeping the GraphQL layer over that service layer makes the harness’s verification task tractable.

Phase four: running the agent iteration loop from first diff to full parity

Diagram: From Zero Tests to Full Parity: The Four-Phase Migration Loop. Visualizes: Show the four sequential phases of the agent-driven migration as a stepped flow: Phase 1 'Inventory' (audit routes, types, traffic → 31 active, 6 single-caller, 3…

The agent runs the harness, reads every failing diff, traces each discrepancy to its resolver, changes the resolver, and runs the harness again. A failure is a precise specification. It identifies the field, its path, the parameter signature, and the value that differs, giving the agent a narrower repair target than a general request to make an endpoint compatible.

The instruction file should record completed endpoints, work in progress, and architectural decisions already settled, as well as the harness command. That is the minimum viable memory for the migration. Without it, a new session may reopen a naming decision or revisit completed work, delaying progress on the remaining failures.

Parallel work can shorten the process, but does not replace the loop. Separate agents can migrate distinct endpoint areas in isolated git worktrees, with the same verification rules governing their changes. Google’s Antigravity IDE supports isolated worktrees for concurrent agent sessions. The exit condition for this phase is full differential parity across the established coverage.

The N+1 problem as the migration's hidden landmine

A GraphQL server can pass every differential comparison and still be far slower than the REST endpoints it replaces. The harness checks the data that comes back from a single request, leaving database behavior under load unchecked. Performance therefore needs a separate agent-driven verification phase after functional parity, rather than an assumption that passing output tests also proves the implementation scales.

The landmine follows from how field resolvers execute. A query for a list of orders and their nested user and subscription data can trigger database lookups for each record. The output remains correct throughout this multiplication of work.

DataLoader supplies the standard batching and caching mechanism. The agent should send related-entity fetches through it before load testing, then test each migrated endpoint and read query traces to see whether the number of queries grows with each record. Where a resolver repeatedly fetches an entity by foreign key, batching keyed on that foreign key provides a consistent repair pattern. The agent can apply it, rerun the load test, and inspect the trace again. Here, query behavior supplies the feedback that response diffs could not.

Security and authorization expand at the field level in GraphQL

There is more security work to do after reusing service-layer authorization. REST authorization typically guards a route, whereas GraphQL exposes fields that can be selected and combined through different queries. The migration adds more places where access must be explicitly allowed or denied. A field without an applicable authorization check risks becoming available to an authenticated caller simply because it is reachable in the graph.

The agent should audit that surface as a third verification phase. It should flag resolvers that have no explicit authorization checks and add a coverage report alongside the differential score and load-test results. This report answers a different question from the harness. Returning the correct value does not prove that the caller should have received it, so behavioral parity cannot stand in for authorization coverage.

Agents consuming the migrated API should be treated as untrusted clients by default. Named operations for writes constrain ad-hoc mutation construction, while read cost budgets constrain expensive queries and field-level scopes limit access to the actual task. Schema discovery needs a boundary as well. Production introspection should be disabled or restricted for untrusted clients, and the migration agent should apply that configuration by default. The schema’s usefulness to machines is not a reason to expose it without controls.

What verified parity looks like and what changes after it

Diagram: Three Exit Conditions Before Migration Is Complete. Visualizes: Show three parallel verification gates that must all pass before the migration is declared done: (1) Differential Parity — 500 request signatures, 0 → fully passing; (2) N+1…

Completion requires three separate forms of evidence. The migrated API must reach differential parity and pass both the N+1 load tests and the field-level authorization audit. In the 40-endpoint migration, round trips per screen dropped from 6 to 1, p95 screen load fell substantially, and the harness moved from zero passing to fully passing. The implementation used Claude Code CLI with Opus 5, Apollo Server 5.x, and DataLoader 2.x.

The resulting API also changes what authorized agents can discover. Where introspection is permitted, an agent can inspect types, fields, arguments, and nested relationships without reading separate documentation, then use that structure for runtime discovery and self-correction. Hygraph identifies structure, discoverability, and predictability as properties that support autonomous LLM operation. Requesting only needed fields can cut response tokens by 40 to 70 percent on complex queries compared with REST, reducing the data entering the context window.

MCP and GraphQL serve different layers of that arrangement. GraphQL describes the system’s capabilities, while MCP exposes those capabilities to models, making the typed schema a useful foundation for an MCP server’s self-description. Persistent state, cross-session memory, and scheduled execution help run both the migration loop and the agents that later use the API. The lasting gain is therefore not just a different interface, but a structured contract that agents can use within tested behavioral and security boundaries.

This article is sponsored by UFO.

Every new piece from Working Agents, as it's published.

Follow