Request a callbackBook a call
← All posts

Build an AI Coding Agent That Ships Real Pull Requests: Architecture, Guardrails and Cost Per PR (2026)

TL;DR
  • A coding agent that opens a pull request costs about 64 cents in models, sandbox and CI on the arithmetic below. That number is almost irrelevant. The cost that decides whether the system is worth building is the human review minute, and it is roughly fifty times larger.
  • The single metric that determines the entire business case is merge rate — the share of agent attempts that a human actually merges. On the model here, the agent pays for itself above roughly a 19% merge rate and is a rounding error of value below it. Nobody selling you a coding agent will quote that number.
  • Test-driven verification is not a quality feature, it is the containment boundary. An agent that cannot run your test suite in a sandbox is not a coding agent; it is an autocomplete with a git remote, and it will generate confident diffs that compile and break production.
The seven components of a PR-shipping coding agent
1 · Task intake and scoping

An issue, a spec or a review comment turned into a bounded task with an explicit definition of done, a file allowlist and a budget. Unbounded tasks are how agents burn $8 and produce nothing.

human: ~6 min · fails: vague spec
2 · Repo grounding

An AST-derived symbol map plus a hybrid index over code, tests, past PRs and review comments. This is the layer that decides whether the agent edits the right file at all.

$0.0004/task amortised · fails: wrong module
3 · Planner

One turn that produces a typed plan: files to touch, tests that must pass, tests that must be written, and a turn budget it has to justify. Never let the loop start without one.

$0.0374 · fails: over-broad plan
4 · Plan-then-edit loop

Read, patch, run tests, read failures, patch again. Capped at twelve turns with no-progress detection. Two thirds of the entire bill lives here.

$0.43 · fails: oscillation
5 · Sandboxed execution

A disposable container with the repo and no outbound network. The agent may run anything inside it and nothing outside it. This is the containment boundary, not a performance optimisation.

$0.039 · fails: network egress
6 · MCP tools into git and CI

Branch, commit, push, open PR, read check runs, search code. Scoped to one branch on one repo, never to main, never to the org.

$0.072 CI · fails: over-scoped token
7 · Merge gate

Tests green, diff inside declared bounds, no forbidden paths touched, a cold critic pass, and a human who has to click. Five checks, four of them deterministic.

$0.034 · fails: green tests, wrong change
Layer five is the one teams skip because it is infrastructure work with no demo value, and it is the one that converts a coding agent from a liability into a system. Without a sandbox that can actually run the test suite, every other layer is generating text that nobody has executed — and a diff nobody has executed is a hypothesis, not a change.

What is an AI coding agent, architecturally?

A grounding index over the repository, a planner that emits a typed plan, a bounded edit loop that runs the test suite after every patch, a sandbox that contains it, and a merge gate that a human has to pass. Five parts. Only the edit loop looks like an agent, and it is the part you should spend the least design time on.

That framing is unfashionable because the demo of a coding agent is the edit loop. A model reads a file, writes a diff, and the video ends. What the video omits is that the diff was applied to a repository the model already had memorised, that nothing ran the test suite, and that no human had to decide whether to merge it. All three of those omissions are where the engineering is.

Be precise about the scope this design targets. It is not an autonomous engineer. It is a system that takes well-specified, test-covered, small-to-medium changes — a bug with a reproduction, a well-scoped refactor, a dependency bump with fallout, an endpoint that mirrors an existing one — and produces a pull request a human reviews in under ten minutes. Everything about the architecture below follows from choosing that scope and defending it.

This is a reference design: how I would build this, and what the arithmetic says it costs. It draws on architecting AccioMatrix, an event-driven AI assessment and interview platform orchestrating multiple LLMs, agents and MCP tooling for 20+ enterprise clients. I have not shipped a production coding agent, and nothing below is an invoice. Every dollar figure is a model with its inputs printed, and you should substitute yours before quoting any of it.

One published finding should sit next to your business case from the first meeting. METR's randomised controlled trial, published in July 2025, found that sixteen experienced open-source developers took 19% longer to complete 246 real tasks on their own repositories when allowed to use early-2025 AI tools — while estimating afterwards that the tools had made them 20% faster. That gap between measured and perceived productivity is the reason the merge-rate arithmetic later in this post matters more than any benchmark number you will be shown.

ComponentWhat it doesImplementationCost per attemptPrimary failure modeSkip in v1?
Task intakeTurns an issue or comment into a bounded task with a definition of done, a file allowlist and a budgetA template plus a validator; reject tasks with no acceptance criteria~$0 model, ~6 min humanA vague task produces a plausible diff that solves a different problemNo — this is where merge rate is won
Repo groundingAST symbol map plus hybrid index over code, tests, past PRs and review commentstree-sitter or the language server, plus pgvector and BM25$0.0004 amortisedAgent edits a deprecated module that still compiles and is no longer calledNo
PlannerOne turn producing a typed plan: files, tests to pass, tests to write, turn budgetSonnet-class, structured output, ~25k in / 900 out$0.0374Plans a twelve-file refactor for a two-file bug and burns the whole budgetNo — a fixed plan template is worse but still a plan
Edit loopRead, patch, run tests, read failures, patch. Twelve-turn cap, no-progress detectionIn-house loop, Sonnet-class, ~8 turns typical$0.4304 (8 turns)Oscillates between two failing states, re-applying the same patch with different whitespaceNo
Sandbox runnerDisposable container, repo mounted, no outbound network, full test suite runnableFirecracker-class microVM, 2 vCPU / 4 GiB, per-second billing$0.039Egress allowed by default; a fetched dependency exfiltrates a tokenAbsolutely not
MCP tool gatewaygit branch, commit, push, open PR, read check runs, code search — one branch, one repoMCP servers behind a gateway, ~10 tools, ~1.5k tokens of schema per turnIn the turn costA token scoped to the org lets a bad plan force-push another repositoryGateway: no. MCP transport: often
CI verificationRuns the real pipeline on the pushed branch, not just the sandbox suiteGitHub-hosted Linux 2-core at $0.006/min, ~6 min per run$0.072 (2 runs)Sandbox green, CI red, because the sandbox skipped the integration testsNo
Merge gateTests green, diff bounds, forbidden paths, cold critic, human approvalDeterministic checks plus one Haiku-class critic on the diff$0.034Green tests on a change that satisfies the tests and not the requirementNo
Run journalEvery turn, diff, test result, tool call, token count and dollar, queryablePostgres tables plus OTel GenAI spans~$0You cannot answer why the agent did that three weeks laterNo

What does the full architecture look like?

Twelve components across four columns: intake and grounding, planning and the edit loop, sandbox and tool gateway, and the pull request that a human has to approve. The load-bearing detail is that the sandbox sits between the agent and every side effect, and the merge gate sits between the agent and the default branch. Neither boundary is inside the model.

Read it left to right and the containment story falls out. A task arrives and is resolved against a specific repository and a specific base commit — not a branch name, a commit SHA, because an agent that starts from a moving target produces a diff that no longer applies by the time a human looks at it. Grounding then answers a question the model cannot answer from its weights: in this repository, today, which files implement this behaviour and which tests cover them.

The planner exists to make the loop cheap. One turn that costs about 3.7 cents produces a typed plan naming the files it expects to touch, the tests that must go from red to green, and the turn budget it is requesting. That plan is then a contract: if the loop tries to edit a file outside the declared set, that is a signal worth acting on rather than an inconvenience to route around. Most agents that go badly wrong go wrong by quietly expanding their own scope, and a declared file set turns that from an invisible drift into a hard stop.

The edit loop is deliberately dull: read a file, propose a patch, apply it in the sandbox, run the targeted tests, read the failures, patch again. What makes it work is not the prompt but the feedback signal. A test failure with a stack trace is a dense, unambiguous, machine-generated correction — it is by some distance the highest-quality feedback any agent in your company receives, and it is free. The general shape of this loop, with its budgets and journal writes, is the same one described in the agent loop in production.

Everything to the right of the tool gateway is ordinary software that already exists in your company. Git hosts branches. CI runs pipelines. Humans review pull requests. The mistake teams make is trying to replace those with agent-native equivalents; the correct move is to make the agent a participant in the workflow your engineers already trust, because trust is the scarce resource in this product and you cannot bootstrap it with a new UI.

The system
PR-shipping coding agent — full reference architecturerepo + base SHApast PRssymbols + candidate filestyped plan + budgetapply + run testsfailures + tracestools/callevery turnbranch-scoped tokentest evidencecandidate diffall five checks passblocked · draft with reasonsstatus checks
Task intakeissue · spec · review comment · base SHA
Repo groundingAST symbol map + hybrid code index
Repo + PR historypast diffs, review comments, ownership
Plannertyped plan · files · tests · turn budget
Edit loopread · patch · test · 12-turn cap
Run + step journalturn, diff, test result, tokens, cost
Sandbox runnermicroVM · repo only · no egress
MCP tool gatewaygit · code search · CI reads · allowlist
Merge gatetests · diff bounds · paths · critic
Pull requestdiff + plan + test evidence + cost
Git host + CIbranch, PR, pipeline run
Human reviewermerge · request changes · close
Two edges leave the merge gate and neither of them goes to main. The dashed one is the branch that makes the system safe to run on a real repository: when the gate blocks, the agent still opens a draft pull request with the plan, the diff so far and a written explanation of which check failed. A blocked run that produces a readable artefact costs the same as a blocked run that produces silence, and one of them teaches your team something.

How does the agent ground itself in an unfamiliar repository?

With a symbol map derived from the parser, not from embeddings. Function and class definitions, their call sites, their imports and the tests that exercise them, extracted with tree-sitter or the language server and stored as a graph. Semantic search over code chunks is a useful second layer and a terrible first one.

The reason is that code retrieval and prose retrieval are different problems with the same interface. When a support agent searches documentation, semantic similarity is the right relation: the customer said one thing, the doc says another, and the embedding bridges the gap. When a coding agent needs to change the behaviour of a function, the relations it needs are exact: who calls this, what does it call, which tests import it, which module owns it. Those are graph queries, they are cheap, they are deterministic, and they are correct. An embedding will happily return a similarly named function in a deprecated package.

The layered index I would build has four tiers, and their costs are wildly asymmetric. The symbol graph is a parse over the repository — minutes of CPU, no model calls, refreshed incrementally per commit. The chunk embeddings are one pass at $0.02 per million tokens on a small embedding model, which puts a 500,000-line repository at roughly six million tokens and about twelve cents to embed in full, then near-zero to keep current. The past-PR corpus is where the institutional knowledge lives: how this team names things, which reviewer always asks for the null check, what got rejected last time. And the ownership map, from CODEOWNERS and git blame, is what lets the agent route its own PR to the right human.

Freshness is handled by commit, not by TTL, and this is a rare case where the cache invalidation problem is easy. A commit hook re-parses only the changed files and re-embeds only their chunks. A repository receiving 200 commits a day touching an average of four files is a few thousand re-embedded chunks daily, which is a fraction of a cent. Anyone quoting a large ongoing indexing bill for a code agent is either re-indexing the whole repository on a schedule or selling you something.

One deliberate omission: do not put the entire repository in context because the context window is large enough to hold it. It is a genuine option now and it is almost always the wrong one. A million-token context on Sonnet-class pricing is $2 of input per turn before the agent has done anything, and retrieval quality does not improve monotonically with context size — precision matters more than recall once you are past the relevant files. The related trade-offs for private corpora are worked through in RAG over private documents.

Four grounding tiers, in the order the agent consults them
1 · Symbol graph (deterministic)

Definitions, call sites, imports, test coverage edges, extracted by tree-sitter or the language server. Answers who calls this and what tests cover it exactly, not approximately. Refreshed per commit on changed files only.

CPU only · no model calls
2 · Hybrid chunk index

BM25 plus vector over code and doc chunks, for the fuzzy first hop when the task description does not name a symbol. Second, never first.

~$0.12 to embed 500k LOC once
3 · Past-PR corpus

Merged diffs, review comments and the discussion that produced them. This is where team convention lives: naming, error handling, which reviewer always asks for the same thing.

highest-value, lowest-hygiene
4 · Ownership map

CODEOWNERS plus git blame recency, so the agent routes its own PR to a human who will actually recognise the code. Costs nothing and materially improves review latency.

$0 · improves time-to-review
The ordering is the point. Teams build tier two first because it is the fun one and the demo works, then discover that the agent keeps editing a similarly named function in a package nobody has imported since 2024. Tier one answers that question exactly and costs nothing per query, because a parse is not an inference.
agent/edit-loop.ts
import { createHash } from "node:crypto";

type Plan = {
  files: string[];          // declared file set - enforced, not suggested
  testsMustPass: string[];
  testsToWrite: string[];
  turnBudget: number;       // <= MAX_TURNS
};

type TestResult = { passed: boolean; failures: string[]; durationMs: number };

const MAX_TURNS = 12;
const MAX_USD = 2.5;
const NO_PROGRESS_LIMIT = 3;

export type LoopOutcome =
  | { status: "candidate"; diff: string; turns: number; usd: number }
  | { status: "abandoned"; reason: string; turns: number; usd: number };

export async function editLoop(
  plan: Plan,
  sandbox: Sandbox,
  model: Model,
  journal: Journal,
): Promise<LoopOutcome> {
  let usd = 0;
  let seen = new Map<string, number>();
  const budget = Math.min(plan.turnBudget, MAX_TURNS);

  for (let turn = 1; turn <= budget; turn++) {
    if (usd >= MAX_USD) {
      return { status: "abandoned", reason: "usd_budget", turns: turn - 1, usd };
    }

    const context = await sandbox.readFiles(plan.files);
    const step = await model.propose({ plan, context, turn });
    usd += step.usd;

    // Enforce the declared file set. Scope creep is a stop, not a warning.
    const touched = filesInPatch(step.patch);
    const outside = touched.filter((f) => !plan.files.includes(f));
    if (outside.length > 0) {
      await journal.step(turn, "scope_violation", { outside, usd });
      return { status: "abandoned", reason: "scope_violation", turns: turn, usd };
    }

    const applied = await sandbox.applyPatch(step.patch);
    if (!applied.ok) {
      await journal.step(turn, "patch_rejected", { error: applied.error, usd });
      continue; // a rejected hunk is information, not a crash
    }

    const result: TestResult = await sandbox.runTests(plan.testsMustPass);
    const diff = await sandbox.diff();
    await journal.step(turn, "tested", {
      passed: result.passed,
      failures: result.failures.length,
      usd,
    });

    if (result.passed) {
      return { status: "candidate", diff, turns: turn, usd };
    }

    // No-progress detection: same diff shape AND same failure signature.
    const fp = fingerprint(diff, result.failures);
    const n = (seen.get(fp) ?? 0) + 1;
    seen.set(fp, n);
    if (n >= NO_PROGRESS_LIMIT) {
      return { status: "abandoned", reason: "no_progress", turns: turn, usd };
    }
  }

  return { status: "abandoned", reason: "turn_budget", turns: budget, usd };
}

function fingerprint(diff: string, failures: string[]): string {
  const normalisedDiff = diff.replace(/\s+/g, " ").trim();
  const normalisedFailures = failures.map((f) => f.split(":")[0]).sort().join("|");
  return createHash("sha256")
    .update(normalisedDiff + "::" + normalisedFailures)
    .digest("hex");
}
The plan-then-edit loop, with the three things that separate it from a demo: a declared file set that the loop enforces rather than the prompt suggesting, a no-progress detector keyed on the hash of the diff plus the test signature so oscillation terminates instead of burning the budget, and a hard dollar ceiling checked before every turn. The loop never returns a diff that has not had the test suite run against it.

What happens on one pull request, end to end?

Intake, grounding, one planner turn, eight edit turns each followed by a targeted test run, a push, a real CI run, a critic pass on the final diff, then a pull request that a human either merges or closes. Roughly fourteen minutes of wall clock, about eleven model calls and one row per turn in a journal you own.

The ordering choice that matters most is when the full test suite runs versus when a targeted subset runs. Inside the loop, only the tests named in the plan execute — typically eight to forty tests, three to twenty seconds. Running the complete suite after every patch would be architecturally purer and would multiply both the wall clock and the sandbox bill by an order of magnitude for a signal the agent mostly does not need. The full suite runs once, in real CI, on the pushed branch, and its failure is a legitimate reason to send the run back for one more turn rather than to open the PR.

The second detail is that the sandbox is created once per run and destroyed at the end, not created per turn. Sandbox billing is per second of existence rather than per second of execution, so an idle sandbox costs exactly what a busy one does. At the modelled rate of $0.000046 per second for 2 vCPU and 4 GiB, a fourteen-minute run is about 3.9 cents. Leave that sandbox alive for an hour because a queue backed up and it is 16.6 cents, which is a quarter of your entire per-PR budget spent on a container doing nothing. A hard TTL on the sandbox is a cost control disguised as hygiene.

The third detail is that the pull request carries evidence, not just a diff. The body should contain the plan the agent committed to, the tests that went red-to-green with their names, the files touched against the files declared, the turn count, and the dollar cost of the run. That is roughly three cents of model call and it changes the review from an act of faith into an act of checking. A reviewer who can see that the agent declared four files and touched four files, and that the two named tests moved from failing to passing, reviews substantially faster than one staring at an unexplained diff.

Finally, note where the run can end early and cheaply. Scope violation terminates at the turn it happens. No-progress terminates after three identical diff-plus-failure fingerprints. The dollar ceiling terminates before a turn rather than after it. Each of those is a few cents of loss instead of two dollars, and together they are why the abandoned-run cost in the model is $0.77 rather than the $2.50 ceiling.

The ship path
One pull request, end to end, with tokens and cost on every model callIssueOrchestratorGroundingAgentSandboxMCP GWMerge gateReviewer
task: bug with reproduction + acceptance criteria
resolve repo + pin base SHA
never a branch name
symbol lookup + hybrid search
9 candidate files, 14 covering tests
$0.0004 amortised
create sandbox (2 vCPU / 4 GiB), clone at SHA
clock starts
plan: 25.0k in (12k cached) / 900 out
$0.0374 · Sonnet 5
typed plan: 4 files, 2 tests must pass, 8-turn budget
turn 1: apply patch, run 2 named tests
$0.0538 · 21s model, 9s tests
1 failing: AssertionError line 84 + trace
turns 2-7: patch, test, read failures
$0.323 combined
turn 8: all named tests green
$0.0538
tools/call git_commit + git_push (branch only)
CI run on pushed branch, 6 min
$0.036 · Actions Linux 2-core
critic pass on final diff: 30k in / 800 out
$0.034 · Haiku 4.5
gates pass: open PR with plan + evidence
$0.030 PR body
PR routed via CODEOWNERS
merged after 9 min of review
$11.25 of human time
Total modelled machine cost: $0.6432. Total modelled human cost on this path: $11.25 of review plus about $7.50 of task specification. The machine column is 3.4% of the run. Every optimisation conversation about coding agents that starts with model choice is optimising the small number, and every conversation that starts with how do we make review faster is optimising the large one.
Where a run can stop early, and what it costs when it does
  1. 1
    Stop 1 · Intake rejects the task$0.00

    No acceptance criteria, no reproduction, or a file allowlist that spans more than eight files. The task goes back to a human with a specific reason. This is the cheapest possible failure and the most under-used.

  2. 2
    Stop 2 · Grounding finds nothing$0.0004

    No symbol match and no chunk above the relevance floor. The agent should say I cannot locate the code this describes rather than guess a module. Refusal is a feature.

  3. 3
    Stop 3 · Scope violation~$0.15 at turn 3

    The loop tries to patch a file outside the declared set. Terminate at that turn, journal the attempted files, and surface them — a repeated scope violation on the same task usually means the plan was wrong, not the agent.

  4. 4
    Stop 4 · No progress~$0.31 at turn 6

    Three consecutive turns with the same diff-plus-failure fingerprint. This is oscillation, and it will not resolve itself with more turns. Terminate and open a draft PR showing the wall it hit.

  5. 5
    Stop 5 · Budget exhausted$0.77 modelled

    Twelve turns or $2.50, whichever binds first. The dollar check runs before the turn, not after, so the ceiling is a ceiling.

  6. 6
    Stop 6 · Merge gate blocks$0.61 + human triage

    CI red, diff outside declared bounds, a forbidden path touched, or the critic flags a mismatch between the change and the requirement. Draft PR with the failing check named.

Six stops, and the first two cost less than a twentieth of a cent. The economics of a coding agent are dominated by how quickly it gives up on work it cannot do, because a run that fails at turn eleven costs twenty times a run that fails at intake and produces the same amount of merged code, which is none.

How do you stop it merging nonsense?

With five checks outside the model, four of them deterministic, and a human who has to click merge. Tests green in real CI. Diff inside the declared file set and under a line ceiling. No forbidden path touched. A cold critic that compares the diff to the original requirement rather than to the plan. And an approval from a code owner.

The critic deserves the most explanation because it is the only one that involves a model, and because the naive version of it is useless. Asking the same model that wrote the diff whether the diff is good returns yes. What works is a separate, cheaper model given three things and nothing else: the original task description, the final diff, and the test names that changed state. It is asked one question — does this diff accomplish what the task asked, and does it do anything the task did not ask for. It never sees the agent's reasoning, because the reasoning is exactly the artefact that will talk it into agreement.

The forbidden-path list is the check that saves you from the worst outcome, and it is fifteen lines of configuration. Migrations, authentication and authorisation modules, payment handling, CI configuration, dependency manifests, infrastructure-as-code, secrets templates. An agent should be able to read all of those and write to none of them without an explicit per-task override that a human grants. The reason is not that a model cannot write a migration; it is that a wrong migration is not a code review problem, it is an incident, and the asymmetry between the value of automating that change and the cost of getting it wrong is not close.

The diff-bounds check is the quiet one that catches the most real problems. A task declared four files and a hundred lines; the diff touches four files and nine hundred lines. Nothing has failed — the tests are green and the paths are allowed — but something has gone sideways, usually a reformat, a lockfile regeneration or an over-eager refactor riding along with the actual change. A ceiling of roughly three times the planned line count, with the overrun surfaced in the PR body, turns that from a reviewer's twenty-minute discovery into a one-line banner.

None of these checks make the agent correct. They make its failures cheap and visible, which is the achievable goal. The honest framing to give a team is that the merge gate is a filter on a probabilistic generator: it converts a distribution of diffs, some of which are dangerous, into a distribution of diffs whose worst case is a wasted review. That is the same design philosophy as the confidence gate in the customer support agent build, applied to a domain where the blast radius is your default branch.

gate/merge-gate.ts
import { z } from "zod";
import micromatch from "micromatch";

export const CriticSchema = z.object({
  accomplishes_task: z.boolean(),
  does_extra_work: z.boolean(),
  concerns: z.array(z.string()).max(5),
});

const FORBIDDEN = [
  "**/migrations/**",
  "**/auth/**",
  "**/billing/**",
  "**/payments/**",
  ".github/workflows/**",
  "infra/**",
  "**/*.tf",
  "package-lock.json",
  "pnpm-lock.yaml",
];

const DIFF_LINE_MULTIPLIER = 3;

type Candidate = {
  task: string;
  diff: string;
  filesTouched: string[];
  filesDeclared: string[];
  linesChanged: number;
  linesPlanned: number;
  ciGreen: boolean;
  testsFlipped: string[];
};

export type GateResult =
  | { decision: "ready_for_review"; notes: string[] }
  | { decision: "blocked"; reason: string; detail?: string };

export async function mergeGate(
  c: Candidate,
  critic: (c: Candidate) => Promise<z.infer<typeof CriticSchema>>,
): Promise<GateResult> {
  // 1. free: the real pipeline, not the sandbox subset
  if (!c.ciGreen) {
    return { decision: "blocked", reason: "ci_red" };
  }

  // 2. free: forbidden paths are a hard stop, never a warning
  const forbidden = micromatch(c.filesTouched, FORBIDDEN);
  if (forbidden.length > 0) {
    return { decision: "blocked", reason: "forbidden_path", detail: forbidden.join(", ") };
  }

  // 3. free: the plan is a contract, not a suggestion
  const undeclared = c.filesTouched.filter((f) => !c.filesDeclared.includes(f));
  if (undeclared.length > 0) {
    return { decision: "blocked", reason: "undeclared_files", detail: undeclared.join(", ") };
  }

  // 4. free: a green diff 9x the planned size is still a red flag
  const ceiling = Math.max(40, c.linesPlanned * DIFF_LINE_MULTIPLIER);
  if (c.linesChanged > ceiling) {
    return {
      decision: "blocked",
      reason: "diff_too_large",
      detail: c.linesChanged + " changed vs ceiling " + ceiling,
    };
  }

  // 5. the only model call, on a cheap model, without the agent's reasoning
  const verdict = await critic(c);
  if (!verdict.accomplishes_task) {
    return { decision: "blocked", reason: "critic_task_mismatch", detail: verdict.concerns.join("; ") };
  }

  const notes = verdict.does_extra_work
    ? ["Critic flagged work beyond the task: " + verdict.concerns.join("; ")]
    : [];

  // The best outcome this function can produce is a PR a human still has to merge.
  return { decision: "ready_for_review", notes };
}
The merge gate in full. Four deterministic checks run before the one model call, in ascending order of cost, so a forbidden-path violation never pays for a critic pass. The critic sees the task, the diff and the changed test names — deliberately not the agent's reasoning, because the reasoning is a persuasion artefact. Note that the gate never merges; its best outcome is ready_for_review.
Which tasks should reach the agent at all
Should this task go to the coding agent, to a human, or nowhere?
Bug with a reproduction, in a directory above the coverage threshold
Agent, full autonomy to PR

The best case. A failing test is the definition of done, the loop has a real signal, and the merge gate has something to verify against. Expect the highest merge rate in this class by a wide margin.

Mechanical change across many files — rename, API migration, dependency bump fallout
Agent, with a raised diff ceiling

High merge rate, low review cost per line, and exactly the work engineers most resent. Raise the line ceiling explicitly for this task class rather than removing the check.

New endpoint or component that mirrors an existing one
Agent, with the exemplar named

Naming the file to imitate in the task is worth more than any prompt engineering. Without it the agent invents a house style; with it, review is a diff against a known shape.

Anything touching auth, billing, migrations, CI or infrastructure
Human, agent may draft only

The forbidden-path list. The agent can produce a draft in a scratch branch for a human to take over, but it may not open a PR against these paths. The asymmetry is not close.

Untested legacy module, or a task with no acceptance criteria
Nowhere — reject at intake

Without tests there is no falsification signal and the loop optimises for plausibility. Without acceptance criteria the critic has nothing to compare against. Both produce diffs that look right and waste a review.

Merge rate is not a property of the agent. It is a property of the task distribution you allow through intake. A team that routes only the first three classes will report a merge rate two to three times higher than a team that routes everything, using identical software — which is why comparing vendor merge-rate claims without knowing their intake policy is meaningless.

What tools should the agent have, and how are they defined?

About ten, and the scoping matters more than the count. Reads: search code, read file, list symbols, read test output, read check runs, read past PRs. Writes: apply patch (sandbox only), commit, push to a run-specific branch, open a pull request. There is deliberately no merge tool and no force-push.

Every tool schema costs roughly 150 tokens on every turn, so ten tools is about 1,500 tokens per turn and roughly 15,000 tokens across a ten-turn run. That is real but not dominant. The dominant reason to keep the surface small is selection accuracy: an agent choosing between ten well-described tools picks correctly far more often than one choosing between forty, and every wrong tool choice costs a full turn at roughly five cents plus the wall clock.

The credential scoping is where a coding agent differs sharply from every other agent you will build. A support agent reads customer records; the blast radius of a mistake is one customer. A coding agent holds a git credential, and the blast radius of a badly scoped one is your organisation's source control. The correct scope is a token valid for one repository, one branch whose name contains the run id, with no force-push, no branch deletion, no tag push, no workflow-file write and no merge permission. Mint it per run, expire it in an hour, and record its identity in the journal so any commit can be traced to a run.

On transport: use MCP for the things you did not build — the git host, the CI system, an external code search — and plain typed function calls for your own sandbox. The July 2026 specification revision made the operational side considerably easier, removing protocol-level sessions and the Mcp-Session-Id header from Streamable HTTP so that any server instance can serve any request behind an ordinary load balancer, and adding Mcp-Method and Mcp-Name headers that a gateway can route and rate-limit on without parsing the JSON body. For an agent that fans out tool calls across many concurrent runs, being able to rate-limit per tool name at the edge is genuinely useful. The full gateway pattern is in MCP in production.

The tool definition below is the shape I would ship for the single most important tool in the system: running tests. Three things distinguish it from the naive version. The sandbox id comes from the verified run context and is never a model-settable parameter, so an agent cannot execute in another run's container. The output is truncated server-side with the failures preserved and the passing noise dropped, because a verbose test runner will emit 60,000 tokens of green dots. And the description tells the model when not to call it, which removes an entire class of wasted turn.

mcp-tools/run-tests.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

const server = new McpServer({ name: "sandbox-exec", version: "1.4.0" });

const MAX_FAILURES = 12;
const MAX_CHARS = 8000;

server.registerTool(
  "run_tests",
  {
    title: "Run a named subset of the test suite in this run's sandbox",
    description:
      "Runs the given test files or test names inside the sandbox for the " +
      "current run and returns pass/fail plus the first failures with stack " +
      "traces. Call this after every patch. Do NOT call this to explore the " +
      "codebase - use search_code or read_file, which are free and instant. " +
      "Do NOT pass an empty selector to run the whole suite; the full suite " +
      "runs once in CI after the branch is pushed.",
    inputSchema: {
      selectors: z.array(z.string()).min(1).max(40)
        .describe("Test file paths or fully qualified test names"),
      timeoutSec: z.number().int().min(5).max(300).default(120),
    },
  },
  async ({ selectors, timeoutSec }, extra) => {
    // Sandbox is NOT a model-settable argument. It is resolved from the
    // verified run context. This is the entire run-isolation story.
    const runId = requireRunId(extra.requestInfo);
    const sandbox = await sandboxes.forRun(runId);

    const started = Date.now();
    const exec = await sandbox.exec({
      cmd: testCommandFor(sandbox.language, selectors),
      timeoutSec,
      network: "none", // no egress, ever, including from the test process
    });

    const parsed = parseTestOutput(exec.stdout, exec.stderr);
    const failures = parsed.failures.slice(0, MAX_FAILURES).map((f) => ({
      test: f.name,
      message: f.message,
      trace: f.trace.split("\n").slice(0, 8).join("\n"),
    }));

    const payload = {
      passed: parsed.failed === 0 && exec.exitCode === 0,
      total: parsed.total,
      failed: parsed.failed,
      truncatedFailures: Math.max(0, parsed.failures.length - MAX_FAILURES),
      durationMs: Date.now() - started,
      timedOut: exec.timedOut,
      failures,
    };

    const text = JSON.stringify(payload).slice(0, MAX_CHARS);

    return {
      // A failing suite is the signal the agent needs, not a tool error.
      content: [{ type: "text", text }],
      structuredContent: payload,
    };
  },
);
The run_tests MCP tool, production-shaped, on the 2026-07-28 stateless spec. The sandbox is resolved from the verified request context rather than a model argument — that one line is the entire run-isolation story. Output is bounded server-side to the first twelve failures with passing output stripped, because trusting the caller to bound a test runner's stdout is how a context window dies. A non-zero exit is information for the model, not a protocol error.
The ten-tool surface, with scope and blast radius
 KindScopeApprovalBlast radius if wrong
search_codereadone repo, indexed snapshot at base SHAnoneAgent edits the wrong module; caught by the critic or the reviewer
read_filereadone repo, path-allowlistednoneWasted tokens. The cheapest possible mistake
list_symbolsread, deterministicsymbol graph onlynoneStale graph after a fast-moving merge; refresh is per-commit
read_past_prsread, merged onlyone repo, review comments includednoneLearns a convention the team has since abandoned
apply_patchwrite, sandbox onlythe run's container filesystemnoneContained by construction. Nothing outside the sandbox can see it
run_testsexecute, no egressthe run's container, subset selectorsnoneA test that mutates shared state — which is why egress is off and the container is disposable
git_commitwriterun-specific branch onlynoneA bad commit on a branch nobody merges. Recoverable by deletion
git_pushwrite, no forcerun-specific branch, per-run token, 1h TTLnoneThe single most dangerous tool here. No force-push, no tag push, no workflow writes
open_pull_requestwriteone repo, base branch fixedmerge gate must passReview-queue noise, which is a real cost at volume — cap PRs per repo per day
read_check_runsreadthe run's branch onlynoneMisreads a flaky failure as a real one and burns two turns chasing it
merge_pull_requestnot exposedn/an/aDeliberately absent. The human click is the product, not the friction
force_push / delete_branchnot exposedn/an/aDeliberately absent. There is no task worth the recovery story
The last two rows are the most defensible decisions in the whole design. A coding agent with merge permission is a system where a prompt injection in a dependency's README becomes a commit on your default branch. The human click costs about nine minutes and buys a containment property that no amount of gating replaces.

What does one pull request actually cost?

About 64 cents in models, sandbox and CI for a run that produces a PR, and about 77 cents for one the budget governor abandons. Blended over a modelled mix of 55% merged, 25% closed and 20% abandoned, that is roughly $1.21 of machine cost per merged pull request. The human cost on the same run is about $19, and that is the number that decides everything.

Work the resolve path and the shape is immediate. The planner turn is $0.0374. Eight edit turns at $0.0538 each — 20,000 fresh input tokens, 14,000 cached, 1,100 output on Sonnet-class pricing — is $0.4304, or 67% of the machine bill. The sandbox at $0.000046 per second for fourteen minutes is $0.039. Two CI runs at six minutes each on Linux 2-core at $0.006 per minute is $0.072. The Haiku-class critic on a 30,000-token diff is $0.034 and the PR body is $0.030. Grounding is $0.0004 amortised. Total $0.6432, every line of which is a token count multiplied by a printed rate.

Now the arithmetic that matters. Take a hundred attempts. Specification costs six minutes each at $75/hour fully loaded, which is $750. Machine cost across the mix is $66.60. Review costs nine minutes on the 55 merged PRs and seven on the 25 closed ones, which is 670 minutes or $837.50. Triage on the 20 abandoned runs is four minutes each, $100. That is $1,754 for 55 merged changes, or $31.89 each. The same 55 changes written by hand at a modelled 70 minutes each cost $4,812, or $87.50 each. The saving is real — about 64% — and 95% of both sides of the comparison is human minutes.

Which means the sensitivity analysis is the whole analysis. Hold everything else fixed and vary merge rate: at 40% the cost per merged change is $42.92, at 30% it is $56.39, at 20% it is $83.33, and the crossover with writing it by hand lands at roughly 19%. Below that the agent is a net cost, and the reason is not tokens — it is that every closed PR consumed seven minutes of a senior engineer's attention and produced nothing. A coding agent is a machine that converts review minutes into merged changes at some efficiency, and if the efficiency is bad the machine is a tax.

Two implications follow that most vendor pages will not tell you. First, tightening intake raises merge rate far more cheaply than upgrading the model, because rejecting a task that was never going to merge saves the entire review minute. Second, the cheapest available optimisation is making agent PRs faster to review — evidence in the body, small diffs, a declared file set the reviewer can check at a glance. Neither of those is a model problem. The general cost machinery for the model side, including routing and cache design, is in LLM routing, caching and cost per request.

Line itemModel / rateTokens or unitsPR openedAbandoned runNote
Repo grounding (amortised)text-embedding-3-small · $0.02 per 1M~6M tokens indexed once, incremental after$0.00040$0.00040One-time full index of a 500k-LOC repo is about $0.12
Planner turnSonnet 5 · $2 / $10 per 1M25.0k in (12k cached) / 900 out$0.03740$0.03740Cached prefix = system + 10 tool schemas + repo conventions
Edit turnsSonnet 534k in (14k cached) / 1.1k out per turn$0.43040 (8 turns)$0.64560 (12 turns)67% of the machine bill. Each turn re-sends files plus failures
Sandbox computeE2B-class · $0.000046 per second2 vCPU / 4 GiB, 840s vs 1,200s$0.03864$0.05520Billed for existence, not execution. A TTL is a cost control
CI runsGitHub Actions Linux 2-core · $0.006/min2 runs x 6 min vs 1 run x 6 min$0.07200$0.03600The real pipeline, not the sandbox subset
Critic pass on final diffHaiku 4.5 · $1 / $5 per 1M30.0k in / 800 out$0.03400n/aSees task, diff and flipped test names. Never the agent's reasoning
PR body with evidenceSonnet 512.0k in / 600 out$0.03000n/aPlan, tests flipped, files declared vs touched, turns, cost
Machine subtotal$0.64284$0.77460Modelled from Aug 2026 list prices, not measured
Task specification$75/hr fully loaded6 min$7.50000$7.50000Paid on every attempt, including the ones that go nowhere
Human review or triage$75/hr fully loaded9 min merged / 7 min closed / 4 min abandoned$11.25000$5.00000The dominant term by a factor of about thirty
Total per attempt$19.393$13.275Blended per merged change at 55% merge rate: $31.89
Where 64 cents of machine cost goes
$ per pull request opened, 8-turn runlower is better
Edit turns (8 x Sonnet 5)67% — each turn re-sends files plus failures$0.4304
CI runs (2 x 6 min)11% — the real pipeline, worth every cent$0.0720
Sandbox (840s at 2 vCPU / 4 GiB)6% — billed for existence, so cap the TTL$0.0386
Planner turn6% — the cheapest turn in the run and the highest leverage$0.0374
Critic pass (Haiku 4.5)5% — the only model call in the merge gate$0.0340
PR body with evidence5% — three cents that saves review minutes$0.0300
Repo grounding (amortised)<1%$0.0004
Machine total$0.6432
This entire chart is 3.4% of the run's true cost. Print it, then put it away. The chart that decides whether you build this is the next one, and it has no model prices in it at all — because a coding agent is an economic machine whose input is senior engineer attention and whose output is merged diffs.
Cost per merged change, by merge rate
1841389246010%20%30%40%55%70%Fully loaded cost per merged change ($)Share of agent attempts a human actually merges
Crossover ~19% merge rate
Agent path — spec + machine + review + triageHuman writes it — 70 min at $75/hr
The agent line is steep at the left because the fixed human costs — six minutes of specification per attempt and seven minutes of review on every PR that gets closed — are paid regardless of outcome and divided by a shrinking number of merged changes. Below roughly a 19% merge rate the agent costs more than writing the change by hand, and the fix is never a better model. It is a stricter intake policy that stops sending it work it cannot do.
The four numbers to put on the business case
$0.64
modelled machine cost of one run that opens a pull request
$31.89
modelled fully loaded cost per merged change at a 55% merge rate
vs $87.50 by hand
~19%
merge rate below which the agent costs more than writing the change yourself
3.4%
share of the true per-run cost that is models, sandbox and CI combined
The fourth number is the one that reframes the project. If models were free tomorrow, the cost per merged change would fall from $31.89 to $30.68 — a 3.8% improvement. Everything that actually matters is intake quality and review speed, and both are product decisions rather than model decisions.

What breaks in production, and how would you know?

Eleven things, and the three most expensive all produce a green pipeline. A diff that satisfies the tests without satisfying the requirement, a flaky test the agent silently disables, and a change that quietly widens its own scope all look like healthy successful runs on every dashboard you own. No error rate moves.

Read the failure table below by its second column first, because what the user sees is what determines whether a failure is a wasted review, a rolled-back deploy or a security incident. The row worth staring at is the prompt-injection one. A coding agent reads issues, README files, dependency documentation and code comments — all of it attacker-influenceable in an open-source dependency, and all of it landing in a model context that holds a git credential. The defence is not a better prompt. It is that the credential cannot force-push, cannot touch workflow files, cannot merge, and expires in an hour, so a successful injection produces a bad branch rather than a bad deploy.

Four signals catch most of the rest. Merge rate by task class is the primary one and it must be broken out, because aggregate merge rate hides a bug-fix class at 70% and a refactor class at 15% averaging to a comfortable-looking 55%. Review time on agent PRs versus human PRs is the second: if reviewers are spending longer on agent diffs than on their colleagues', the system is transferring work rather than removing it. Revert rate within seven days is the third and the sharpest quality signal available, requiring no labelling and no judge. Turn distribution is the fourth, because a rightward shift in turns per run is the earliest warning that grounding quality has degraded.

Alert on rates and distributions, never on individual runs. An agent that produces one unmergeable PR in six is a functioning agent. An agent whose merge rate dropped from 55% to 32% over a week is an incident, and the usual cause is a repository change — a refactor that invalidated the symbol graph, a test suite that got slower and started timing out, a dependency bump that broke the sandbox image. All three are invisible in model metrics and obvious in merge rate.

The last row of the table is the capability that separates a system from an experiment. If you cannot answer, for a specific pull request from three weeks ago, which files the agent read, what its plan declared, which tests flipped, how many turns it took, what it cost, and which check the gate evaluated last, you do not have a production coding agent. You have a very expensive suggestion box. That capability is one journal table, one query, and it is what makes the difference between debugging the system and rewriting it.

Failure modeWhat the user seesWhere to fix itDetection signalCost of getting it wrong
Diff satisfies the tests, not the requirementA green PR that a reviewer approves and that ships the wrong behaviourMerge gate: the cold critic compares diff to the original task, never to the planRevert rate within 7 days; reviewer change-request reasonsOne production bug plus the review minutes that legitimised it — roughly $11 wasted and an incident
Agent disables or weakens a flaky testAll green, coverage silently down, the flake returns in productionGate: block any diff that deletes assertions or adds skip markers without an explicit task authorisationAssertion-count delta per PR; skip-marker linter in CISystemic. Every future run trusts a suite that no longer tests the thing
Scope creep inside an allowed file setA 900-line diff for a 100-line task; review takes 40 minutesGate: diff-line ceiling at 3x planned, surfaced in the PR body as a bannerRatio of changed lines to planned lines, p90Review cost triples on that PR and reviewer trust in the whole system drops
Prompt injection via issue text or dependency docsA branch containing an unrelated change, possibly an exfiltration attemptCredential scope: branch-only token, 1h TTL, no force-push, no workflow writes, no mergeDiff touching paths unrelated to any declared file; egress attempts from the sandboxContained to a branch by design. Without the scoping, this is your worst day
Sandbox has outbound networkNothing visible; a dependency install phones home with an env varSandbox: egress denied by default including from the test process, allowlist only a package mirrorEgress attempt count per run; any non-zero value on a default-deny policyCredential exfiltration. The one failure here that is not fixable with an apology
Grounding returns a deprecated moduleA confident diff to code nothing imports; tests pass because nothing calls itGrounding: symbol graph first, with call-site counts and last-modified as ranking featuresShare of PRs touching files with zero inbound call edgesA full wasted run plus a review, about $19, and a reviewer who now distrusts intake
Loop oscillates between two failing statesNothing for eleven minutes, then an abandoned runRuntime: fingerprint the diff plus failure signature, terminate after 3 repeatsNo-progress termination rate; turn-count distribution shifting right$0.31 to $0.77 per stuck run, and the wall-clock slot it occupied
Base SHA moved during the runA PR with conflicts that the reviewer has to resolveOrchestrator: pin the base commit at intake, rebase once at push, abandon on a second conflictConflict rate at push; time between intake and push, p95Reviewer resolves a merge conflict for a machine, which is the fastest way to kill adoption
Test output floods the context windowTruncated reasoning, then a nonsense patchMCP server: bound output server-side to the first N failures with passing output strippedTool result size p99; context-overflow error rateA wasted turn at about $0.05, plus a patch generated from half a stack trace
Review-queue floodingEngineers ignoring agent PRs entirelyOrchestrator: cap open agent PRs per repository per day; queue rather than fan outOpen agent PRs older than 48h; median time-to-first-reviewThe system dies socially before it fails technically. This is the most common real-world ending
No run journalNobody can explain a merged change three weeks laterJournal every turn, diff, tool call, test result, token count and dollar in PostgresCan you answer why did it do that from one SELECT — yes or noYou lose the ability to distinguish a regression from a bad week, permanently
Checklist
The six signals to instrument before the first PR
  • Merge rate, broken out by task classAggregate merge rate is the single most misleading number in this product. Bug fixes and speculative refactors do not belong in the same average.
  • Review minutes on agent PRs versus human PRsIf agent diffs take longer to review than human ones, you have moved work rather than removed it. This is the honesty metric.
  • Revert rate within 7 days of mergeThe sharpest quality signal in the domain. No judge, no labelling, no eval set. Wire it before launch.
  • Turn-count distribution per runA rightward shift is the earliest warning that grounding degraded — usually after a refactor invalidated the symbol graph.
  • Cost per merged change, with a price snapshotStore cost as bigint micros with the rate card id, and include the human minutes. A models-only cost dashboard tells you almost nothing here.
  • Sandbox egress attempts, on a default-deny policyAny non-zero value is worth a look. This is the cheapest security signal you will ever add and it takes an afternoon.
  • Reviewer change-request reason codesA dropdown on the review — wrong approach, missed a case, style, scope. Organisational change rather than engineering, which is why it never gets built and why it would be the most valuable eval set you own.
Six of these seven are a day of work on top of a journal table you need anyway. The seventh needs a human to click a dropdown, which makes it the hardest to ship and the only one that produces a labelled dataset of why agent changes get rejected — which is precisely the dataset that would let you fix intake.

What does it take to build, in engineer-weeks?

About ten engineer-weeks for a two-person team to a v1 that can be pointed at a real repository, plus roughly four engineer-days a month of maintenance. The distribution surprises people: the agent loop is about a week, and the sandbox, the grounding index and the merge gate are six between them.

Week one is intake, the journal and cost attribution — the task schema with its acceptance criteria and file allowlist, the run and turn tables, and cost middleware that stores micros against a rate-card id. No demo value, and everything downstream depends on it. Week two is the sandbox: a disposable microVM image that can install your dependencies and run your test suite with egress denied, per-second billing, a TTL, and a clean teardown. This is unglamorous platform work and it is the component that most determines whether the project survives its first security review.

Weeks three and four are grounding. The symbol graph via tree-sitter or the language server, the incremental per-commit refresh, the hybrid chunk index, and the past-PR corpus with its review comments. Like the retrieval corpus in a support agent, this phase is underestimated by the widest margin, because the parse is a day and the incremental freshness plumbing is the rest.

Week five is the planner and the edit loop with all their budgets. Week six is the MCP tool surface and the per-run credential minting, which is more security work than software. Weeks seven and eight are the merge gate — the deterministic checks are quick, the critic prompt and its calibration against a set of real merged and rejected diffs is not. Weeks nine and ten are evals, dashboards, the PR-body template, review-queue rate limiting, and the actual work of introducing this to engineers who did not ask for it and will judge it on their first three PRs.

Two things get cut and both are mistakes. The first is the PR evidence body, cut because it is not a feature, despite costing three cents and being the single largest lever on review time, which is 95% of your cost. The second is intake validation, cut because rejecting a colleague's task feels rude — and then merge rate sits at 22%, the crossover is 19%, and nobody can explain why the project feels like a tax. If you need to move faster than ten weeks, compress scope rather than quality: one repository, one task class, six tools, a hard eight-turn cap. That is closer to five weeks and it is a real system.

Ten engineer-weeks, sequenced
  1. Week 1
    Intake, journal, cost attribution

    Task schema with acceptance criteria and file allowlist. Run and turn tables. Cost middleware storing micros against a rate-card id. Zero demo value, total downstream dependency.

  2. Week 2
    The sandbox

    Disposable microVM that installs dependencies and runs your suite with egress denied. Per-second billing, hard TTL, clean teardown. Platform work that decides whether this passes a security review.

  3. Weeks 3‑4
    Repo grounding

    Symbol graph via tree-sitter or the language server, incremental per-commit refresh, hybrid chunk index, past-PR corpus with review comments. The parse is a day; freshness is the rest.

  4. Week 5
    Planner and edit loop

    Typed plan, declared file set enforced by the loop, turn and dollar budgets, no-progress fingerprinting, journal writes per turn. Smaller than anyone expects.

  5. Week 6
    MCP tools and per-run credentials

    Ten tools, branch-scoped token minted per run with a one-hour TTL, no force-push, no workflow writes, no merge. More security design than code.

  6. Weeks 7‑8
    Merge gate and critic calibration

    Deterministic checks are quick. Calibrating the critic against a real set of merged and rejected diffs is the work, and skipping it means your gate is a guess wearing a schema.

  7. Weeks 9‑10
    Evals, evidence, rollout

    Deterministic CI suite over a frozen task set, dashboards, the PR-body template, review-queue rate limiting, and shadow mode on a real repo where nobody merges anything for two weeks.

Note that the agent appears in week five of ten. Teams that start with the loop have a compelling demo by Wednesday and then spend two months discovering that the demo was the easy fifth of the problem. Starting with the journal and the sandbox feels slow and is the only ordering that ends on time.
Modelled economics at 100 agent attempts a month
Engineers write the changes
Changes shipped
55
Time per change
70 min
Fully loaded rate
$75/hr
Machine cost
$0
Monthly cost
$4,813
Cost per merged change
$87.50
Agent at a 55% merge rate
Task specification
100 x 6 min = $750
Machine (models, sandbox, CI)
$66.60
Review on 80 PRs
670 min = $838
Triage on 20 abandoned runs
80 min = $100
Monthly cost
$1,754
Cost per merged change
$31.89
Modelled saving $3,059/month at 100 attempts — entirely contingent on merge rate
Recompute this table at your own merge rate before you believe it. At 30% the agent column becomes $1,692 for 30 merged changes, or $56.39 each, and the saving shrinks to about a third. At 19% the two columns meet. The input most worth challenging is the 70-minute human baseline: if the tasks you route to the agent would have taken 25 minutes by hand, the agent never wins at any merge rate.

What should you skip in version one?

Multi-repository changes, autonomous merge, self-improving prompts and a custom fine-tuned model. Each is a real capability and each costs a month you should instead spend discovering whether your merge rate clears 30% on one repository and one task class.

Skip multi-repository work first, because it is the most requested and the most expensive. A change spanning three services needs coordinated branches, a deployment ordering, and a review across three sets of owners — that is a release-engineering problem with an agent attached, and the agent is the easy part. Get single-repo merge rate stable before you go near it.

Skip autonomous merge permanently, or at least until you have a year of revert-rate data on a specific task class. The human click is not friction; it is the containment property that makes every other risk in this system survivable. The correct path is to measure how often a reviewer merges an agent PR without requesting a single change, and only when a narrow class clears something like 95% over hundreds of PRs should anyone open that conversation — and even then, only for that class.

Skip the multi-agent decomposition. A planner agent, a coder agent, a tester agent and a reviewer agent is a diagram people like and a system that costs several times more for the same output; Anthropic's own guidance reports multi-agent implementations typically consuming three to ten times the tokens of a single-agent approach for equivalent tasks. A single change to a single repository is a short-horizon task with an unambiguous success criterion, which is exactly the shape where one well-prompted agent with ten tools wins. The decision procedure is in multi-agent versus single agent.

What you must not skip: the sandbox with egress denied, per-run branch-scoped credentials, the forbidden-path list, the run journal, and merge-rate instrumentation broken out by task class. Those five are about three of the ten weeks and they are the difference between a system you can defend in a security review and a demo with a git token. If you want this architected and built rather than described, that is what AI product development is for, and the broader stack these components sit in is laid out in the AI product architecture guide.

Build now, or build when you have a merge rate
pick
Build in v1
Five things, none of them impressive in a demo
  • A sandbox with egress denied by default, including from the test process
  • Per-run branch-scoped git credentials with a one-hour TTL and no force-push
  • The forbidden-path list covering migrations, auth, billing, CI and infrastructure
  • A run journal recording every turn, diff, test result, token count and dollar
  • Merge rate and revert rate broken out by task class, from the first PR
Defer until measured
Four things you will be asked for in month one
  • Multi-repository changes — a release-engineering problem with an agent attached
  • Autonomous merge — the human click is the containment property, not the friction
  • Multi-agent decomposition — 3-10x tokens for equivalent tasks on Anthropic's own numbers
  • A fine-tuned model — you have no labelled dataset until reviewers record rejection reasons
The left column is roughly three of the ten weeks and produces nothing you can show a stakeholder. The right column is where most coding-agent budgets go in month two, before anyone has measured a merge rate on a real repository with real reviewers.

Building an AI coding agent: common questions

How much does an AI coding agent cost per pull request?

On the model in this post, about 64 cents in machine cost for a run that opens a PR — 43 cents of Sonnet-class edit turns, 7 cents of CI on GitHub-hosted Linux 2-core at $0.006 per minute, 4 cents of sandbox at published per-second rates, and the rest in planning and gating. Blended across a mix of merged, closed and abandoned runs that is about $1.21 per merged pull request. The human cost on the same run is roughly $19 at $75/hour fully loaded, which makes the machine bill about 3.4% of the total. These are modelled figures with their token counts and rates printed, not measurements of a system I have operated.

What merge rate should I plan for?

Plan for 30-40% across a mixed task queue and treat anything higher as a consequence of good intake rather than a good model. Merge rate varies enormously by task class: bug fixes with a reproduction in well-tested code sit far above the average, and speculative refactors in untested modules sit far below it. On the arithmetic here the crossover with writing the change by hand is roughly 19%, so a system running at 22% is technically working and economically pointless. Break the metric out by task class from the first PR, because the aggregate number hides exactly the information you need.

Should the agent be allowed to merge its own pull requests?

No, and not because of a policy preference. The human click is the containment property that makes every other risk survivable — prompt injection through dependency documentation, a diff that satisfies the tests without satisfying the requirement, a silently disabled flaky test. Each of those becomes a bad branch rather than a bad deploy specifically because merge permission is absent. If you eventually want to revisit it, the prerequisite is hundreds of PRs in one narrow task class merged without a single requested change, plus revert-rate data over months. Merge is also not exposed as a tool at all, so a compromised context cannot reach for it.

Does the agent need a sandbox, or can it just propose diffs?

It needs a sandbox, and the reason is epistemic rather than operational. A diff nobody has executed is a hypothesis. The entire quality mechanism of a coding agent is the loop between a patch and a real test failure with a real stack trace, which is the densest and cheapest correction signal available anywhere in your engineering organisation. Without it the model optimises for plausibility, and plausible diffs are exactly the ones that pass review and break production. Modelled at published per-second rates, a fourteen-minute sandbox at 2 vCPU and 4 GiB costs under four cents.

Which tasks are worth routing to a coding agent?

Bugs with a reproduction in code above your coverage threshold, mechanical changes across many files such as renames and API migrations, and new components that mirror a named existing one. Those three classes have an unambiguous definition of done, a real falsification signal, and a review that is a diff against a known shape. Route nothing that touches migrations, authentication, billing, CI configuration or infrastructure, and reject at intake any task without acceptance criteria — a task the critic cannot check is a task that will consume a review minute and produce nothing.

How long does it take to build one?

About ten engineer-weeks for a two-person team to a v1 you can point at a real repository, plus roughly four engineer-days a month of maintenance. The edit loop is about one week of that. The sandbox is one, repo grounding is two, the merge gate and its critic calibration are two, and evals plus rollout are two. A narrower version — one repository, one task class, six tools, a hard eight-turn cap — is closer to five weeks and is a legitimate way to get a real merge rate before committing to the full build.

Do AI coding tools actually make engineers faster?

The best available randomised evidence says be careful. METR's July 2025 trial found that sixteen experienced open-source developers took 19% longer on 246 real tasks in their own repositories when allowed to use early-2025 AI tools, while estimating afterwards that they had been about 20% faster. That is a study of interactive assistance on mature codebases rather than of an autonomous PR pipeline, so it does not settle the question — but it does establish that self-reported speedup is an unreliable metric, which is why the design here insists on merge rate, review minutes and revert rate as the measurements that count.

Ready to talk numbers?

Twenty minutes, straight to the engineer. No sales rep, no deck.