Long coding-agent sessions read the same file again and again. The agent opens config.py, edits something elsewhere, opens config.py again to check, and a while later opens it a third time. If the file did not change in between, every one of those identical result bodies stays in the conversation, and every one of them is sent to the model again on every later request.
Jev Prune Kit is a small tool I built to deal with exactly that case and nothing broader. It finds older file-read results that are byte-for-byte identical to a later read of the same file with the same arguments, and, in the harnesses where it has a native adapter, leaves the older body out of the outgoing request only. The body is replaced by a one-line marker that names the copy that was kept. Nothing is summarized, and the harness’s own transcript is never edited.
Status: version 0.1.0, labelled experimental by the repository. The README says it plainly: “Not live-tested. No real harness session and no live TypeSafe service were used to validate this build.” There are no measurements of token or cost savings.
Why not just compact?
Most harnesses handle a full context window with compaction: a model rewrites the conversation into a summary and the details are gone. That is a reasonable last resort, but it is lossy by design, and repeated file reads are a poor reason to trigger it. A repeated read carries no new information when the later copy is identical and still present.
So the question this kit asks is very narrow: is there an older read result whose exact text is still available later in the same request? If yes, and some extra checks pass, the older copy can be left out of what the model sees without paraphrasing anything. Everything else in the conversation stays as it was.
It is also not a replacement for /compact (choosing Compact makes zero calls to the assessment service), and it is not token accounting.
How it works: request projection
I call the approach request projection. The harness keeps its native transcript exactly as it is. When the harness is about to send a request to the model, the adapter takes a copy of the outgoing message list, applies any approved omissions to the copy, and hands the copy back. Remove the adapter and the original content simply reappears in the next request.

There are two phases. On /prune, run manually at an idle point, the kit finds candidate pairs, asks an evaluator whether the older copy can be omitted, and stores a receipt for each approved pair. On every outgoing request after that, the adapter re-checks each receipt from scratch and only then swaps the older body for a marker in the copy.
Choosing candidates
Selection lives in candidates() in jev_prune/core.py. A result is only considered if it comes from a tool named read, read_file or file_read, if a later result has exactly the same text and exactly the same request arguments, and if it sits before both the current user turn and the last 16 messages:
# jev_prune/core.py
cutoff = min(snap.last_user, len(snap.messages) - RECENT)
latest: dict[str, Item] = {}
for x in snap.items:
latest[x.request_key] = x
chosen: list[tuple[Item, Item]] = []
for x in snap.items:
witness = latest[x.request_key]
if (x.id in pinned or x.index >= cutoff or x.index >= witness.index
or not x.eligible or not witness.eligible or x.tool not in READ_TOOLS
or not 256 <= len(x.text.encode()) <= MAX_TEXT or x.text != witness.text
or len(dumps(x.request).encode()) > MAX_TEXT):
continue
The “witness” is the latest result for the same tool and arguments. The older item is only a candidate if its text equals the witness text exactly; a read of the same path that returned different content (because the file changed) is never paired. Bodies under 256 bytes or over 8,000 bytes are skipped, as are failed results, multimodal results, and any tool call or result ID that appears more than once. For Pi, the result must explicitly say isError: false; for OpenAI-style messages, which have no standard success flag, explicit error flags disqualify a result.
There is also a pattern veto. If the goal, the result text or the arguments match common credential or error patterns (a private key header, Bearer tokens, password=, traceback, permission denied and similar), the pair is skipped. The code comment is careful to call this a veto, not redaction, and so is the documentation.
Snapshot parsing is strict: three explicitly named formats (pi, opencode, openai), a required session ID, and no scanning of a harness’s session directory on disk.
The assessment step
Exact text equality says the later copy contains the same bytes. It does not say the older copy was unimportant in its position: an agent may have read a file before and after some step precisely to show that nothing changed. So each candidate pair goes to an external typed-inference service, TypeSafe, pinned to model jev-1.13.0, with three questions per candidate: does the later result fully preserve the useful content, would omitting the older one lose any unique fact or evidence, and does the older one carry unresolved work or an error.
The answers come back as probabilities, and the thresholds are strict:
# jev_prune/core.py
for i, (source, witness) in enumerate(selected):
if (probabilities[f"c{i}_coverage"] >= .99 and probabilities[f"c{i}_unique"] <= .01
and probabilities[f"c{i}_unresolved"] <= .01):
new_receipts.append({"policy": POLICY, "model": MODEL, "session": snap.session,
"format": snap.format, "source": source.proof(), "witness": witness.proof()})
The module docstring describes these thresholds as “experimental policy choices, not calibrated safety guarantees”, and I agree with that wording. Nobody has measured how the live model’s probabilities behave on real sessions.
This step is off by default. It needs JEV_PRUNE_ALLOW_REMOTE=1 and a TYPESAFE_API_KEY in the environment that launches the harness. The transport runs in a separate child process with a fixed HTTPS endpoint, no redirects, no retries, a 10-second wall-clock limit and size caps on request (24,000 bytes) and response (32,000 bytes). What gets sent is the current goal plus the selected read arguments and result bodies, not the whole transcript, but that can still include private code or paths, and the security notes say so. Installation and the offline inspect command never make the call. Without this step, no receipts are created, so in practice nothing is pruned without opting in.
Receipts and projection
A receipt stores the policy and model names, the session, the format, and two proofs: one for the source (the older body) and one for the witness (the retained body). Each proof holds the native ID, a SHA-256 hash of the call and result, and a hash of the tool name plus arguments. No context text is stored in a receipt. A session holds at most 128.
Projection is where the “re-verified every request” part happens. For each receipt, project() rebuilds the proofs from the current messages and compares them:
# jev_prune/core.py
if (witness is None or source.proof() != r["source"] or witness.proof() != r["witness"]
or source.request_key != witness.request_key or source.text != witness.text
or not source.eligible or not witness.eligible or source.index >= witness.index
or source.tool not in READ_TOOLS):
invalid += 1 # Retain original; do not pretend an old proof still applies.
continue
marker = MARKER.format(witness=witness.id[:160])
If the source has disappeared (for example because the host compacted or forked the session), the receipt is counted as inactive. If anything about either side no longer matches, it is counted as invalid and the original body stays. Only when everything still matches is the older body replaced, and the replacement is a fixed string:
# jev_prune/core.py
MARKER = "[Jev prune: repeated read-result body omitted; retained witness: {witness}]"
The call and result envelopes, their IDs, and every other message stay exactly as they were. The function works on a deep copy, so the snapshot it was given is never modified.
A synthetic walk-through
To see the whole path end to end without a harness or a network connection, I wrote a short script against the kit’s own functions. It builds a synthetic Pi-format session of 26 messages: a 12-line synthetic config file read twice with identical arguments and content, a different file read once, a new user turn, and enough later messages to push the first reads outside the protected tail. In place of TypeSafe it uses a stub evaluator that approves every candidate, the same kind of deterministic fixture the repository’s tests use. Then it projects, changes the retained copy, and projects again.

The results match the design:
- Exactly one candidate pair was found: the first read of
app.conf(source) and the second (witness). The read of the other file was never paired. - The outgoing copy had the first body replaced by the marker naming
r2, while the original message list was unchanged. - When the retained copy’s text changed, the same receipt no longer applied:
applied: 0, invalid: 1, and the original body was sent again.
The 496 bytes are one synthetic body minus the marker. They say nothing about what a real session would save; nobody has measured that for this kit.
Where it runs
The installer targets ten namespaces: OpenClaw, Hermes, OpenCode, Codex, Claude Code, Pi, Gemini CLI, Cursor, GitHub Copilot CLI and the shared .agents skills directory. What each one gets differs a lot:
| Target | Default install | With --experimental-adapters |
|---|---|---|
| Pi | Assessment skill | Native /prune, context transform, optional Prune/Compact/Pause chooser |
| OpenCode | Assessment skill | Native /prune, experimental message transform |
| Hermes | Assessment skill | Native /prune [session-id], single-user request middleware |
| Other seven | Assessment skill | Unchanged |
The assessment skill can describe what the host supports and inspect a snapshot you hand it explicitly, but a skill cannot edit context the harness holds, so on those seven targets nothing is pruned. The installer prints a plan by default and writes nothing until --apply; uninstall removes only files whose hashes are unchanged.
Each native adapter has caveats, listed in docs/COMPATIBILITY.md. On Pi, the optional chooser shown on threshold and overflow compaction events cancels compaction and stops the current run if you pick Prune or Pause, so you resume explicitly. On OpenCode, automatic compaction is not intercepted. Hermes is supported for a local single-user process only.
The repository also contains jev_prune/gate.py, a tested state machine describing what a full host integration would need (a revision stamp, an atomic compare-and-swap commit, a real next-request token budget). None of the three adapters implements that whole contract yet.
Working alongside jev-context-fabric
This kit has a sister project, jev-context-fabric. That project prunes the other half of a transcript: old assistant prose, with the originals kept in a local database. The two touch disjoint message types, but both wanted the same OpenCode transform hook and both wanted to own /prune.
The shared coordination contract, docs/BUS.md (byte-identical in both repositories), settles that. On each host one package is the carrier that registers the hook, and the others run as ordered stages inside it. Prune Kit carries Pi and Hermes; jev-context-fabric carries OpenCode. The dedup stage runs first and claims only tool-result:read; it substitutes bodies in place without changing the array length, which matters because the other package keys messages by position. Overlapping claims are refused at install time. A stage inside another package’s carrier only runs the local projection, never the remote assessment.
What I ran
I cloned the repository at commit 4cf4e40 and ran everything offline on Windows 11 with Python 3.14.3 and Node 24.18.0, with the home directory redirected into a scratch folder and remote assessment disabled.
| Check | Result on my machine |
|---|---|
Python suite (python -m unittest discover -s tests -v) |
107 run: 100 passed, 6 errors, 1 skipped |
Node suite (node --test tests/native.test.mjs) |
15 run: 15 passed |
runner.py inspect examples/pi-snapshot.json |
1 eligible pair, remote_called: false |
examples/gate_demo.py --choice prune |
Outcome ready; 1 simulated evaluator call, 0 simulated compactions |
All six Python errors were in installer and config tests that clear the process environment and then call Path.home() or start a subprocess; on Windows both fail without the usual environment variables (Could not determine home directory and WinError 87). The one skip was a symlink test that my account could not create symlinks for. None of the failures were in selection, receipts or projection. The project documents Linux as its tested platform and Windows as untested, and this is consistent with that.
The README’s count of 107 Python tests (27 for the bus) and 15 Node tests matches this commit. The committed docs/VALIDATION.md and raw logs record an earlier Linux run (Python 3.13.5, Node 22.16.0) with 73 Python and 15 Node tests passing; it predates the commit that added the bus tests. I did not reproduce a Linux run.
All of these tests use synthetic messages and deterministic evaluator fixtures. They are not real-world validation and say nothing about the live evaluator’s accuracy or about savings.
Limitations
Jev Prune Kit is an experimental release (v0.1.0). The list below comes from the repository’s own documentation and from what I observed.
- Not live-tested. No real Pi, OpenCode, Hermes or other harness session was used, and the live TypeSafe service was not called. Plugin loading, version compatibility and UI behaviour in those applications are unverified.
- No savings measurements. There is no benchmark of token, cost, latency or task-quality effects. Evaluator responses in the tests are fixtures.
- Bytes, not tokens. Projection removes bytes from outgoing requests. Host token counters, compaction thresholds, provider-side history and prompt caches may still reflect the original transcript, and a later native compaction can still consume the original material.
- Narrow scope. Only exact repeats from three read-tool names are eligible, and the selector assumes those tools are read-only. A custom tool with one of those names but different semantics would need the selector reviewed.
- Pruning requires a remote call. Receipts only come from the opt-in TypeSafe assessment, which sends selected read bodies off the machine. The credential check is a veto, not a redactor, and the Python worker does not inherit a harness’s egress or residency policy.
- Thresholds are uncalibrated. The .99 and .01 cut-offs are policy choices, not measured safety guarantees.
- Incomplete host integration. No adapter certifies a next-request token budget or transparently resumes a paused task. Pi’s chooser stops the run; OpenCode’s automatic compaction is not intercepted.
- Platform coverage. Developed and tested on Linux. Windows and macOS are untested by the project; on my Windows machine six installer and config tests errored for environment reasons. There is no CI configuration in the repository, and the committed validation report predates the most recent test additions.
If you try it, start with the plan-only installer run, use a disposable session, and run runner.py inspect on a snapshot you supply before enabling anything remote.
Building something small, bounded and honest about its limits, then writing down exactly what is and is not proven, is how I approach early-stage tools in my Prototyping and MVP Development work.
Resources
- CompleteTech-LLC-AI-Research/jev-prune-kit, MIT License (copyright CompleteTech LLC), commit
4cf4e40.
