« Phase 15 README · Warmup · Hitchhiker's · Deep Dive · Principal Deep Dive · Staff Notes

Phase 15 — Core Contributor Notes: How Real Coding Agents Apply Edits

This is the maintainer's-eye view of the real tools — Aider, Claude Code, Codex, Cursor — and the SWE-bench harness, not our miniature. Where the lab injects a planner/patcher and edits a dict[str, str], the real systems run a tool-use loop over a git working tree, feed apply failures back to the model, and score against a Dockerized test suite. The interesting engineering is in the edit format and the seams around it. Where I describe a pattern rather than a verified internal, I say so.

The edit format is where these tools actually differ

Everything user-visible converges — read the repo, plan, edit, run tests — but the edit format is the real design axis, and each tool made a different bet:

  • Aider exposes edit formats as a first-class, per-model setting: whole-file, search/replace blocks (its diff format), unified diff (udiff), and fenced variants. The reason it is configurable is the sharp edge itself — different models are reliable at different formats, and Aider tunes the format to the model. Its search/replace block is a fenced SEARCH half and a REPLACE half against a named file; the applier finds the search text and swaps it. This is the format our lab implements, and it is Aider's workhorse.
  • Claude Code's file-editing tool is exact string replacement: the old_string must match the file and must be unique, or the edit errors and asks for more surrounding context. That is precisely the lab's "exactly one match, or fail" rule, shipped in a production tool — the uniqueness requirement is not a lab simplification, it is the real safety contract. Claude Code also enforces a read-before-edit invariant: you cannot edit a file the agent has not read in this session, which prevents editing a stale mental model of the file.
  • Codex / OpenAI lean on a structured apply_patch tool with a bespoke envelope (begin/end markers, per-file Update/Add/Delete sections, @@ context hunks with +/- lines). The envelope exists to make the model's output reliable — a rigid, easy-to-emit grammar reduces malformed patches — while the applier matches context with a fuzzy fallback for drift.
  • Cursor popularized a speculative / fast-apply split: the frontier model emits a terse edit sketch with // ... existing code ... elision markers, and a separate, cheaper "apply" model rewrites the full file merging the sketch. This trades one extra model call for application speed and robustness — the frontier model never has to reproduce unchanged code verbatim.

The lesson a committer internalizes: the format is chosen to minimize the model's format-error rate and the applier's mismatch rate, per model. There is no universally best format; there is the one your model emits reliably and your applier can land without corrupting the file.

Whitespace and uniqueness are the two failures every applier fights

Two gotchas dominate real bug reports:

  • Whitespace / indentation drift. The model's old has four spaces; the file has a tab (or the reverse). A byte-exact match fails. Real appliers add tolerance — normalizing leading whitespace, trying an indentation-insensitive match, or re-anchoring on a unique inner line — because a strict applier rejects too many correct edits. Our lab is deliberately byte-exact (str.count / str.replace), which is the honest teaching baseline; production tools layer forgiveness on top, and every bit of forgiveness is a bit of "did it land where I meant?" risk they must bound.
  • Ambiguity. When old appears more than once, the tool cannot guess. Claude Code errors and asks for more context; Aider's block model relies on enough surrounding lines to be unique. This is the lab's count > 1 → fail rule, and it is not optional in the real tools either — guessing the first match is the classic silent-corruption bug.

Both push the same design pressure: the model must emit enough context to be unique but not so much that whitespace drift breaks the match. That tension is why edit formats keep evolving.

The reflection loop: apply failures feed back to the model

A committer's non-obvious insight: the apply step is not fire-and-forget. When a search/replace block does not apply — not found, ambiguous, whitespace mismatch — Aider (and the pattern generalizes) feeds the specific failure back to the model and asks it to re-emit the block, usually with more context. This is the same failures-back mechanism the lab's loop uses, but at the edit granularity rather than the verify granularity: the model gets a second chance to produce a landable patch before any test even runs. Our lab collapses this by scripting a patcher that recovers on the next iteration; real tools have both an inner (apply-retry) and an outer (verify-retry) loop.

Rollback is git, and that shapes the ergonomics

Aider auto-commits each successful edit to git with a generated message. That is the production form of the lab's snapshot/restore: git is the rollback substrate, and every landed edit is a revertable commit. The consequence a maintainer lives with: it produces a granular, sometimes noisy history (hence --no-auto-commits and squash-on-merge conventions), but it makes "undo the agent's last change" a git reset rather than a prayer. Claude Code operates on the working tree directly and leaves committing to you and your hooks. Either way the invariant is the lab's: an edit is revertable, so a bad one is an observation, not a catastrophe.

Context is a repo map, not the whole codebase

The lab hands the patcher the Repo; real agents cannot fit a codebase in context, so they build a repo map. Aider's is the well-known one: it parses the project with tree-sitter to extract symbols and their references, then ranks what to show by graph centrality (a PageRank-style ranking over the symbol-reference graph), capped to a token budget. This is the retrieval layer (Phase 05/06) specialized to code — the agent sees a concise, relevance-ranked skeleton, not every file. The sharp edge: the map's token budget competes with everything else in context, so tuning what to include is a real, ongoing engineering problem the lab abstracts away entirely.

Reusable commands, skills, and the always-on spec

The lab's CommandRegistry (name + required_params + build → Spec) is faithful to how the tools ship reusable workflows. Claude Code slash-commands are markdown files under .claude/commands/ with argument substitution ($ARGUMENTS, positional $1), skills are packaged capabilities loaded on demand, and subagents are named agents with their own prompt and tools you delegate to. CLAUDE.md (and the open AGENTS.md convention) is the always-on repo spec the agent reads every session. The lab's "missing required param is a structured error" is the same discipline these tools need: a command that silently builds a wrong spec is worse than one that refuses.

SWE-bench: the verify loop as a benchmark

The lab's verify is a benchmark in miniature. SWE-bench gives the agent a real GitHub issue and the repo at the buggy commit; the predicted patch is applied inside a per-instance Docker image, and acceptance requires two test sets to hold: the FAIL_TO_PASS tests must flip from failing to passing (the bug is fixed) and the PASS_TO_PASS tests must stay green (nothing regressed). That second set is the part people forget — it is the regression guard that makes the reward honest, and it is why "the new test passes" is not sufficient. SWE-bench Verified is the human-validated subset that removed under-specified or broken instances. The whole harness is the plan→patch→verify loop scored on real code, which is exactly why it is the field's ground truth.

What our miniature deliberately simplifies

  • A dict[str, str] repo with snapshot/restore, not a git working tree with auto-commits and a reflog. Same invariant (edits are revertable), no real VCS.
  • Byte-exact search/replace only (plus whole-file), not the production zoo of formats, whitespace-tolerant matchers, fuzzy diff fallbacks, and speculative fast-apply.
  • Static ast + string checks, not a Dockerized test runner with FAIL_TO_PASS / PASS_TO_PASS sets — the harness never executes agent code, on purpose.
  • An injected planner/patcher instead of a real tool-use loop with a tree-sitter repo map, retrieval, and an apply-retry reflection loop.
  • An allowed_paths set instead of a real permission system (allow/deny/ask), protected paths, and an ephemeral network-isolated sandbox.

Know the edit format is the real design axis, that uniqueness and whitespace are the two failures every applier fights, that git is the rollback substrate, and that PASS_TO_PASS is the honest half of the reward — and the real tools' docs read as confirmation rather than surprise.