<!-- Automatically generated by Staverse -->
<!-- Author: NeaByteLab | Date: 2026-07-23T20:02:32Z | Title: Anchored Editing for LLMs Using V4A Diff | Source: https://neabyte.com/articles/anchored-editing-for-llms-using-v4a-diff.md -->

Line numbers fail the moment a model edits a file, because the next patch points at old spots while the text below has moved.

V4A removes that failure by tying each edit to the words around it, rather than to a number that goes stale between turns, so the patch still finds its correct place even after the whole file shifts under an earlier change made in the same session.

This format marks a hunk with the text it sits beside instead of a fragile position, so a patch written on one turn still lands in the right spot after later edits move the code, which is what makes it fit a model that edits files across many turns.

Small as it sounds, the idea is useful, an edit points at the nearby text, so it keeps working while the file around it changes.

> [!NOTE]
> The engine behind this article is open source on GitHub at [NeaByteLab/V4A-Diff](https://github.com/NeaByteLab/V4A-Diff) under the MIT license, where the parser, matcher, and structured diff output live as real code to read, run, and adapt freely.

## Formats the Industry Uses

Most coding agents settle on one of a few edit formats, and each one trades effort against safety in its own way, so it helps to see where an anchored format sits next to the ones that a tool such as Aider ships with today for the popular models.

Returning a full file is the plainest option, since the model sends a whole updated copy of everything that it touches, which never gets lost on its own location but burns tokens on unchanged lines and grows slow once the edited file gets large.

Sending a search and replace block instead carries only the changed region, marked like a git conflict for a tool to then swap.

```
mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```

Placing the file path inside the code fence is a variant for models that mishandle the plain layout, but the idea is the same.

Unified diff output leans on the shape most engineers already read, since it trimmed the lazy edits that some models fell into, yet it still carries hunk headers with line ranges that drift once the file moves under an earlier edit in a session.

```diff
--- mathweb/flask/app.py
+++ mathweb/flask/app.py
@@ ... @@
-class MathWeb:
+import sympy
+
+class MathWeb:
```

Aider documents these formats in its [edit formats guide](https://aider.chat/docs/more/edit-formats.html), covering whole, diff, diff-fenced, and udiff, then picks the one that fits each model, while an anchored format keeps the token savings and drops the line ranges a unified diff still carries.

## Anchors Beat Line Numbers

An anchor is a short piece of the surrounding text, written on an @@ line, that tells the engine where a hunk belongs, and a bare @@ on its own marks the start of the file, which keeps the address easy to read for both a model and a human reviewer.

Written as text and never as a hardcoded number, the location keeps a hunk valid after the file grows or shrinks above it here.

Every hunk carries three kinds of lines, a space prefix for context that stays, a minus for a line that leaves, and a plus for a line that arrives, so the shape reads like a normal diff while the actual location comes from the anchor text above.

```
*** Begin Patch
*** Update File: /src/config.ts
@@ function add(a, b) {
 function add(a, b) {
-  return a - b
+  return a + b
 }
*** End Patch
```

## Matching That Handles Drift

Matching does the real work, because a model rarely repeats a line exactly, so the engine tries a fixed order of passes here.

First it runs an exact compare, then a trimmed one, then a pass that collapses runs of whitespace, and last a pass that folds fancy quotes, long dashes, and unusual spaces into plain ASCII, so the cleanest match still wins ahead of a rougher copy.
Each step that loosens the rules adds to that fuzz score, which keeps a clean exact match ranked well ahead of a rough one here.

When an anchor could point at two identical lines, the engine does not guess, it raises an error that asks for more surrounding context, so a patch never lands in the wrong place without warning when the target line was not unique enough to place.

## A Single Call for CRUD

One function covers the file lifecycle, since apply takes the source text, the diff text, and a mode of update, create, delete, or move, then returns the patched text next to a structured record of what changed on every single line of the result.

Nothing reads or writes the disk, because the engine takes text and returns text, and it leaves file access to code that calls it.

Portability comes from that same choice, since the identical code runs in a browser, a CLI, or a server with no change, and the create mode builds a new file from plus lines alone while the delete mode returns an empty result and lists what left.

Renaming rides on the move mode, which applies edits at a new path or renames a file cleanly when no edits are attached at all.

```ts
import V4A from '@neabyte/v4a-diff'

// Update an existing line by anchoring to the text around it
const result = V4A.apply(
  'function add(a, b) {\n  return a - b\n}',
  '@@\n function add(a, b) {\n-  return a - b\n+  return a + b\n }'
)

console.log(result.text)
// function add(a, b) {
//   return a + b
// }
```

## Structured Diff and Rollback

Structured output carries more than patched text, because each line comes back tagged as add, delete, or equal, paired with its old and new line numbers, which gives an editor what it needs to render a diff view without scanning the whole file again.

Original source comes back in the same result too, so a rollback reads a single field instead of tracking the old text by hand.

A refine step optionally adds character level detail, since it pairs a delete line with the add line next to it and runs a grapheme aware compare, so a diff view can mark the exact characters that changed inside a single edited line of the code.

```ts
// Each entry knows its type and its position in old and new text
result.diff
// [
//   { type: 'equal',  value: 'function add(a, b) {', oldLine: 1, newLine: 1 },
//   { type: 'delete', value: '  return a - b',       oldLine: 2, newLine: null },
//   { type: 'add',    value: '  return a + b',       oldLine: null, newLine: 2 },
//   { type: 'equal',  value: '}',                    oldLine: 3, newLine: 3 }
// ]
```

## Tradeoffs Worth Knowing

Efficiency is the headline gain here, since a patch sends only the changed lines plus a short anchor, rather than a full file copy or a wide block of repeated search text, so the token cost per edit stays low across a long editing loop of many turns.

Bundling a structured diff into the result adds a second gain, because the add, delete, and equal tags arrive with old and new line numbers straight from apply, so an editor renders a diff view without a separate diff package or its own parsing pass.

Dropping that extra tooling keeps the surface small, since one call gives back the patched text, the tags, and the source at once.

Weakness shows up on smaller models, because the anchor and hunk shape demand a precise reply, so a light model often drifts unless the prompt spells the rules out well, while a frontier model trained on this style produces clean patches on its own.

## Built for Tool Calling

The format targets one job, letting a language model edit real files through tool calls without breaking on the line drift that plain diffs create, so the anchors keep a patch valid across turns, the fuzzy matching absorbs the small errors a model makes when it repeats code back, and the ambiguity check turns a quiet wrong edit into a clear error that asks for context.

Ready made schemas for both common tool calling formats ship in the repo, so connecting the engine to an agent is a copy step.

Pointing an edit at the text around it, rather than at a line number that goes stale, is a small change that pays off on every turn of a long editing loop, because the file keeps moving under the model while the anchor holds the patch on target.

<!-- CITATION POLICY -->
<!-- Any use, including AI training data, must cite the original source, author, and date. -->
<!-- Author: NeaByteLab | Date: 2026-07-23T20:02:32Z | Title: Anchored Editing for LLMs Using V4A Diff | Source: https://neabyte.com/articles/anchored-editing-for-llms-using-v4a-diff.md -->
