Brex Migrates 180 Microservices in a Month with Self-Hosted Cursor Agents: 32x, ~90% Cheaper

Brex replaced a manual Micronaut 3-to-4 migration with a dual-agent pipeline of self-hosted Cursor agents: a PR Generation Agent triggered by Linear tickets, plus a Validation Agent that monitors CI and self-corrects up to three attempts. One engineer migrated 180 microservices in a month — 32x per-capita acceleration, cost per migration down ~90%. Company-reported; not independently re-tested. Task: Blog Brex Eng Blog Agent automation... Brex Eng Blog Agent automations for big migrations Sofhia de Souza Gonçalves Jul 22, 2026 Jul 22, 2026 Large-scale framework migrations are notoriously painful. While essential for security, performance, and keeping a tech stack current, they frequently drain developer morale, derail product roadmaps, and stall innovation due to the amount of time and effort required to troubleshoot every compilation and runtime errors that surfaces. Our core Kotlin framework at Brex is Micronaut and we use it across over 500 microservices. Last year, we tried migrating just 50 of these services from Micronaut 3 to 4. It took us 3 engineers working full time for an entire quarter and was an expensive, manual grind that forced us to pause other critical product initiatives. A year later, we took a completely different approach. We built a pipeline of custom self-hosted AI agents in Cursor that are capable of writing, building, and dynamically self-correcting code. This allowed a single engineer to migrate 180 microservices in just one month. This represents a 32x per capita acceleration in delivery speed. Below is how we arrived at this highly efficient system, along with the missteps we made along the way that are worth understanding. The first attempt: A manual grind When we first tackled this migration, it was in the middle of 2025, before the frontier models we have today. We evaluated OpenRewrite OSS, a tool for deterministic, recipe-based refactoring in the JVM ecosystem, but its Bazel support was limited and wouldn't integrate well with the custom Bazel tooling our monorepo relies on. We then built a straightforward script to automate predictable, repetitive tasks, primarily updating version flags in build targets and replacing import statements. Once the script ran and generated a Pull Request, the rest of the lifecycle fell entirely on the engineer: Inspecting CI failures and manually writing fixes. Deploying the services to our development environment to catch runtime exceptions. Executing integration tests against the new version and resolving compatibility issues. Every time we encountered a new error pattern, we documented it in a shared migration guide to streamline subsequent fixes. This migration approach was painful, slow, and manual for a few reasons. The Micronaut framework sits underneath nearly every line of code in a service, wiring components together and pulling in dozens of supporting libraries (for JSON parsing, GraphQL, caching, and more) that all need to upgrade in lockstep. Many of the resulting failures don't even surface at build time, instead appearing only once the service is running, like a component failing to wire on startup, a test silently no longer testing what it claims to, or a date being serialized with slightly different precision. Each of the 500+ microservices had to be individually rebuilt, redeployed, and revalidated against the new runtime, which is what made the original effort so expensive. Many of the breaking changes required real code rewrites whose correct form depended on context. For example, the GraphQL library upgrade made many return types nullable, so call sites that used to be straightforward: Now had to handle a nullable value. If the field was a runtime invariant, the fix was an explicit assertion: If it was genuinely optional, the call site had to branch instead: Each call site needed to be evaluated against the schema and the surrounding code, which is the kind of judgment a deterministic recipe can't make. Recognizing the mounting friction, we paused the migration after one quarter. With three full-time engineers dedicated to the effort, we had managed to migrate only around 50 microservices. The second attempt: What failed before it worked Nearly a year later, the migration had become critical. Postponing it further was beginning to block vital downstream dependency upgrades. However, LLM capabilities had advanced significantly in that short window, giving us renewed confidence that we could successfully automate the workflow. Initially, we attempted to build a single, massive AI agent to handle the entire lifecycle: making code changes, running validations, and fixing errors. The underlying prompt was an exhaustive wall of text, stuffed with every edge case we had documented. This approach failed for two primary reasons:; tools: Cursor. Human role: - Setup: engineers trigger the pipeline by creating Linear tickets. Review: every draft PR is human-reviewed; production merges always require human approval. - Finer division of labor: Not disclosed by the source.; limitations: - Not fully autonomous: human review is mandatory before merging to production. - A strict three-attempt cap prevents infinite loops and runaway costs. - Specific to Micronaut 4 + custom Bazel tooling; heavy upfront setup of self-hosted infra and 11 skills. - Coverage gaps let subtle runtime bugs slip into production. - Token-use details beyond the ~90% cost claim: not disclosed..

Tools

Cursor

Published on Agent Work Atlas:

Overview

Brex replaced a manual Micronaut 3-to-4 migration with a dual-agent pipeline of self-hosted Cursor agents: a PR Generation Agent triggered by Linear tickets, plus a Validation Agent that monitors CI and self-corrects up to three attempts. One engineer migrated 180 microservices in a month — 32x per-capita acceleration, cost per migration down ~90%. Company-reported; not independently re-tested.

Workflow

Blog Brex Eng Blog Agent automation... Brex Eng Blog Agent automations for big migrations Sofhia de Souza Gonçalves Jul 22, 2026 Jul 22, 2026 Large-scale framework migrations are notoriously painful. While essential for security, performance, and keeping a tech stack current, they frequently drain developer morale, derail product roadmaps, and stall innovation due to the amount of time and effort required to troubleshoot every compilation and runtime errors that surfaces. Our core Kotlin framework at Brex is Micronaut and we use it across over 500 microservices. Last year, we tried migrating just 50 of these services from Micronaut 3 to 4. It took us 3 engineers working full time for an entire quarter and was an expensive, manual grind that forced us to pause other critical product initiatives. A year later, we took a completely different approach. We built a pipeline of custom self-hosted AI agents in Cursor that are capable of writing, building, and dynamically self-correcting code. This allowed a single engineer to migrate 180 microservices in just one month. This represents a 32x per capita acceleration in delivery speed. Below is how we arrived at this highly efficient system, along with the missteps we made along the way that are worth understanding. The first attempt: A manual grind When we first tackled this migration, it was in the middle of 2025, before the frontier models we have today. We evaluated OpenRewrite OSS, a tool for deterministic, recipe-based refactoring in the JVM ecosystem, but its Bazel support was limited and wouldn't integrate well with the custom Bazel tooling our monorepo relies on. We then built a straightforward script to automate predictable, repetitive tasks, primarily updating version flags in build targets and replacing import statements. Once the script ran and generated a Pull Request, the rest of the lifecycle fell entirely on the engineer: Inspecting CI failures and manually writing fixes. Deploying the services to our development environment to catch runtime exceptions. Executing integration tests against the new version and resolving compatibility issues. Every time we encountered a new error pattern, we documented it in a shared migration guide to streamline subsequent fixes. This migration approach was painful, slow, and manual for a few reasons. The Micronaut framework sits underneath nearly every line of code in a service, wiring components together and pulling in dozens of supporting libraries (for JSON parsing, GraphQL, caching, and more) that all need to upgrade in lockstep. Many of the resulting failures don't even surface at build time, instead appearing only once the service is running, like a component failing to wire on startup, a test silently no longer testing what it claims to, or a date being serialized with slightly different precision. Each of the 500+ microservices had to be individually rebuilt, redeployed, and revalidated against the new runtime, which is what made the original effort so expensive. Many of the breaking changes required real code rewrites whose correct form depended on context. For example, the GraphQL library upgrade made many return types nullable, so call sites that used to be straightforward: Now had to handle a nullable value. If the field was a runtime invariant, the fix was an explicit assertion: If it was genuinely optional, the call site had to branch instead: Each call site needed to be evaluated against the schema and the surrounding code, which is the kind of judgment a deterministic recipe can't make. Recognizing the mounting friction, we paused the migration after one quarter. With three full-time engineers dedicated to the effort, we had managed to migrate only around 50 microservices. The second attempt: What failed before it worked Nearly a year later, the migration had become critical. Postponing it further was beginning to block vital downstream dependency upgrades. However, LLM capabilities had advanced significantly in that short window, giving us renewed confidence that we could successfully automate the workflow. Initially, we attempted to build a single, massive AI agent to handle the entire lifecycle: making code changes, running validations, and fixing errors. The underlying prompt was an exhaustive wall of text, stuffed with every edge case we had documented. This approach failed for two primary reasons:

Reported result

- One engineer migrated 180 microservices in one month — 32x per-capita acceleration over the manual attempt. - Cost per migration down ~90% (infrastructure + token costs vs engineering time). - First manual attempt: 3 engineers, one full quarter, ~50 services. - Some automated PRs passed all tests yet caused subtle runtime bugs in production via coverage/monitoring gaps. (Company blog figures; not independently re-tested.)

Human role

- Setup: engineers trigger the pipeline by creating Linear tickets. Review: every draft PR is human-reviewed; production merges always require human approval. - Finer division of labor: Not disclosed by the source.

Limitations

- Not fully autonomous: human review is mandatory before merging to production. - A strict three-attempt cap prevents infinite loops and runaway costs. - Specific to Micronaut 4 + custom Bazel tooling; heavy upfront setup of self-hosted infra and 11 skills. - Coverage gaps let subtle runtime bugs slip into production. - Token-use details beyond the ~90% cost claim: not disclosed.

Try it

Act as a DevOps engineer implementing a framework migration pipeline. I need to migrate [replaceable: number] microservices from [replaceable: old framework] to [replaceable: new framework] using Cursor agents. First, analyze the codebase to identify deterministic changes (e.g., version flags, imports) and context-dependent refactoring needs. Create a PR Generation Agent that applies deterministic scripts and uses LLM reasoning for complex changes, opening draft PRs. Then, create a PR Validation Agent that monitors CI, deploys to a dev environment, and attempts to fix errors up to 3 times before flagging for human review. Ensure the agents are self-hosted to avoid local machine bottlenecks. Before running on all services, execute a dry run on [replaceable: sample service name] and present the diff and CI logs for my approval. Do not merge any code without my explicit confirmation. If errors persist after 3 attempts, stop and generate a report. Output the agent configuration files and a summary of the dry run results.

Original source

Source reports are not independently verified. Publication date is the catalog publication date, not the original article date.

More cases · Software & development