AI coding

Diff Budgets: Setting Hard Limits on Agent Changes

Antonio Evans8 min read

A code review screen shown as a wide panel.
Key points

Key points

  1. 1A diff budget is a numeric cap on lines added, lines removed, and files touched for one agent task, and it only works when it is machine-checkable.
  2. 2Declare the same number in three places: the system prompt, the task brief, and the repository, so CI can verify the branch against its merge base.
  3. 3Over-editing passes the tests while turning a one-line fix into a rewrite, so path hooks catch it before the edit lands and CI catches the final size.
  4. 4When the agent exceeds the budget, stop the run and report the overage instead of letting it continue, then split the task or escalate to a reviewer.

Why a budget is different from a request

Ask an agent to fix an off-by-one and you often get the bug fixed plus a rewritten function, a renamed variable, and new input validation nobody asked for. One write-up calls this the over-editing problem: the output is functionally correct but structurally diverges from the original code far more than the minimal fix requires, and the author shows a model turning a range(len(x) - 1) correction into a full function rewrite with added None checks and a changed call signature (Coding Models Are Doing Too Much). The important part of that observation is that over-editing is a brown-field failure, so a test suite that only checks behavior cannot see it.

That is why an instruction alone underperforms. A practitioner running autonomous agents found that a plain "only change what the task requires" line "helped a little, and not nearly enough," because from inside the task a rename feels like good work rather than scope creep (How I Stopped My AI Coding Agent's Scope Creep With a Diff Budget). The conclusion is the decision rule for the rest of this article: instructions describe intent, and if you need a guarantee you need a mechanism. A diff budget is that mechanism expressed as numbers.

Over-editing also makes review materially harder. The same analysis notes that reviewers must understand what changed, why, and whether it is safe, and a model that rewrites whole functions makes that job dramatically harder because the code is no longer recognizable (minimal editing). The cost shows up as fatigue. Review effectiveness drops sharply after roughly 200 to 400 changed lines, and median time-to-review climbs with each additional block of lines (Pull Request Best Practices). A separate summary of the research states that more than 400 changed lines is generally considered too large for a single review and that defect-finding falls as size grows (Pull Request Size Matters). A budget turns that human finding into a check that fires before anyone opens the pull request.

Define the budget in three numbers

A diff budget has three numeric parts and one list of paths. Keep the schema boring so both a prompt and a CI job can read it.

FieldWhat it countsFailure it prevents
max_added_linesLines added versus the merge baseSpeculative code, new helpers, unrequested validation
max_removed_linesLines removed versus the merge baseSilent deletion of code the team depends on
max_filesDistinct files with any changeDrive-by edits to neighboring modules and config
allowed_pathsGlob list of writable pathsAny wandering at all, blocked before the first write

You can fold the two line counts into one max_changed_lines if you prefer; the scope tooling that inspired this pattern pairs a file cap with a single changed-line number (diff budget write-up). The tradeoff is precision: a change that removes 200 lines and adds 3 is primarily a deletion, and one combined number hides that. If deletions are the risky move in your codebase, keep the two counters separate and report them separately in CI.

Set the numbers by task type, the way the original author does: bug fixes get a tight budget, feature work gets a wider one, and refactors are their own task type with an explicitly large budget (diff budget write-up). That last rule matters most. You are not against refactoring, you are against refactoring smuggled inside something else, and a separate task type with its own generous ceiling is the honest way to allow it.

The agent's instructions: a rule with a stop condition

Write the budget into the system prompt as a rule the model can check itself, not as a style preference. Vague guidance about minimal edits is easy to rationalize. A number and a stop condition are not.

DIFF BUDGET (hard limit for this task)
- Add at most 40 lines and remove at most 20 lines across the whole diff.
- Touch at most 3 files.
- Edit only paths under src/pagination/ and tests/pagination/.
STOP CONDITION
- Before finishing, count your own diff: added lines, removed lines, files.
- If any count would exceed the budget, do not continue. Stop and report
  the counts you reached, the file or lines that pushed you over, and
  whether the goal is reachable within budget or needs a scope extension.
- Improvements you notice outside the task go in PARKING_LOT.md, not the diff.

Two details carry most of the weight. First, the agent should count its own diff before declaring done, which forces an explicit self-check rather than a hopeful guess. Second, out-of-scope observations get a named destination. The parking lot is the escape valve that makes the fence tolerable, since the agent's drive-by ideas become follow-up tasks instead of surprise changes (diff budget write-up).

Fold the underlying behavioral guidance into the prompt as well. The widely shared Karpathy guidelines tell a model to touch only what it must, avoid improving adjacent comments or formatting, match existing style, and leave pre-existing dead code alone unless asked (karpathy-guidelines SKILL.md). Their test is worth pasting verbatim: every changed line should trace directly to the request. More generally, agent prompts need explicit scope and stop conditions rather than conversational nudges, because an unset stop condition is exactly what lets a run drift (What is Agent Prompt Engineering?).

The task brief: one number per task, set by the planner

The system prompt holds the rule; the task brief holds the number. Put it in the task spec so the planner sets it when the task is created, not the agent when it starts editing.

{
  "id": "task-0412",
  "goal": "Fix off-by-one in pagination helper",
  "scope": {
    "allowed_paths": ["src/pagination/**", "tests/pagination/**"],
    "max_files": 4,
    "max_changed_lines": 80
  }
}

This follows the scope block used in the original write-up, which pairs allowed paths with a file cap and a changed-line cap and sets them at planning time based on task type (diff budget write-up). Placement matters. When the planner writes the number, a human has effectively approved it during planning. When the agent writes its own number, it can pick a large one and the check becomes theater.

A cleaner workflow separates product design from technical design before any code is written, so the task arrives with a verifiable definition of success and a known surface area (AI That Works on product and technical design). The budget is one more field in that technical design. That discussion also notes that giving the agent a measurable definition of success is what lets it propose improvements over time rather than guess at what "done" means, which is the same reason a numeric budget beats a prose request.

You also want a race-free workspace so the budget applies to one task's changes only. Git worktrees let several agents work on separate branches from one repository, each in its own directory while sharing the same history (How Git Worktrees Changed My AI Agent Workflow). Without that isolation, uncommitted work from another task leaks into the diff and inflates the counts through no fault of the agent under test.

Enforce it in CI against the merge base

Prompts persuade; CI enforces. The reliable measurement is a three-dot diff against the merge base, which compares your branch to where it forked from the target and excludes changes that landed on the target since.

BASE=$(git merge-base "origin/$TARGET_BRANCH" HEAD)
git diff --numstat "$BASE"...HEAD | awk '
  { files += 1; added += $1; removed += $2 }
  END { printf "files=%d added=%d removed=%d\n", files, added, removed }
'

Compare the printed numbers to the budget and exit non-zero on any violation. A ready-made GitHub Action does the same shape of check by reading additions and deletions from the pull-request files API, failing when the diff crosses a line threshold, and supporting ignored paths for docs, lockfiles, and generated code plus a bypass label for deliberate exceptions (pr-size-check). Two design choices there are worth copying. Ignored paths keep lockfile churn from blowing a budget, and the bypass label is exact and case-insensitive, with the workflow listening for both labeled and unlabeled events so a required check recalculates when the exemption changes.

A decision table for what the check should do:

SituationCI responseWhy
Under every limitPassBudget respected
Over one limit by a small marginFail with counts namedAgent or human trims
Over only on ignored pathsPassLockfiles and generated code are not review work
Labeled size-exemptPass, visibly bypassedDeliberate, auditable, human-approved
Same task fails repeatedlyEscalateTask is likely too big and needs splitting

Keep the bypass path narrow and auditable. A budget that can be waved through silently is not a budget, it is a suggestion with extra steps.

Stop the overage before the edit lands

CI catches the final diff, but by then the agent has spent the run and the tokens. A pre-edit hook catches the cheap failures before a write happens. The pattern that makes this work is an exit code that blocks the tool call plus feedback on stderr, which the agent then reads.

import fnmatch, json, os, sys
call = json.load(sys.stdin)
if call.get("tool_name") not in ("Edit", "Write"):
    sys.exit(0)
scope = json.load(open(os.environ["TASK_SPEC"]))["scope"]
target = os.path.relpath(call["tool_input"]["file_path"], call.get("cwd", os.getcwd()))
if any(fnmatch.fnmatch(target, p) for p in scope["allowed_paths"]):
    sys.exit(0)
print(f"BLOCKED: {target} is outside this task's scope. Stop and request a "
      f"scope extension with a one-line reason, or append the idea to "
      f"PARKING_LOT.md and keep going.", file=sys.stderr)
sys.exit(2)

The hook blocks the call and tells the model why and what to do instead, so the agent hits a signposted wall rather than a silent failure (diff budget write-up). Two honest caveats. The hook only covers the file-editing tools, so an agent with shell access can still write through a shell command, which is why the post-run check above exists. And path hooks enforce scope, not size, so you still need the CI comparison to catch a legal path used for far too many lines.

Hooks are also where the repository default belongs. Put the budget in a checked-in config file that both the hook and CI read, so there is one number rather than a prompt copy that drifts. The lesson from teams that let instruction files grow is that a giant context file rots, and standardizing what gets checked, such as flagging deviations from the plan, does more for trust than a longer set of instructions (AI That Works on product and technical design).

When the agent exceeds the budget

The right behavior on overage is to stop and report, never to push through and hope a reviewer sorts it out. A good overage report names the counts, the file that pushed it over, and whether the goal is reachable within budget. That hands a reviewer or planner a clean decision instead of a twelve-file diff to reverse-engineer.

Use a short decision tree. If the excess is small and the goal is close, return the task to the agent with a trim instruction and the exact number to cut. If the excess is driven by a genuinely larger change, raise the budget on the task and record why. If the task keeps blowing the same budget, the task is too big: split it into stacked changes, the technique teams use for large features by breaking them into sequential, independently reviewable pull requests (Pull Request Best Practices).

Make the escalation concrete. A reviewer given a named excess ("18 extra lines in pagination/index.ts, 2 files over the cap") can approve an extension in seconds. A reviewer handed a sprawling diff has to reconstruct intent from scratch, which is where review quality collapses. The general Git discipline helps here too: work from a known state, commit verified checkpoints as you go, and let the agent draft the commit message without staging anything itself (Git workflow for agent-assisted development). Small, budgeted commits make a bad change trivial to revert without also reverting an unrelated refactor that a later task now depends on.

If agents run at volume across many repositories, the enforcement step is worth putting behind one internal service rather than copying a hook into every project. That is the shape of a platform play: a single review endpoint that reads the budget config, checks the diff, and returns a pass or a named overage. Teams building that often standardize on one place for model access so the agent runtime does not carry per-provider plumbing; Quanta Inference puts text, image, video, audio, and embedding models behind one OpenAI-compatible API key, while the diff check stays a plain script in CI.

FAQ

FAQ

What exactly does a diff budget measure?

It caps three things in one task: lines added, lines removed, and files touched, usually measured against the branch merge base. Some setups fold the line counts into a single changed-line number, but keeping added and removed separate catches large silent deletions. The budget is separate from tests, because over-editing passes tests while still making review hard (minimal editing).

Why not just tell the agent to make small changes?

Because an instruction is a request, not a guarantee. A practitioner found that a "only change what the task requires" line helped only a little, since the agent does not experience its cleanup as unrelated (diff budget write-up). The fix is a mechanism: a numeric budget in the task spec, a pre-edit path hook, and a CI check that fails on overage.

Where should the budget number live?

In exactly one place, a checked-in repository config, with the prompt and task brief referencing it. The system prompt states the rule and stop condition, the planner writes the per-task number into the task spec, and CI reads the same config so it cannot drift. The original design puts the number in the task's scope block, set by the planner based on task type (diff budget write-up).

What limit is right for a bug fix?

Start tight and calibrate from real data. Bug fixes get a small cap, feature work a wider one, and refactors a separate, explicitly large budget so refactoring is not smuggled inside a fix (diff budget write-up). Since review effectiveness falls after a few hundred changed lines, a budget well under that ceiling keeps every agent change comfortably reviewable (Pull Request Size Matters).

What should happen when the agent goes over?

Stop and report, then escalate rather than continue. A good report names the counts and the lines that pushed it over, so a reviewer can grant a scope extension quickly or send the task back to be trimmed. If the same task keeps failing, split it into stacked, independently reviewable changes rather than raising the budget again (Pull Request Best Practices). Teams that want the check designed across many repositories can hand it to an expert; Quanta Experts reads repo context and returns the finished policy and scripts.

Sources

Sources

  1. Coding Models Are Doing Too Much - wh
  2. How I Stopped My AI Coding Agent's Scope Creep With a Diff Budget
  3. Pull Request Best Practices: A Complete Guide (2026)
  4. Pull Request Size Matters
  5. skills - karpathy-guidelines - GitHub
  6. What is Agent Prompt Engineering? - by Paolo Perrone
  7. Why Your AI Coding Agent Keeps Writing Bad Code (It's Not the Prompt)
  8. How Git Worktrees Changed My AI Agent Workflow | Nx Blog
  9. SpecterOps/pr-size-check
  10. AI agent coding for ESP-IDF workshop - Optional: Git workflow for agent ...
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles