Reasonix: Byte-Exact SEARCH/REPLACE with Edit-Gate Review
Reasonix enforces a strict byte-for-byte SEARCH/REPLACE protocol: the SEARCH block must match the file content exactly, empty SEARCH creates new files, and every edit passes through a review gate before landing on disk. No fuzzy matching, no silent corrections — just exact semantics and predictable outcomes.
Why It Belongs in a File-Editing Blog
Reasonix is a DeepSeek-native coding agent engineered around DeepSeek's prefix-cache mechanic. That means every layer — including file editing — is designed for stability. The editing protocol is not an afterthought or a cascade of forgiving fallbacks. It is a deliberate choice to prefer loud failure over silent corruption.
In a landscape where many agents pad their edit engines with 4, 7, or 9 matching layers, Reasonix draws the opposite line: one exact match, period. If the SEARCH block does not appear byte-for-byte in the current file, the edit is rejected with a clear status code. The model gets precise feedback and retries with better context. This is the DeepSeek-native approach: lean on the model's ability to self-correct rather than on the framework's ability to guess.
The SEARCH/REPLACE Protocol
The core of Reasonix's editing engine lives in
src/code/edit-blocks.ts. It defines a simple, parseable
block format that the model emits in its response text:
path/to/file.ts
<<<<<<< SEARCH
existing code to match exactly
=======
new code to replace it with
>>>>>>> REPLACE
The parser uses a single regular expression to extract these blocks from
the model's output. The regex anchors each block with
^ (line start) and the m flag so that a file
containing the literal text <<<<<<< SEARCH
does not accidentally parse as a real edit block.
| Condition | Status | Behavior |
|---|---|---|
| SEARCH non-empty, exact match found | "applied" |
First occurrence replaced on disk |
| SEARCH empty, file does not exist | "created" |
New file written with REPLACE content |
| SEARCH non-empty, no match in file | "not-found" |
Rejected; model must provide better context |
| SEARCH empty, file already exists | "not-found" |
Empty SEARCH only creates new files |
| SEARCH non-empty, file missing | "file-missing" |
Model told to create with empty SEARCH instead |
| Path escapes rootDir | "path-escape" |
Refused on safety grounds |
| Filesystem operation throws | "error" |
IO or permission error |
No fuzzy matching, ever
There is no whitespace-normalized matcher, no anchor fallback, and
no diff rescue pass. The only leniency is line-ending adaptation:
the engine normalizes \r\n vs \n to match
the target file's actual line endings. That is it. "A silent wrong
edit beats a missing one."
Each edit replaces only the first occurrence of the SEARCH text. If the same string appears multiple times, the engine refuses to guess which one to change — the model must include more surrounding context to disambiguate. Auto-expanding to replace-all is deliberately avoided as a footgun.
For whole-file rewrites, Reasonix provides
toWholeFileEditBlock(), which reads the existing file
content as the SEARCH block (or empty string if the file does not
exist) and wraps the new content as REPLACE. This lets the model emit
a single block that covers the entire file when needed.
The Edit-Gate Review Mechanism
Reasonix separates the parsing of edit blocks from their application. Blocks are parsed from the model's response text first, then presented through an interactive review gate:
| Command | Behavior |
|---|---|
/apply |
Confirms pending edits; writes to disk after user approval |
| Auto-apply | When configured, edits land immediately without prompting |
/undo |
Restores pre-edit snapshots; created files are deleted |
/history |
Shows the sequence of applied edits |
/show |
Displays before/after diff for a specific edit |
Before any edit is applied, Reasonix captures a
snapshot of each affected file's content. The snapshot
is de-duplicated by path — one "before" per file even when multiple
blocks target the same file. These snapshots power /undo:
if a file was created by an edit, undo deletes it; if it existed
before, undo restores the exact pre-edit content.
Each edit produces a diff display showing the change with line numbers,
so the user sees exactly what changed before approving. The
edit_file tool reports both the character-length delta
(e.g., 45→120 chars) and a rendered diff block.
First-occurrence only
The engine replaces only the first occurrence of SEARCH text. If the same string legitimately appears in several unrelated places, the model must emit separate blocks with more surrounding context for each target. This prevents accidental mass replacement.
Tool-Call Repair Pipeline
Before edits are even parsed, the model's tool-call output passes through a four-stage repair pipeline. This ensures the edit blocks that reach the parser are structurally sound:
| Stage | What It Does | Problem It Solves |
|---|---|---|
| flatten | Converts schemas with >10 leaf params to dot-notation; re-nests after dispatch | DeepSeek drops args on deeply nested or wide schemas |
| scavenge | Regex + JSON parser sweeps reasoning_content for forgotten tool calls |
Model emits tool JSON inside reasoning but forgets the tool_calls field |
| truncation | Detects unbalanced JSON; repairs by closing braces or requesting continuation | Model output gets cut off mid-JSON by token limits |
| storm | Suppresses identical (tool, args) tuples within a sliding window; injects a reflection turn | Model loops, emitting the same edit call repeatedly |
The pipeline runs in order: scavenge → truncation → storm. Schema flatten runs at loop construction time, not per-turn. The scavenge stage bounds its input to 100 KiB to prevent ReDoS on adversarial input. The storm breaker tracks recent calls per session and clears read-only entries after mutating calls, so a post-edit re-read is not flagged as a repeat.
Sandbox and Binary Protection
Reasonix enforces strict filesystem boundaries and defends against accidental binary file corruption:
| Defense | Mechanism |
|---|---|
| Path sandboxing | Every path is resolved against rootDir; paths escaping the root are refused with "path-escape" status |
| Binary detection | Extension blocklist (common binary types) plus NUL-byte sniff in the first 8 KiB of file content |
| Outline mode | Files exceeding 512 KiB are shown as structure outlines instead of full content, with a configurable threshold |
| Hard size cap | Files above 32 MiB are refused entirely — outline mode would have to slurp the whole file to scan it |
| Session approval | Read access to directories outside the root requires explicit user approval, cached for the session |
The sandbox check uses resolve() to normalize
.. segments, then verifies the resolved target starts with
the normalized root path. The platform-aware separator (Windows
\, POSIX /) ensures correct comparison on
both systems. Snapshot restore also runs the same escape check, so
/undo cannot write outside the sandbox either.
Layered binary defense
Binary protection works on two levels: the extension blocklist
catches known binary types immediately, and the NUL-byte sniff
catches mislabeled files (e.g., a binary with a .txt
extension). UTF-16 files are accepted as a known false positive
since they are rare in source code.
How It Compares
| Repo | Main editing idea | Failure strategy |
|---|---|---|
| Cline | Custom search/replace plus layered fallbacks | Try more forgiving matchers until something lands |
| Codex / Claude Code | Patch-oriented mutation surface | Patch parsing plus runtime validation |
| OpenCode | Exact edits plus a large fallback stack | Nine matching layers before giving up |
| Reasonix | Byte-exact SEARCH/REPLACE, no fallback | Return precise status codes; model retries with better context |
| ADK-Rust | Provider-native wrappers | Return precise errors and let the caller or model retry |
What I Would Steal from It
Exact match is a feature, not a limitation
Reasonix proves that a single, unforgiving exact matcher works when paired with precise failure signals. The model learns to include more context rather than the framework learning to forgive less context.
Edit-gate with snapshots is the right UX
Capturing before/after snapshots and gating edits behind
/apply gives the user a clean undo path. The
de-duplication by path prevents redundant reads, and the diff
display makes every change auditable before it lands.
Repair pipeline catches model failures before they hit the editor
Scavenge, truncation repair, and storm suppression form a defense layer that catches the model's structural mistakes before they become edit-block parsing errors. This keeps the editing surface clean without adding matching complexity.
Sandbox enforcement at the boundary
Path escape checks run at both apply time and snapshot restore time, with the platform-aware separator handling Windows and POSIX correctly. Binary detection combines extension blocklists with content sniffing — simple, layered, effective.