TAI BUI
Back to curriculum
CapstonePython (agent), Java / Python (targets), TypeScript (dashboard)

Capstone 09 — Code Migration Agent (Repo-Level Language / Runtime Upgrade)

Amazon's MigrationBench (Java 8 to 17) and Google's App Engine Py2-to-Py3 migrator set the 2026 bar. Moderne's OpenRewrite does deterministic AST rewrites at scale, and Grit targets the same problem with a codemod-style DSL. Production combines both: a deterministic substrate for the safe rewrites, an agent layer for the ambiguous cases, a sandbox for per-branch builds, and a test harness that goes green before the PR opens. The capstone is to migrate 50 real repos and publish a pass rate with a failure taxonomy.

TIME: 30 hoursPREREQUISITES: Phase 5 (NLP), Phase 7 (transformers), Phase 11 (LLM engineering), Phase 13 (tools), Phase 14 (agents), Phase 15 (autonomous), Phase 17 (infrastructure)

Learning Objectives

  • Split a repo migration into a deterministic recipe pass (OpenRewrite or libcst) and an agent loop that handles only the failures the recipes leave behind.
  • Enforce the per-repo limits of 30 minutes, $8, and 20 agent turns, and gate every branch on green tests and a coverage drop of no more than 2%.
  • Classify every failed repo into a failure taxonomy class (such as dep_upgrade_required or budget_exhausted) with an exemplar diff attached.
  • Measure pass rate, coverage preservation, and cost per repo across a 50-repo run, and compare the result against a deterministic-only baseline.

Problem

A code migration agent takes a repo on an old language or runtime version (Java 8, Python 2) and produces a branch that builds and passes its tests on the new one. It is one of the cleanest production uses of 2026 coding agents. The ground truth is unambiguous: either the test suite passes after the migration or it doesn't. The payoff is real, since a Java 8 fleet migration is a headcount-scale project. And the benchmarks are public, including the MigrationBench 50-repo subset.

Moderne's OpenRewrite covers the deterministic side. The agent layer picks up what OpenRewrite recipes cannot: ambiguous rewrites, build-system drift, long-tail syntax, and transitive dependency breakage.

You will build an agent that takes a Java 8 repo (or a Python 2 repo) and produces a green-CI migrated branch. You will measure pass rate, test-coverage preservation, and cost per repo, and you will build a failure taxonomy. Then you run the same repos through a deterministic-only baseline. The gap between the two is where the agent's value actually lives.

Warm-up0/4
1. A coding agent edits a repo and then needs a signal that its change worked. Which signal is the most trustworthy?Warm-up
2. Why run an agent that executes builds and edits code inside an isolated sandbox rather than on a shared machine?Warm-up
3. An autonomous agent loop retries until it succeeds, with no other stopping rule. What is the main risk?Warm-up
4. What does a code coverage percentage report for a test suite?Warm-up
Check: Problem0/4
1. Why run a deterministic-only baseline on the same repos as the full pipeline?Explain
2. OpenRewrite alone passes 31 of 50 repos and the full pipeline passes 40. By how much does the agent layer lift the pass rate?Apply
3. OpenRewrite alone passes 44 of 50 repos and the full pipeline passes 45. What does this suggest?Analyze
4. You must plan a migration of a large fleet with little time. Which first step best decides how much agent to build?Design

Concept

The pipeline has two layers. The deterministic substrate is a recipe engine (OpenRewrite for Java, libcst for Python) that applies declarative AST rewrites. It handles the bulk of the mechanical work safely: imports, method signatures, null-safety edits, try-with-resources, deprecated API replacements. It is fast, and its diffs are auditable. The agent layer (OpenAI Agents SDK or LangGraph over Claude Opus 4.7 and GPT-5.4-Codex) takes the cases recipes cannot: build-file upgrades (Maven, Gradle, pyproject), transitive dependency conflicts, test flakes, and custom annotations.

Each repo gets its own Daytona sandbox with the target runtime preinstalled. The agent loops: run the build, classify the failures, apply a fix, rerun. Three hard limits bound every repo: 30 minutes, $8, and 20 agent turns. If all tests pass and coverage has not dropped by more than 2%, the branch opens a PR. Otherwise the repo is filed under a failure class with evidence attached.

The failure taxonomy is the deliverable. Across 50 repos, what broke? Transitive deps, custom annotations, build tool versions, or test flakes unrelated to the migration? Each class gets a count and an exemplar diff, so future recipe authors know which three to target first.

Check: Concept0/11
1. What is the deterministic substrate in a migration pipeline?Define
2. What is a codemod?Define
3. What is build drift?Define
4. What is a failure class?Define
5. What is an agent turn?Define
6. What does the agent layer handle in this pipeline?Define
7. Why do imports, try-with-resources edits, and deprecated API replacements go to recipes instead of the agent?Explain
8. Why does each failure class carry a count as well as an exemplar diff?Explain
9. A Java 8 repo has 400 mechanical import and API changes plus one transitive dependency conflict. How should the layers split the work?Apply
10. Design A has the agent make every edit. Design B runs recipes first and sends only the remaining failures to the agent. Which statement best explains why A is wasteful?Analyze
11. After migration the same pom.xml resolves differently under the newer Maven, and no source or dependency version changed. Which failure class fits?Analyze

Architecture

target repo
      |
      v
OpenRewrite / libcst deterministic recipes
   (safe, fast, auditable, ~70-80% of fixes)
      |
      v
Daytona sandbox per branch
      |
      v
agent loop (Claude Opus 4.7 / GPT-5.4-Codex):
   - run build -> capture failures
   - classify failures (build, test, lint)
   - apply fix (patch or retry recipe)
   - rerun
   - budget: 30 min, $8, 20 turns
      |
      v
test + coverage delta gate
      |
      v (passed)
open PR
      |
      v (failed)
file under failure class + attach repro
Check: Architecture0/2
1. Why does the agent loop rerun the build after every fix?Explain
2. main.py computes recipe rewrites as int((20 + loc / 500) * (1 - 0.2 * hardness)). How many rewrites does a 10,000-line repo with hardness 0.5 get?Apply

Stack

  • Deterministic substrate: OpenRewrite (Java) or libcst (Python)
  • Agent: OpenAI Agents SDK or LangGraph over Claude Opus 4.7 + GPT-5.4-Codex
  • Sandbox: Daytona devcontainers per branch, pre-installed target runtime (Java 17 / Python 3.12)
  • Build systems: Maven, Gradle, uv (Python)
  • Benchmarks: Amazon MigrationBench 50-repo subset (Java 8 to 17), Google App Engine Py2-to-Py3 repos
  • Test harness: parallel runner, coverage via Jacoco (Java) or coverage.py (Python)
  • Observability: Langfuse + trace bundle per repo with every diff chunk
  • Dashboard: failure-taxonomy dashboard with per-class counts and exemplar diffs
Check: Stack0/1
1. What does the MigrationBench 50-repo subset provide in this project?Define

Build It

  1. Recipe pass. Run the OpenRewrite (Java) or libcst (Python) recipes first. They catch the 70-80% of migrations that are mechanical. Commit the result as a "recipe" commit.

  2. Build trial. Start a Daytona sandbox, install the target runtime, and run the build. If it is green, skip ahead to tests. If it is red, hand off to the agent.

  3. Agent loop. Build a LangGraph agent with five tools: run_build, read_file, edit_file, run_test, git_diff. The agent classifies the failure (dep, syntax, test, build-tool), applies a targeted fix, and reruns.

  4. Budget caps. Enforce 30 minutes wall-clock, $8, and 20 agent turns per repo. Any breach halts the run and files the repo under "budget_exhausted" along with the current diff.

  5. Test + coverage gate. Once the build is green, run the test suite and compare coverage to the base repo. If coverage dropped by more than 2%, file the repo under "coverage_regression".

  6. PR open. On success, push the branch and open the PR. The description carries the diff, which recipes applied, and which commits the agent authored.

  7. Failure taxonomy. Tag every failed repo with one class: dep_upgrade_required, build_tool_drift, custom_annotation, test_flake, syntax_edge_case, or budget_exhausted. Then build a dashboard on top of the tags.

  8. 50-repo run. Execute the whole pipeline across the MigrationBench subset. Report per-class pass rate, cost per repo, and coverage preservation, plus a comparison against the deterministic-only baseline.

Check: Build It0/17
1. What is the coverage delta?Define
2. A repo is filed under budget exhaustion. What happened to it?Define
3. Why does the agent classify a failure before it applies a fix?Explain
4. Why does a repo that hits a budget limit still get filed with its current diff?Explain
5. Why does the coverage gate compare the migrated branch to the base repo instead of using a fixed coverage number?Explain
6. Why should test_flake be kept narrow?Explain
7. A migrated branch builds and every test passes, but coverage fell from 84% to 80%. What does the pipeline do?Apply
8. A repo fails because an unrelated integration test is flaky on the base branch too. Which class and evidence fit?Apply
9. In main.py a repo with hardness 0.8 goes straight to pass after the recipes with probability 0.55 * (1 - hardness). What is that probability?Apply
10. In main.py a repo with hardness 1.0 never passes. Each turn adds 4.8 minutes and $1.10, and the limits are 30 minutes, $8, and 20 turns. How many turns run, and what stops it?Apply
11. In main.py the coverage check fails a passing run only when final coverage is below the base minus 2.0. With base 80.0 and a final of 78.5, what is the outcome?Apply
12. When run as shipped, main.py passes 30 of 50 simulated repos. Which failure class has the most repos, and how many?Apply
13. In cost.ts, checkBudget is called on a migration with turns 20, maxTurns 20, spentUsd 9.40 and budgetUsd 8. What does it return?Apply
14. In migrations.ts, advanceFile moves a queued file to rewriting and sets linesAdded to 4 + floor(rng * 24). With rng always returning 0.99, what are the new status and linesAdded?Apply
15. A teammate raises the coverage gate from 2% to 10% and the pass rate climbs. Why is the new number not comparable to the old one?Analyze
16. Most failed repos in a run are filed as budget_exhausted, so the dashboard says little about what broke. What change fixes that?Analyze
17. You must add a Node 18 to Node 22 migration to this system. What do you reuse and what do you swap?Design

Use It

$ migrate legacy-java-service --target java17
[recipe]   27 rewrites applied (JUnit 4->5, HashMap initializer, try-with-resources)
[build]    FAIL: cannot find symbol sun.misc.BASE64Encoder
[agent]    turn 1 classify: removed_jdk_api
[agent]    turn 2 apply: sun.misc.BASE64Encoder -> java.util.Base64
[build]    OK
[tests]    412/412 passing; coverage 84.1% -> 84.3%
[pr]       opened #1841  cost=$3.20  turns=4
Check: Use It0/2
1. After the recipe pass, the build fails with "cannot find symbol sun.misc.BASE64Encoder". What happens next?Apply
2. In the Use It transcript, 412 tests pass and coverage goes from 84.1% to 84.3%. What is the coverage delta, and does the gate pass?Apply

Ship It

outputs/skill-migration-agent.md is the deliverable. Given a repo, it runs the deterministic recipes, then the agent loop, and produces either a green migrated branch or a filing under a taxonomy class.

WeightCriterionHow it is measured
25MigrationBench pass rate50-repo subset pass@1
20Test-coverage preservationMean coverage delta vs base
20Cost per migrated repo$/repo on passing runs
20Agent / deterministic-tool integrationFraction of fixes that OpenRewrite handled vs agent authored
15Failure analysis write-upTaxonomy completeness with exemplars
100
Check: Ship It0/3
1. The integration criterion on the Ship It rubric scores the fraction of fixes OpenRewrite handled versus the agent authored. What behaviour does it reward?Explain
2. You want to stop migrated branches that introduce new style violations. Following Exercise 2, where does the check go?Design
3. A manager must decide whether the agent is worth running on the fleet. Which report from the 50-repo run supports that decision best?Design
Author's Note

My bet is that the baseline run decides how much agent you need. I'd do Exercise 1 first, OpenRewrite only with no agent, before writing the agent loop in step 3. The Problem section says the gap between the two is where the agent's value lives, and I'd rather learn that number up front than after building the agent loop.

I'd also treat the coverage gate in step 5 as the check on the agent itself. It is the one gate that compares the migrated branch to the base repo, and a drop of more than 2% files the repo under "coverage_regression" instead of opening a PR. I'd keep that threshold fixed while tuning everything else, because a pass rate measured against a loosened gate isn't comparable to one measured against the original.

On the taxonomy, I'd keep "test_flake" narrow. It is for flakes unrelated to the migration, and I'd file a repo there only with evidence attached, since the failure write-up is worth 15 points on the rubric.

Exercises

  1. Run the migrate pipeline with OpenRewrite only (no agent). Compare pass rate to the full pipeline. Identify the cases where the agent alone is the difference.

  2. Implement a "lint-clean" check: after migration, run a style linter (spotless for Java, ruff for Python). Fail the PR if new lint errors appear. Measure the coverage-preserved-but-style-regressed rate.

  3. Add a "minimal-diff" optimizer: after the agent's branch passes tests, trim unnecessary changes with a second pass. Report diff-size reduction.

  4. Extend to a third migration: Node 18 to Node 22. Reuse the sandbox wrapping; swap the recipe layer for a custom codemod.

  5. Measure time-to-first-green-build (TTFGB) as a UX metric. Target: p50 under 10 minutes.

Key Terms

TermWhat people sayWhat it actually means
Deterministic substrate"Recipe engine"OpenRewrite / libcst: declarative AST rewrites with safety guarantees
Codemod"Code-modifying program"A rewrite rule that changes source code mechanically
Build drift"Tool version skew"Subtle Maven / Gradle / uv behavior changes between major versions
Failure class"Taxonomy bucket"A labeled reason a repo did not migrate: dep, syntax, test, build-tool, budget
Coverage delta"Coverage preservation"Change in test coverage % from base to migrated branch
Agent turn"Tool-call round"One plan -> act -> observe cycle in the agent loop
Budget exhaustion"Hit the ceiling"The repo consumed its 30-min / $8 / 20-turn limit without passing

Further Reading

Code

Ships With