Engineering
9 Ways to Meaningfully Improve Your Coding Agent's Performance on Existing Codebases
Point a coding agent at an existing codebase and the weak spots in your development process show up fast. Here's the infrastructure that actually moves the needle.
Abstract
- 01Improving agent performance on an existing codebase is mostly an infrastructure problem, not a model or prompting problem.
- 02Agents need a monorepo view of the system, reproducible environments, and remote compute that survives a closed laptop.
- 03Isolated per-agent app environments, deterministic test-data presets, and structured PR reporting turn "the code compiles" into "the feature works."
- 04Hard rules (tests, types, lint, CI) should block agents outright; soft rules (diff size, scope creep, architectural reach) should force a judgment call instead.
- 05Fresh specialist reviewers catch what the implementation agent can't see because it's carrying the assumptions that produced the code.
Point a few coding agents at an existing codebase and the weak spots in your development process show up fast.
One agent can’t find the service it’s supposed to change. Another burns half its time rebuilding an environment that only ever worked on someone’s laptop. A third finishes the feature but can’t actually run the application. A fourth opens a 2,000-line pull request that no human is going to review properly.
The models aren’t the problem. The repository just wasn’t built with them in mind.
Most established codebases run on years of accumulated tribal knowledge — which README is stale, which credentials actually work, which “obviously wrong” abstraction is load-bearing and should never be touched. Humans absorb this over time and stop noticing how much unwritten context a change requires.
Agents notice immediately, because they have none of it.
So improving their performance isn’t primarily a model problem or a prompting problem — it’s an infrastructure problem. Agents need somewhere reliable to work, a deterministic way to test what they build, and guardrails that step in when their work starts to drift.
Here are nine changes that made a real difference for us.
Part I: Set Up the Remote Development Environment
The first three fixes are about removing basic operational friction. None of them make the model smarter — they just free up more of its intelligence to spend on the actual task.
1. Move your system into a monorepo
An agent can’t reason about code it can’t see.
When your frontend, backend, shared libraries, and supporting services are scattered across separate repos, even a small feature can require an agent to hunt down several projects, figure out how they’re versioned relative to each other, and coordinate changes across multiple release processes. Humans build up the muscle memory for this over years. Agents start from zero every time.
A monorepo gives the agent one coherent view of the system. That doesn’t mean collapsing everything into a monolith — services, workers, apps, libraries, and deploy processes can stay independent. They just need to live in one place.
With that, an agent can actually complete a change end to end: update a database model, modify the endpoint that exposes it, update the shared type, update the frontend that consumes it, run tests across every affected package, and ship it all as one coordinated PR.
You don’t need to migrate everything overnight. The goal is just fewer artificial walls between the agent and the thing it’s trying to understand.
Once it can see the whole system, it needs somewhere to actually work on it.
2. Run agents on remote developer boxes
A coding agent shouldn’t stop working because you closed your laptop.
Local agents inherit all the fragility of a local machine — it sleeps, the Wi-Fi drops, you move between meetings and devices, and a long-running task can just vanish because someone closed a lid. A remote box fixes this: start a task at your desk, check on it from your phone, review the PR later that night.
It also buys agents stable compute. They can run builds, tests, and migrations without eating the resources on the machine you’re actively using.
At minimum, a remote box should let an agent keep working after your laptop sleeps, preserve its branch and workspace between sessions, run long test suites without blocking your computer, hold a stable connection to dev services, and be checked on or redirected from another device — ideally while other agents work in their own separate boxes nearby.
Treat these as disposable infrastructure, not machines you carefully maintain. Agents install dependencies, spin up branches, generate stray files, and occasionally leave a mess. That’s fine, as long as tearing one down and spinning up a fresh one costs nothing.
3. Make the development environment reproducible
Most existing dev environments run on oral history.
One person knows the right package-manager version. Someone else has the working env file tucked away somewhere. A third person remembers that service A has to start before service B, and everyone quietly knows the setup docs have been six months out of date since the last migration.
A human might tolerate losing an afternoon to this once. An agent may have to repeat that setup every single time it gets a new workspace.
Starting from the repo, an agent should be able to reach a known-good state through one deterministic process — no guessing, no tribal knowledge required. That process needs to hand it: the correct runtime and package-manager versions, required system dependencies, safe testing credentials, access to dev-only APIs, repo-specific CLI tools, test database credentials, standard build/test/lint commands, and some deterministic way to confirm setup actually succeeded.
Whether that’s a container, a machine image, a setup script, or a hosted environment doesn’t matter much. What matters is that it’s repeatable — the agent shouldn’t have to reverse-engineer how your company works from a pile of half-accurate docs.
Once the agent can see the system, stay online, and work reliably, the next question is whether it can prove its work actually works.
Part II: Build the Agent Testing Experience
Writing code is only one slice of software development. Agents also need to create realistic state, run the app, look at what happened, and explain it clearly.
4. Give each agent an isolated way to run the application
The machine where an agent writes code doesn’t have to be the machine where the app actually runs.
A modern stack might need a database, a cache, a queue, a browser, a backend, a frontend, and a handful of workers. Try to run a full copy of all that for every agent on one shared box, and you’ll blow through memory and start colliding on ports, databases, and background jobs.
Instead, give each agent its own way to launch an isolated environment running the exact code it’s working on — including uncommitted changes, not just what’s already pushed. That environment needs a unique URL, its own database, isolated caches and queues, access to app and infra logs, a browser it can drive, a clean way to shut it down, and automatic cleanup once the task wraps.
All of that should sit behind a single command. The agent shouldn’t need to understand the infrastructure underneath it.
And speed genuinely matters here. If spinning up an environment takes 30 minutes, agents will avoid using it — they’ll start guessing, run narrower tests, or just declare victory without ever watching the app actually work. An agent that can only edit code is a fancy autocomplete. One that can launch the app, poke at it, look at the result, and iterate is actually developing software.
But an empty environment only gets you so far. The agent also needs realistic data to work with.
5. Give agents deterministic test data
Try to keep agents from inventing state on the fly. Most changes don’t actually need brand-new state in a test database — they need a specific, known state that already has a name.
If every task starts with “find a customer with three active subscriptions” or “create an org with a failed payment,” the agent is doing archaeology instead of testing the feature.
Give it a way to summon known states on demand, through deterministic commands or presets. For a simple app that might be a handful of seed scripts. For something more complex, it might mean real dev integrations, sanitized snapshots, or purpose-built fixtures for external systems.
A useful set of presets might cover: a brand-new user with no history, an org with several team members, a customer with an active subscription, a customer with a failed payment, an account with years of activity behind it, a partially completed onboarding flow, a database loaded with a large volume of records, or an integration stuck in a known error state.
The exact syntax matters less than the contract — the agent asks for a state, and knows exactly what it’ll get:
seed --preset failed-payment
seed --preset enterprise-account
seed --preset partially-onboarded-user
seed --preset high-volume-workspace
Where the app depends on external systems, the test environment should expose equally deterministic ways to read and write against those too. The goal: when an agent tests a feature, it knows exactly what data exists, why it exists, and how to get it back if it breaks something.
Once it can produce the right state, it needs an easy way to show a human what happened.
6. Make screenshots, videos, and PR reporting deterministic
Finishing the work isn’t the same as being able to explain it.
For anything visual, a written description usually falls flat. “Implemented the new checkout flow” tells a reviewer almost nothing. A 20-second recording of the checkout flow tells them everything.
Give agents deterministic tools for capturing evidence and attaching it to the PR — screenshots of a known page, a full recording of a user flow, browser console output, network traces, application logs, before/after database state, direct uploads to the PR, and a structured summary of what was actually tested.
This should be as easy as running one command. The agent shouldn’t have to hunt down files, wire up an upload destination, or guess at PR formatting.
A solid completion report covers what changed, which environment it was tested in, which data preset was used, what commands ran, screenshots or video of the result, and anything that’s still untested or a known limitation.
This isn’t just about review speed. It changes what “done” even means. The agent isn’t finished when the code compiles — it’s finished when it can show the feature working, with the evidence sitting right where the reviewer already looks.
Once agents can build, test, and report on their own work, what’s left is governance.
Part III: Teach the Repository How to Push Back
Agents should be free to explore while they work, but at clear handoff points, the repo needs to hold the line and demand judgment.
Senior engineers have always turned repeated mistakes into tooling — compilers, type systems, linters, test suites, coverage thresholds, CI checks. Every one of them takes a lesson learned the hard way and turns it into a rule everyone gets automatically.
Agents deserve the same treatment. In some cases we can honestly hold them to a higher bar than we hold humans.
7. Encode hard rules that agents can’t get around
Hard rules are non-negotiable and deterministic. The agent doesn’t move forward until they pass: the test suite is green, coverage stays above threshold, type checking passes, linters and formatters pass, generated files are current, no secrets in committed code, schema changes ship with their migrations, CI is green, blocking comments are resolved.
Agents shouldn’t get around these by skipping hooks, disabling tests, or slapping on an ignore directive just to move forward. Hard rules can govern sequencing too — an agent working through a stack of PRs shouldn’t start the next change while the one before it is still red or has unresolved blocking feedback.
Yes, this is stricter than what we ask of human engineers. That’s fine. Humans get impatient, context-switch, tolerate temporary mess. Agents can just wait for the conditions to be met.
8. Encode soft rules that force a judgment call
Soft rules work differently. The violation is detectable, but the right response requires judgment. The repo doesn’t say no — it says “you crossed a line, now decide whether that was on purpose.”
Think: a PR crosses roughly 500 changed lines, a change reaches across several architectural boundaries, scope has quietly grown past the original ticket, a migration is hard to reverse, a new abstraction duplicates something that already exists, or a public interface changed with no compatibility plan.
The 500-line threshold is the clearest example. A big PR might mean the work should’ve been split into a stack — or it might just be a low-risk file move, a generated update, or a repetitive migration that’s genuinely easier to review as one chunk. Reasonable exceptions: moving files without touching behavior, renaming a widely-used type, updating generated code, applying a repetitive migration, or copying an implementation and its tests ahead of updating the callers.
When the threshold trips, show the agent the rule, why it exists, and when breaking it is fine. Then it either splits the work or makes the case for why the bigger diff is still reviewable.
That’s the shape of the whole system: friction scales with risk.
9. Bring in fresh specialist reviewers before a commit lands
Before a commit lands, spin up new agents with narrow review mandates. The implementation agent is carrying all the assumptions that produced the code — a fresh reviewer sees the diff cold, with none of that baggage.
Useful reviewers: security, race conditions and concurrency, database and migrations, architecture, testing, accessibility, API compatibility. Give each one the diff, relevant repo context, and a tight remit, and have it report back through a shared structured format — JSON works fine.
The roles split cleanly: the harness checks that every required review actually ran, the specialists flag potential problems, and the implementation agent reconciles the feedback and makes the changes. The point isn’t to make one implementation agent infinitely self-aware — it’s to surround it with a few independent systems that know when and how to say “wait.”
Bonus: Detect when review has stopped converging
AI review can look like progress long after the progress has stopped.
A reviewer leaves a few comments, the implementation agent fixes them, another round starts, and eventually the PR has picked up dozens of comments. Sometimes that’s real convergence toward a correct implementation. Sometimes it’s the same structural problem getting shuffled around in circles.
Past some threshold — say, seven substantial comments, or a handful of review rounds — the harness should force an architectural gut check: is this actually converging? Is the same category of issue showing up again and again? Does the architecture itself need rethinking? Should this PR get scrapped and rebuilt? Should a fresh agent evaluate the approach from scratch?
The system doesn’t automatically throw the work away. It just makes sure someone — or something — actually stops to think before another round of local patches begins.
Bonus: Maintain skill parity across model vendors
A repo that only works well with one coding agent has quietly locked itself to one vendor.
The setup instructions, testing workflows, review skills, and repo conventions you’ve built up are real infrastructure. They shouldn’t silently belong to one model provider. We keep skill parity across Claude and Codex — every meaningful skill either has identical instructions for both, is generated from one canonical source, or has one implementation that explicitly points back to the canonical version.
That covers repo setup, running tests, launching isolated environments, seeding test data, capturing screenshots and video, reviewing migrations, opening stacked PRs, responding to review feedback, and debugging CI failures. The file formats can differ. The underlying knowledge shouldn’t.
Models will keep changing. What the repo has learned should outlast any one of them.
The Repository Is Part of the Agent
It’s tempting to judge coding agents as standalone products — this model seems smarter, that one writes cleaner code, the new release does better on some benchmark, and we assume that improvement carries straight over into our own codebase.
In practice, the same model performs wildly differently depending on what’s around it. Can it see the whole system? Can it stand up a working dev environment on its own? Can it launch the app with data that actually resembles production? Can it look at the result and tell you what happened? Does the repo catch the obvious mistakes before a human has to?
The repository isn’t just where the agent happens to write code — it’s part of the agent’s operating environment.
A good one hands over context before the agent has to guess, gives it room to explore, and only applies pressure at the moments that matter. It turns repeated human feedback into infrastructure, and makes the right behavior the path of least resistance.
You’re never going to stop agents from making mistakes. That’s not the goal. The goal is to make mistakes show up fast, make recovery cheap, and free up more of an agent’s time for actually building things.
Related
Frequently asked questions
Because the same model performs very differently depending on what's around it — can it see the whole system, stand up a working dev environment on its own, launch the app with realistic data, and get caught by guardrails before a mistake ships. The repository is part of the agent's operating environment, not just where it happens to write code.
An agent can't reason about code it can't see. When a frontend, backend, and shared libraries live in separate repos, even a small feature requires hunting down multiple projects and coordinating changes across separate release processes. A monorepo gives the agent one coherent view so it can complete a change end to end — model, endpoint, shared type, frontend, tests — as one coordinated PR.
The correct runtime and package-manager versions, required system dependencies, safe testing credentials, access to dev-only APIs, repo-specific CLI tools, test database credentials, standard build/test/lint commands, and a deterministic way to confirm setup actually succeeded — all without the agent reverse-engineering tribal knowledge from stale docs.
Hard rules are non-negotiable and deterministic — a green test suite, passing types, no secrets in committed code — and the agent doesn't move forward until they pass. Soft rules flag a detectable but judgment-dependent issue, like a 500-line PR or a change reaching across several architectural boundaries, and force the agent to either split the work or justify why the bigger diff is still reviewable.
The implementation agent is carrying all the assumptions that produced the code, so it's poorly positioned to spot its own blind spots. A fresh reviewer — security, concurrency, migrations, architecture — sees the diff cold, with none of that baggage, and reports back through a shared structured format the implementation agent then reconciles.
Watch for a PR that's picked up something like seven substantial comments or several review rounds without resolving. Past that threshold, force an architectural gut check — is the same category of issue showing up repeatedly, does the approach itself need rethinking — rather than letting another round of local patches paper over a structural problem.