Claude Code plugin that replaces the compaction summary with the decisions of a
fast decision model. The newest turns stay verbatim, every older tool call and
result is scored by the model and kept or cut accordingly, host notices are
trimmed, and older prose is condensed into a digest. The model is TypeSafe's
Jev by default, or OpenAI's Decisions API
(provider: "openai"), or no model at all (provider: "algorithmic"). Nothing
is lost: the recall tool reads back what a compaction cut, and searches your
past sessions.
In A/B runs against the built-in summary, a compaction took under 2 s instead of about 25 s and the model re-read fewer files after it; a session that compacts once cost about 10% less, and one that compacts repeatedly in a small window cost the same. With the default settings, a replay of long real sessions keeps about a third of the context per compaction. See Evals.
JetCompact is a fork of fast-jev-compaction. It combines the most useful work from that project's many forks with its own; the full lineage is under Features and credits.
I used pi-vcc in Pi for some time and liked how fast its compaction is compared to the stock summary. Speed was the main factor for me. When Jev came out, fast-jev-compaction brought it to Claude Code as a mod. The next day Theo posted about it, and his post started a debate. I wanted the speed, so I tried it anyway. It had many issues, and the author stopped merging fixes, so I forked it to fix them. On the way the project turned into an experiment, with evals against Claude Code's built-in summary. Try it, and send feedback and suggestions as issues.
Theo's post is about the original fast-jev-compaction. Other users may have the same questions, so here is how JetCompact answers them.
| Concern | JetCompact |
|---|---|
| Compaction should be rare, not a constant filter. | It runs only when Claude Code would compact anyway: at compactAtPercent of the compaction window, or on /compact. |
| The decision model decides per tool call without seeing the output, so the agent loses what it needs and loops. | A cut result keeps its first few hundred characters, the transcript is never deleted, and the recall tool returns the full output by its id. After each compaction, the model gets a note that says what was cut and tells it to use recall before it runs a tool again. In the evals the model re-read 0.8 files per run after a compaction, against 2.1 with the built-in summary. |
| The output is "deleted permanently", so the agent must re-run the tool. | That is the original's README. JetCompact deletes nothing from the transcript, and recall reads it back. |
| Reasoning traces are lost. | Every compaction loses them: the built-in summary replaces the whole history, reasoning included. JetCompact keeps the last 4 turns and the kept tool output word for word and older turns as a digest, but no reasoning blocks either. |
| Models are trained on their own compaction summary. | Not measured directly. In the evals every arm finished every task, so these tasks do not separate the two on success. |
| Editing history invalidates the prompt cache. | Every compaction rewrites the cache, the built-in one too. How much context a compaction keeps decides how often the cache is rewritten and how much each later turn reads from it. JetCompact keeps about a third of the context, which the cost model puts below the built-in summary over a long session; see How hard a compaction cuts. |
| Feature | What it does | Origin |
|---|---|---|
| Quiet output | Diagnostics, including the per-call decisions, go to the debug log only; you see one short toast instead of a wall of text in the transcript | k-metehan (6bb138d) |
| Compaction log file | Each outcome, and each /compact queued in a headless session, is one line in ~/.claude/jetcompact/compactions.log (newest 200), since the desktop app has no --debug |
k-metehan (aa912a9) |
| Hidden content folded back | Kept messages go back without their engine handles so --resume starts at the compaction (#89). That drops what Claude Code keeps beside each message: skill bodies, prompts typed while a tool ran, attachments, images, ToolSearch-loaded tools. The hook reads them from the conversation's API form and folds them back as text (placeholders for images and deferred tools), counted in the size guards |
JetCompact. k-metehan identified the loss and keeps handles instead, which brings #89 back |
| Request policy | At most maxConcurrentRequests (4) Jev requests at once; one retry on 429/5xx; no further requests after 401/403 or no answer; a failed request's calls are kept and the rest is applied, with the reason in the log |
k-metehan (de21417, 06163e6, b2a3d4c) |
/compact instructions |
Text typed after /compact reaches Jev's goal (first 2000 characters) |
k-metehan (3019dab, 10e00ab) |
| OpenAI Decisions provider | provider: "openai" asks OpenAI's Decisions API (gpt-6-luna, key from OPENAI_API_KEY) instead of Jev; Jev stays the default, and the Jev apiKey/baseUrl never reach OpenAI |
deadczarvc-labs/jev-factkeep-compaction (235a6c6) |
| Recall tool | The model gets a recall tool over the session transcript, which compaction never deletes from: search it (query, ranked or regex), read entries in full by #N or tool_use_id (expand, the uncut result, following outputs Claude Code saved to a file), list them (range), or see which files the session changed and every call on a file (touched), in answers of at most 48k characters; agent does the same inside subagent transcripts. It runs on a pinned Node that tools/node.cmd downloads and verifies on first use, so nothing needs installing. recall: false turns it off |
Ranking, regex mode and the touched view from pi-vcc; answer budget from pi-blackhole; Node wrapper from intellij-community; transcript reader: JetCompact |
| Past-session search | sessions searches the other sessions of this project, another one (project: "billing-api") or all of them, and session opens one: "do it the way we did in the billing-api session" finds that session. The current session's matches are counted on a last line. With qmd installed, the search goes through an index of the sessions, which /recall-index builds for all projects or the ones you name by name, path or folder. Details: Searching past sessions |
JetCompact |
| Recall guidance | The model is told what compaction cut and how to get it back, at the points where it matters and never on every prompt: the recall tool stays loaded instead of behind ToolSearch, each cut note names the recall expand call that returns the rest, a digest or summary says recall reads its turns back, and one note at the top of the compacted history says what this compaction cut and to use recall before re-running a tool or saying something is gone. Each compaction replaces that note. Details: What the model is told |
JetCompact |
| Prose digest | Prose older than the last recentTurns (4) user turns becomes one digest message at every compaction; long sessions keep compacting and keep about a third of the context. Why these defaults: How hard a compaction cuts |
JetCompact; the digest is QuanticPotatoes' oldProse: digest |
| Harder cut | When a compaction frees too little or keeps more than targetPercent, one more attempt halves the keep budgets and cuts to 100-character heads; an automatic one that still misses the target applies its result when that frees 25%, so the built-in summary runs only when JetCompact cannot free that much |
JetCompact |
| Algorithmic provider | provider: "algorithmic" decides with no model and no key, by rules after pi-vcc and pi-blackhole: superseded reads, repeated calls and fixed errors go first; open errors and edits stay |
JetCompact |
Typed /compact applies |
A /compact you type is applied whenever it frees anything; the size guards hold back only automatic compactions |
ferrisworks (bd4a01d) |
| Stops when Claude Code moves on | Esc or another hook settling first abandons the in-flight request; nothing is returned or delegated | k-metehan (18f69f8, 0f4ca22) |
| Own result when the summary fails | If Claude Code's built-in summary fails after a fallback, JetCompact installs its own result when that freed anything | deadczarvc (a45402e) |
| Compaction window | compactAtPercent and targetPercent count against the window Claude Code compacts at (CLAUDE_CODE_AUTO_COMPACT_WINDOW or a setting), not the model's whole window, so the plugin compacts at your setting and on 1M-token models |
JetCompact |
| Evals | A/B runs against the built-in summary on graded coding tasks, with a cost model; see Evals and jetcompact-eval | JetCompact |
| Claude Code 2.1.292 types | $.model.complete returns an object since 2.1.292, which had silently broken oldProse: summarize |
JetCompact |
k-metehan is k-metehan/fast-jev-compaction.
How hard a compaction cuts by default, why, and how to change it is under How hard a compaction cuts.
Function hooks are an early-access Claude Code feature (2.1.292+). Set the flag
and your key where Claude Code runs, e.g. in ~/.claude/settings.json:
{ "env": { "CLAUDE_CODE_ENABLE_FUNCTION_HOOKS": "1", "TYPESAFE_API_KEY": "<your key>" } }claude plugin marketplace add CrazyCoder/jetcompact
claude plugin install jetcompact@crazycoderRestart Claude Code or run /reload-plugins. /compact and auto-compaction now
go through Jev; a one-line toast reports the outcome
(jetcompact: compacted, N% smaller, built-in summary (…), or
not compacted (…)). The details, including the per-call decisions, go to the
debug log only (claude --debug), never into the transcript, and each outcome
is also one line in ~/.claude/jetcompact/compactions.log.
Updating. Claude Code updates a plugin only when its version changes. Set
autoUpdate: true on the marketplace entry, or run
claude plugin marketplace update crazycoder.
To try it from a checkout:
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude --plugin-dir .
Developing. To have every session, including the desktop app's Code tab, run
the plugin from a checkout, add the checkout as a local marketplace and install
from it. Claude Code then reads the plugin from the checkout itself: an edit
takes effect in new sessions, and in open ones after /reload-plugins, with no
version bump or reinstall.
claude plugin marketplace add /path/to/jetcompact
claude plugin install jetcompact@crazycoderDisable any other compaction plugin first, such as fast-jev-compaction or
another of its forks
(claude plugin disable fast-jev-compaction@<marketplace>), so only one
compaction hook runs.
Options are asked at install time and live under pluginConfigs. The ones most
people touch:
{
"pluginConfigs": {
"jetcompact@crazycoder": {
"options": {
"compactAtPercent": 60,
"recentTurns": 8,
"builtinFallback": "auto"
}
}
}
}| Option | Default | Effect |
|---|---|---|
compactAtPercent |
60 |
Context percentage at which the plugin asks for a compaction, of the window Claude Code compacts at (CLAUDE_CODE_AUTO_COMPACT_WINDOW when set, else the model's) |
oldProse |
unset: the digest | Prose older than recentTurns user turns: keep verbatim, digest (offline, deterministic) or summarize (written by proseModel). Unset, the digest; see How hard a compaction cuts |
recentTurns |
4 |
User turns kept outside the digest or summary |
builtinFallback |
auto |
Which compactions fall back to Claude Code's built-in summary when Jev fails or frees too little: auto (only its own automatic one), always, never. Use always if Claude Code's own autoCompact is disabled, so /compact and plugin requests still get a summary |
targetPercent |
45 |
Estimated context after compaction, as a percentage of the compaction window, that the harder cut aims under; an automatic compaction above it still applies its result when that frees minReductionRatio |
minReductionRatio |
0.25 |
Minimum estimated reduction required to replace the history |
keepMode |
rank |
rank keeps the best-scored calls within keepResultTokens / keepCallTokens; threshold uses keepThreshold |
preserveRecentMessages |
6 |
Newest messages never touched |
provider |
jev |
Who scores the calls: jev (TypeSafe Jev), openai (OpenAI Decisions API, gpt-6-luna, key from OPENAI_API_KEY), or algorithmic (no model and no key: rules after pi-vcc and pi-blackhole, see hooks/README.md). Luna's probabilities are on a different scale from Jev's, so keepThreshold and the rank budgets may need retuning |
ageCutResults |
false |
A result already cut by an earlier compaction keeps only its cut note at the next one |
To score with OpenAI's Decisions API instead of Jev, set provider and give the
key through OPENAI_API_KEY in the environment, in the settings' env block,
or in a dotenv file:
{ "options": { "provider": "openai", "envFile": "C:/path/to/.env" } }Every option, with its default, is listed in hooks/README.md.
Each compaction does three things by default:
- Prose older than the last 4 user turns (
recentTurns) becomes one digest message of at most 12,000 characters. The last 4 turns stay word for word. - Tool results are kept whole, best-scored first, within 6,000 estimated tokens
(
keepResultTokens). Calls whose result is cut keep their input within 2,000 tokens (keepCallTokens). Every other result keeps its first 300 characters and a cut note. - Host notices are cut to a head.
When the result frees less than minReductionRatio (25%) or leaves more than
targetPercent (45% of the compaction window), a harder cut is tried: the
keep budgets halve and cut results keep 100 characters. It asks Jev nothing new.
An automatic compaction that still misses targetPercent applies its best
result anyway when that frees 25%. Claude Code's built-in summary runs only for
an automatic compaction that JetCompact cannot free 25% of, or whose decision
model fails; a typed /compact applies whatever it frees.
The digest keeps the first prompt (up to 2,000 characters), each later prompt
(up to 400), the assistant's final reply of each turn (up to 500), the tool
names used per turn with counts, and the paths and ids mentioned. It drops the
tool calls and results of those turns and the narration between them. Each
digest folds the previous one in, so it does not grow with the session. The
recall tool reads every original message, call and result back from the
transcript.
Why these defaults
How hard to cut depends on many variables: the model, the window, how fast a session grows, the cache TTL, how often the agent needs old output, and which decision model ranks the calls. The evals cover only part of that, so more research and evals are needed (see Evals). These defaults come from what was measured:
- Fast on every compaction. No compaction falls back to the built-in summary
just for missing
targetPercent. In the repeated-compaction evals a compaction took 1.8 s on average, against about 25 s, and the few that fell back freed less than 25%. - About a third of the context kept. In a replay of 30 large real sessions, these defaults kept 36% of the context, against 70% with the earlier defaults (prose verbatim, 12,000 / 4,000-token budgets, 8 recent turns). A smaller floor means fewer compactions and fewer cache rewrites: the cost model projects a long session at about $36 instead of $65, against $45 with the built-in summary.
- Little more lost. The share of cut results that the agent needed again
later went from 2.2% to 3.1%. Per compaction it went from 1.4 to 1.6, and a
session compacts about a third less often, so it loses about as much as
before. With live Jev the rate went from 1.9% to 2.6%.
recallgets any of it back. - The floor stays flat. Each compaction ended about 6k tokens above the last one, against 21k with prose verbatim, so long sessions keep compacting.
- Why digest first. Digesting old prose freed as much as quartering the tool budgets, at less than two-thirds of the loss.
- Why not harder. Going further toward pi-vcc's end lost more per compaction than it saved. 2 recent turns lost about as much as 4, but the loss measure does not see instructions lost from prose, so 4 stay. 100-character heads doubled the loss, so heads stay at 300. Keeping no results at all, as pi-vcc does, lost about 3 times as much per compaction as the 0.11 defaults.
Cons
- Old turns survive only as digest lines: an instruction or preference you gave
more than 4 turns ago is cut to 400 characters, and an intermediate reply is
gone. Repeat what still matters, put it in
CLAUDE.md, or raiserecentTurns. - Old tool calls leave no stubs, only counts. The model has to call
recallto see them again; the digest header and the compaction note tell it to, but it does not always follow. - The loss measure is a proxy: a path or id from a cut result that a later tool call used before anything showed it again. Task success with these defaults is not measured yet.
Controlling it
| Option | Effect |
|---|---|
oldProse |
Unset: the digest. keep: prose verbatim (long sessions keep more and compact more often). summarize: a summary written by proseModel |
recentTurns |
User turns kept word for word outside the digest (4) |
keepResultTokens, keepCallTokens |
Budgets for whole results and for inputs of cut calls (6000, 2000); raise them to keep more |
truncateHeadChars |
Head of a cut result: 300, or 100 in the harder cut; a set value is used throughout |
targetPercent |
Estimated context after compaction that the harder cut aims under (45% of the compaction window) |
recall |
Keep it on (the default) so the model can read cut output and digested turns back |
Each outcome line in ~/.claude/jetcompact/compactions.log names the prose mode
used (old prose digest: …), and ends with digest was refused (…) when the
harder cut was needed.
The recall tool also searches your other sessions, so you can ask "see how we
set up jscpd in the billing-api session and do the same here". The model calls
it with sessions: "<keywords>", in this project by default, in another with
project: "<part of its name or path>", or in all with project: "all". The
current session is not among the hits, since recall searches it directly; when
the searched projects include it, a last line says how many matches it holds.
There are two ways to search, and recall picks one for you:
- Transcript scan (no setup): reads the session transcripts under
~/.claude/projects/directly, with ripgrep when it is installed to pick the files. Fast enough for one project; across many projects a broad query can take seconds. - Session index: with qmd installed
(
npm install -g @tobilu/qmd; recall never installs it), each session is exported as markdown (prompts, replies, calls with the head of their results) to~/.claude/jetcompact/sessions/and indexed in a qmd index of its own,jetcompact-sessions, apart from your other qmd collections. Searches across many projects then stay fast, and hits are ranked across sessions. The exports and the index are readable by you only, and an export goes when Claude Code deletes its transcript.
Without any command, the first cross-session search builds the index for all
projects in the background (that search is answered by the scan), and every
later search adds the sessions that changed. To build it yourself, or for some
projects only, use /recall-index:
| You want to | Run |
|---|---|
| See what is indexed | /recall-index |
| Index every project | /recall-index all |
| Index this project | /recall-index . |
| Index a project by name or path | /recall-index billing-api or /recall-index C:\work\billing-api |
| Index every project under a folder | /recall-index C:\work |
| Take a project out | /recall-index remove billing-api |
| Delete the index and exports | /recall-index remove or /recall-index remove all |
Names match any part of a project's folder name; paths take any slash style or
case, ~/…, or a path relative to this project. Several targets can be given,
separated by spaces or commas; quote a path with spaces. The command waits for
the build and reports the sessions, projects, time and index size. Once you
choose projects, only those are indexed and kept up to date; a search elsewhere
uses the scan. An index that covers all projects, including the one a search
built on its own, keeps covering all of them when you name one: to index only
some, run /recall-index remove first. sessionIndex: "off" turns the index
off. The details are in hooks/README.md.
history ─┬─ first + newest messages ─────────────── pinned, verbatim
├─ tool calls ── Jev scores each call and result
│ ├─ best results, within keepResultTokens (6000) ─ kept verbatim
│ ├─ best calls, within keepCallTokens (2000) ──── input + 300-char head
│ └─ the rest ──────────────────────────────────── stub note
├─ host notices (<system-reminder>, hook output, …) ──────────────── cut to a short head
└─ prose older than recentTurns (4) ──────────────────────────────── one digest message
▼
frees < minReductionRatio (25%), or estimated floor > targetPercent (45%)
├─▶ harder cut: half the budgets, 100-char heads
├─▶ still over targetPercent but frees 25%: applied
└─▶ frees less: builtinFallback (built-in summary or history unchanged)
Jev scores are compressed (keepCall rarely exceeds 0.3), so the default rank
mode ranks calls instead of using an absolute threshold. Jev sees the whole
conversation with tool outputs replaced by a one-line note, fitted into
maxStateTokens. Prose older than recentTurns user turns becomes a digest at
every compaction; recall reads the originals back. Set oldProse to change
that (keep or summarize). The full algorithm is in
docs/library.md.
Lineage: tamaratran/fast-jev-compaction (upstream, no push since 2026-09-18, about 60 unmerged PRs) → ferrisworks/fast-jev-compaction (integration fork that merged about 13 of them) → QuanticPotatoes/fast-jev-compaction → JetCompact, which also takes ideas and code from k-metehan/fast-jev-compaction and deadczarvc-labs/jev-factkeep-compaction. Thanks to all of them, and to the authors of the upstream PRs linked below. What JetCompact adds is listed under Added in JetCompact.
- Auto-compaction in the desktop app and
claude -p. Those sessions refuse$.session.compact, so thecompactAtPercenttrigger queues/compactinstead (upstream PR #111). - Rank-based keeping. Measured on 4,603 decisions over 60 sessions,
keepCallwas about 0.1–0.3 (max 0.58), so the absolute 0.5 threshold kept 0 calls (upstream issues #56, #26, #52). targetPercent. Because all message text was kept, the floor rose with each compaction (reduction 77% → 30% within one session; upstream issue #70 reports 36K → 87K tokens over 6 rounds).- Host-text trimming (
trimHostText): notices were about 54% of the retained "user" text per issue #70 (idea from upstream PR #78). - Prose digest and summary (
oldProse). - Stubbed instead of deleted calls (
dropCalls: false): a silent drop led the model to report work it never did (#65, PR #83). - Robustness from ferrisworks: secret redaction
(#98), handles
stripped so
--resumeworks (#89), subagent and speculative compactions skipped (#112), code-point-safe truncation (#110), Jev wait bound (#117), auto-only built-in fallback (#103), plus headless-session and surrogate-pair tests (upstream PRs #125, #132). - CI and versioned releases; see CHANGELOG.md.
| upstream 0.3.0 | ferrisworks 0.5.4 | QuanticPotatoes 0.8.0 | JetCompact | |
|---|---|---|---|---|
| Tool calls kept | Absolute 0.5 threshold | Same | Rank within token budgets | Same |
| Dropped calls | Deleted | Stub note | Stub note | Stub note |
| Size guard | Min 25% reduction | Same | Same + targetPercent |
Same, hidden content counted, measured against the compaction window |
| Built-in fallback | Always | Automatic compactions only | Same | Same |
| Secret redaction | No | Yes | Yes | Yes |
| Host notices / old prose | Kept | Kept | Trimmed / optional digest | Trimmed / digest by default |
| Transcript output | Decision dump | Same | Same | One toast; debug log and log file |
| Hidden content after compaction | Kept on untouched rows (--resume reloads the old history) |
Lost (no handles) | Lost | Folded back as text |
| Failed Jev request | Whole compaction falls back | Same | Same | Its calls kept, rest applied |
| Decision provider | Jev | Jev (any System One endpoint) | Same | Same + OpenAI Decisions, or rules with no model |
| What was cut | Gone from the conversation | Same | Same | recall tool reads it back from the transcript |
| Model told what was cut | No | No | No | Cut notes, digest header and one note per compaction point at recall |
| Past sessions | No | No | No | Searched by keyword, optionally through a qmd index |
| CI and releases | No | No | Yes | Yes |
Roadmap: fact salvage on dropped calls (upstream issues #118, #105).
jetcompact-eval runs the same coding tasks headless with the built-in summary and with JetCompact, grades them by hidden tests, and measures cost, re-reads after a compaction and compaction time. The results are in Evals.
tools/replay replays recorded sessions offline under different policies and
reports reduction, post-compaction floor, growth per compaction and a
lost-and-needed rate. See tools/replay/README.md. The
evidence is limited: lost-and-needed is a token-reuse proxy computed offline,
not a measure of task success.
The package is usable without Claude Code (compactMessages, your own
JevAsker, the building blocks): see docs/library.md.
npm install
npm run typecheck # library + hook
npm test # fake Jev, no network; includes the duplicate code gate
npm run jscpd:list # every clone (see docs/duplicate-code.md)
npm run test:hooks:host # native hook dispatcher tests; requires Claude Code
npm run build
npm run validate:plugin # claude plugin validate
TYPESAFE_API_KEY="$(cat ~/.typesafe_key)" npm run demo # live network checkA push to main never makes a release. To release a version:
- Set the new version in
.claude-plugin/plugin.json,.claude-plugin/marketplace.json,package.jsonandpackage-lock.json;npm run check:versionconfirms they agree. - Add its
## [x.y.z]section toCHANGELOG.md, and commit. - Tag the commit and push the tag:
git tag vX.Y.Z
git push origin main vX.Y.ZThe Release workflow then checks that the tag matches every declared version,
publishes the library to npm as jetcompact (through npm trusted publishing,
after the typecheck, the tests and the build pass), and publishes a GitHub
release with the tag's CHANGELOG section as its notes. It fails, and publishes
nothing, when the versions disagree, the CHANGELOG section is missing, or a
check fails. Installed plugins update from the version on main, not from the
release, so push the version bump only when it is ready to ship.
demo/JevDemo is a native SwiftUI app (macOS) that plays a scripted, dramatized
compaction for screen recording; it never calls the API. demo/JevDemo/build.sh
builds and launches it; space replays.