Writing · 2026

The Evolution

Aider: stop asking the model to count lines

Problem. A standard diff includes headers such as @@ -80,2 +80,2 @@. The model has to get the code right and calculate line positions and lengths. Aider found that this bookkeeping caused avoidable failures. (Aider)

Solution. In December 2023, Aider described a simplified unified diff that drops the numbers and uses surrounding code to locate the edit: (Aider)

diff
--- retry.py+++ retry.py@@ ... @@ def retry_download(status):-    return status == 500+    return status in {429, 500, 503}

The applier reconstructs two pieces of text from this patch:

python
# Context lines + removed linessearch = """def retry_download(status):    return status == 500"""# Context lines + added linesreplacement = """def retry_download(status):    return status in {429, 500, 503}"""

It then searches for the first and substitutes the second. If a hunk does not match, Aider can adjust relative indentation, split it into smaller hunks, or vary the surrounding context. Those fallbacks were already present in its 2023 design. (Aider)

Cursor: give the application step its own model

Problem. A chat response often describes a change without producing something a text editor can directly apply:

python
class Client:    # ... existing methods ...    def retry_download(self, status):        return status in {429, 500, 503}

Replacing the original class with this snippet would delete its other methods. Appending it would create another class definition. Someone still has to merge the suggestion into the file.

Solution. Cursor’s May 2024 Editing Files at 1000 Tokens per Second described a separate, trained apply model. It receives the original file, conversation, and suggested code, then generates a complete updated file. (Cursor)

Cursor · apply model
Original file
class Client:
    retry_upload(...)
    retry_download(...)
Suggested change
class Client:
    # ... existing methods ...
    retry_download(...)  # new body
Apply model
Complete file
class Client:
    retry_upload(...)        # preserved
    retry_download(...)      # replaced

Full-file generation creates a second problem: latency. Cursor addressed that with speculative edits, using existing code to supply likely continuation tokens instead of generating every unchanged token from scratch. It still produces a full file; the optimization makes reproducing unchanged sections cheaper. (Cursor)

Codex: make the target explicit in the patch

Problem. A small replacement may appear in several places—even under the same method name:

python
class UploadClient:    def should_retry(self, status):        return status == 500class DownloadClient:    def should_retry(self, status):        return status == 500

Searching for return status == 500 is ambiguous. Including def should_retry still does not distinguish the two.

Solution. The apply_patch format allows explicit file operations and additional text anchors. Here, the class header directs the search toward the download implementation: (OpenAI Developers)

diff
*** Begin Patch*** Update File: retry.py@@ class DownloadClient:     def should_retry(self, status):-        return status == 500+        return status in {429, 500, 503}*** End Patch

The tool searches past class DownloadClient:, then locates the method and old return statement. The anchor helps locate the edit without becoming part of the replacement. This is text matching, not an AST lookup. (OpenAI Developers)

OpenAI also addressed the generation side: its April 2025 GPT-4.1 guide states that the model had been extensively trained on the recommended format. The protocol was something the model had practiced producing, rather than an unfamiliar schema introduced only in a prompt. (OpenAI Developers)

Pi: tolerate small copying errors

Problem. The model copies the right line but leaves out its trailing spaces:

python
old    = "    return status == 500\n"actual = "    return status == 500  \n"assert old != actual

A strict search fails even though the intended location is clear.

Solution. Pi’s January 2026 fuzzy-edit change tries exact matching first, then a normalized comparison that handles trailing whitespace and selected Unicode differences. It does not simply strip all leading indentation. (GitHub)

For the whitespace case, the idea reduces to:

python
# Illustration of the trailing-whitespace rule, not the full implementation.def matching_view(text):    return "\n".join(line.rstrip() for line in text.split("\n"))assert matching_view(old) == matching_view(actual)

The comparison can now find the target. Duplicate detection still matters: normalization must not turn two possible locations into permission to choose one arbitrarily. Pi’s change includes duplicate checks after normalization. (GitHub)

Leading indentation is treated differently because these two blocks do different things:

python
if failed:    retry()    notify()  # Only on failure.if failed:    retry()notify()      # Always.

OpenCode: try several ways to recover the actual target

Problem. Trimming line endings is not enough when the mismatch is inside a line:

javascript
// The model's oldString:"return status === 500;"// The actual file text:"return  status  ===  500;"

Solution. OpenCode’s edit path runs candidate-generating matchers in order. For this one-line example, the relevant path is:

edit · matcher chain
  1. SimpleReplacerthe strings differfails
  2. LineTrimmedReplacertrimming the ends leaves the internal spacesfails
  3. BlockAnchorReplacerthis is not a multiline blockskips
  4. WhitespaceNormalizedReplacerfinds the corresponding text in the filematches

The important detail is what the matcher returns: the actual substring from the file, not the model’s imperfect copy. The replacement engine can then replace that substring. Unless replaceAll is requested, it checks that the returned substring occurs only once.

Other fallbacks are broader. The block matcher uses first and last lines as anchors, then compares the interior for similarity. That can recover more edits, but matching similar text does not establish whether the differences are safe to overwrite.

DeepSeek Harness: reject edits based on an outdated read

Problem. Sometimes the model copied the old code perfectly. The file changed afterward.

python
# What the model read:def retry_download(status):    return status == 500# What a developer subsequently saved:def retry_download(status):    audit(status)    return status == 500

A permissive whole-function replacement could accept the old block as “close enough” and remove audit(status).

Solution. DeepSeek Harness offers an optional fs-observation-policy. It records which file version the session observed and supplies that version as a condition for later edits. A stale version produces FS_STALE_VERSION, with instructions to read again.

fs-observation-policy
  1. Agent reads retry.pyobserved version v7read
  2. Developer adds audit()current version v8changed
  3. Agent submits old editthe edit is now staleFS_STALE_VERSION
  4. Agent reads retry.py againre-observe the current contentread
  5. Agent rebuilds its editagainst the new contentapply

This check is separate from locating the replacement text. The old return statement may still match exactly; the policy nevertheless requires the agent to observe the changed file before editing it. The protection applies when the policy is installed.

After re-reading, the intended change can preserve the new logging call:

diff
 def retry_download(status):     audit(status)-    return status == 500+    return status in {429, 500, 503}

References

  1. Unified diffs make GPT-4 Turbo 3X less lazy | aider aider.chat
  2. Editing Files at 1000 Tokens per Second · Cursor cursor.com
  3. GPT-4.1 Prompting Guide developers.openai.com
  4. feat(edit): add fuzzy matching for trailing whitespace, quotes, dashes, and spaces by dannote · Pull Request #713 · earendil-works/pi github.com