Tools and Hashline Edits
On this page
kli ties every edit to the exact lines the model last read. If those lines no longer match what is on disk, the edit is refused and nothing is written. This page explains how that works and why it is the default.
What a read returns
When kli reads a file, it does not hand the model raw text. Each line comes back prefixed with an anchor: the line number, a short content hash, and the line itself, in the form LINE:HH|content. A read of a three-line file looks like this:
1:a3|def greet(name):
2:7f| return f"hello {name}"
3:00|The number is the line's position. The HH is two lowercase hex digits, a fold of an FNV hash over that line's raw text. It is short on purpose: it is a fingerprint of the line's content, not a checksum you read. Two lines with the same text get the same hash; change one character and the hash changes.
The model uses these anchors to refer to lines when it edits. An anchor names both where a line is and what it contained at read time.
What an edit is
An edit in kli is a patch made of operations against anchors, not a new copy of the file. The patch groups operations under a file path and addresses lines by anchor. To replace lines 1 through 2 of the file above, the patch carries the anchors 1:a3 and 2:7f together with the replacement text. To insert after a line, it names that one line's anchor. To delete a range, it names the start and end anchors.
Every anchor in the patch repeats the hash the model saw. That repetition is the safety mechanism, and it is the reason an edit is small. The model sends only the lines it wants to touch, each one carrying proof of which version it is touching.
How an edit is validated
Before kli writes anything, it checks the patch against the file as it exists on disk right now. Two checks run, in order.
First, read-before-edit. kli keeps a per-file record of what the model last saw, keyed by the file's resolved path. If a patch names a file the model has not read in this session, the patch is rejected with a message telling the model to read the file first. The model cannot edit a file it has never seen.
Second, anchor re-hashing. For every file the patch touches, kli reads the current contents from disk, splits them into lines, and re-computes each line's hash. For each anchor in the patch, it checks two things: that the anchor's line number is in range, and that the anchor's hash equals the freshly computed hash of the line now at that position. If the line number points past the end of the file, the anchor is rejected. If the hash does not match, the anchor is stale and is rejected. The rejection message names the line and tells the model to re-read around it and resend.
The hash check compares against disk, not against the cached record of what the model saw. The cache only answers "did the model read this file." It cannot answer "is this anchor still good," because the file may have changed on disk since the read, from a build step, a formatter, another tool, or you. Only re-hashing the live file can catch that.
Why a stale anchor rejects the whole patch
Validation is all-or-nothing per call. kli gathers every problem across every file in the patch first. If there is even one problem, it raises an error with all the problems listed and writes nothing. Files are written only after the entire patch validates clean.
This is the difference between an anchor-validated patch and a blind overwrite. A blind overwrite takes the new contents and replaces the file, whatever the file now holds. If the file changed under it, the overwrite erases that change without noticing. The damage is silent: the write succeeds, the file looks edited, and the lost change surfaces later as a confusing regression.
An anchored patch cannot do that. The hashes pin the edit to a specific version of each line. If line 2 was return f"hello {name}" when the model read it but is now something else, the anchor 2:7f no longer matches, the patch is rejected, and the file keeps its current contents. The model gets back a precise message: anchor 2:7f is stale, re-read around line 2. It re-reads, gets fresh anchors, and resends a patch built against the current file. The conflict turns into a retry instead of a lost edit.
The drift does not have to land on a line the patch edits. Because anchors carry line numbers and the file is re-split fresh, an insertion or deletion earlier in the file shifts every later line's number. An anchor that still has the right hash but the wrong line, or the right line number holding different text, fails the check. The patch only applies when its view of the file still holds.
How this fits the rest of kli
This anchored read-then-edit cycle is one tool family among several, all of which run with full permission by default; kli has no per-action approval prompt. The safety here is not a gate you click through. It is a property of the edit format: an edit that does not match the file it claims to edit cannot be applied. For how kli decides what a tool may touch at all, see Capabilities and fault barriers. For what happens when a tool call comes back as a problem the model has to resolve, see The agent loop.