Jev decides what Claude Code keeps when it compacts, instead of a model-written summary.

When a conversation gets long, Codex, Claude Code and Antigravity compact it: they replace the old history with a summary written by the model. Exact details get lost that way (an error message, a file already read, a test result), and writing the summary costs a large request.
jevcomp uses Jev, a small, fast AI model that only answers yes/no questions, to decide which old command outputs still matter:
jevcomp codex, the compaction request never reaches OpenAI: jevcomp answers it with what Jev kept.jevcomp agy runs it through jevcomp, but saves nothing yet.Your own messages are never removed. If anything fails, the agent compacts the way it normally does. Each compaction makes a few small Jev requests, billed to your OpenRouter or TypeSafe key.
The same for every agent:
npm install -g --install-links github:jevcomp/jevcomp
jevcomp install
install asks which agents to connect (Codex, Claude Code, Antigravity or all), the provider and the key. To skip the questions: jevcomp install openrouter codex (or typesafe, and claude, agy or all). Run it again to change the key or add an agent. The agents share the key, the settings, the history and the dashboard.
After jevcomp install, open each agent like this:
| Agent | Open it with | What changes |
|---|---|---|
| Codex | jevcomp codex | Saves tokens: jevcomp answers the compaction itself. Plain codex saves nothing. |
| Claude Code | claude (as usual) | Saves tokens on every compaction. The first time, restart Claude Code once. |
| Antigravity (experimental) | jevcomp agy | Runs through jevcomp but saves nothing yet: jevcomp does not know yet which Antigravity request is the compaction, so it has nothing to replace. |
jevcomp codex. To force a compaction and see it work, type /compact.jevcomp install agy adds a local certificate so jevcomp can read the conversation with the model (the Google sign-in passes through untouched); on Windows, click Yes once in the warning. It needs OpenSSL (winget install ShiningLight.OpenSSL.Light). Without the certificate, jevcomp agy opens plain Antigravity. JEVCOMP_CAPTURE=1 saves the requests it sees.jevcomp dashboard opens it by hand.Claude Code can also get jevcomp as a plugin, without npm:
claude plugin marketplace add jevcomp/jevcomp
claude plugin install jevcomp@jevcomp
It saves the same as the npm install. The key comes from one saved by jevcomp install, from OPENROUTER_API_KEY / TYPESAFE_API_KEY, or from /plugin configure jevcomp@jevcomp in Claude Code.
Run jevcomp doctor (plugin: ask Claude Code Check jevcomp). It is working when the agent shows jevcomp dashboard: http://127.0.0.1:43127/ as a session starts.
jevcomp codex [args] runs Codex through a local proxy. When Jev returns a usable result that meets the minimum reduction setting, the proxy answers Codex's compaction request itself instead of sending it to OpenAI. If Jev fails, or its result is too big or cuts too little, the request goes to OpenAI as usual. In the dashboard, a compaction answered by Jev shows as completed.
| Run | What it removes |
|---|---|
jevcomp uninstall | jevcomp from every agent, and stops the dashboard |
jevcomp uninstall codex / claude / agy | Only from that agent (agy also removes the certificate); the dashboard stays while another agent uses it |
claude plugin uninstall jevcomp@jevcomp then claude plugin marketplace remove jevcomp | The Claude Code plugin |
jevcomp uninstall (and remove the Claude Code plugin, if you used it).npm uninstall -g jevcomp.~/.config/jevcomp, and your history, ~/.jevcomp."CLAUDE_CODE_ENABLE_FUNCTION_HOOKS": "1" line that jevcomp added under env in ~/.claude/settings.json, unless another plugin needs it.Open http://127.0.0.1:43127/. It starts by itself when a Codex or Claude Code session starts and keeps running until you restart the computer.
Where is the jevcomp dashboard? (or run jevcomp doctor).jevcomp dashboard, or ask the agent to restart the jevcomp dashboard.JEVCOMP_DASHBOARD_PORT, for example 43200.It shows only what jevcomp can measure: characters before and after each compaction, what was sent back, each Keep / Shorten / Remove decision with its risk, Jev requests and tokens, and runs that were skipped or failed. It does not estimate billing-token savings.
Run jevcomp settings, use the Configurações page of the dashboard, or ask the agent to change a jevcomp setting. Each program has its own settings. The defaults suit most people.
| Setting | Default | Meaning |
|---|---|---|
pin-recent-messages | 6 | Newest messages Jev never touches. |
loss-threshold | 0.5 | Jev removes or shortens an output only when its risk of losing something still needed is below this. Higher cuts more. |
min-reduction-ratio | 0.15 | jevcomp acts only when it cuts at least this share of the text. |
Settings are saved in ~/.config/jevcomp/settings.json. The variables JEVCOMP_PIN_RECENT_MESSAGES, JEVCOMP_LOSS_THRESHOLD and JEVCOMP_MIN_REDUCTION_RATIO override them.
Rarely needed; set them as environment variables.
| Option | Default | What it controls |
|---|---|---|
JEVCOMP_CONCURRENCY | 4 | Jev requests at the same time. |
JEVCOMP_MAX_STATE_TOKENS | 24000 | Size of the conversation copy shown to Jev. |
JEVCOMP_MAX_REQUEST_TOKENS | 30000 | Size of one Jev request. |
JEVCOMP_TRUNCATE_HEAD_CHARS | 300 | Characters kept when an output is shortened. |
JEVCOMP_TIMEOUT_MS | 20000 | Time limit for one Jev request. |
JEVCOMP_RETRIES | 1 | Retries after a network failure. |
JEVCOMP_GOAL | automatic | Task description given to Jev instead of your recent messages. |
JEVCOMP_DATA_DIR | ~/.jevcomp | Where history and session files go. |
JEV_MODEL, JEV_BASE_URL, OPENROUTER_JEV_MODEL, OPENROUTER_JEV_URL and OPENROUTER_HTTP_REFERER change the Jev endpoint, for development.
jevcomp install (or Configure jevcomp with ... in Codex) saves the provider and key in ~/.config/jevcomp; run it again to change them. OPENROUTER_API_KEY or TYPESAFE_API_KEY in the environment take precedence, with JEVCOMP_PROVIDER to pick one. A key can also come from a file: OPENROUTER_API_KEY_FILE, TYPESAFE_API_KEY_FILE or JEVCOMP_KEY_FILE.
For each finished command, Jev answers two questions: would removing the command and its output lose something still needed, and would shortening the output. The answer becomes Keep, Shorten (the first characters plus a note) or Remove (the command can be run again). Your messages and Codex's instructions are never removed. Common secret patterns are hidden before anything goes to Jev, but not every possible secret can be recognised.
Codex runs through jevcomp codex. Its local proxy answers compaction requests with Jev's cuts when they meet the configured reduction threshold; otherwise the request goes to OpenAI as usual.
Claude Code lets a plugin replace the compaction itself (an early-access feature called function hooks), so there jevcomp hands back the conversation with Jev's cuts instead of a summary.
jevcomp does not judge content it cannot read (images, audio, encrypted agent messages): then the agent compacts normally. In Codex, the experimental token-budget reset also goes through jevcomp; turn jevcomp off for that if you want a completely clean context.
To preview what Jev would keep from a Codex rollout without changing anything: jevcomp compact rollout.jsonl --context retained.txt --json retained.json.
Everything is in ~/.jevcomp (or JEVCOMP_DATA_DIR): history.jsonl for the dashboard. History from versions before 0.7.0, in ~/.codex/jevcomp and the old Codex plugin data folder, is still read.
npm run check
No third-party runtime dependencies. dist/ is committed so the plugins run without a build. Tests use a fake Jev and make no paid requests.
Independent implementation informed by these MIT-licensed projects:
IAmUnbounded/save-token-jev-cleanleonaaardob/fast-dev-compactiontamaratran/fast-jev-compactionfatelei/jevcomp (post-compaction membership check)AUDIT.md has the detailed comparison and compatibility notes.
hooks/claude.js 41 lines1import { applyJevCut, toJevMessages } from '../dist/claude.js';
2
3// The module runs without Node, so the Jev work runs in a node process started from this plugin's own dist/.
4function cliPath() {
5 const path = decodeURIComponent(new URL('../dist/cli.js', import.meta.url).pathname);
6 return /^\/[A-Za-z]:\//.test(path) ? path.slice(1) : path;
7}
8
9async function askJev($, e, options) {
10 let sessionId;
11 try { sessionId = await $.session.id(); } catch {}
12 const input = JSON.stringify({ sessionId, agentId: e.agentId, trigger: e.trigger, provider: options.provider, apiKey: options.apiKey, messages: toJevMessages(e.messages) });
13 const run = await $.process.run(['node', cliPath(), 'claude-compact'], { stdin: input, timeoutMs: 300_000 });
14 if (run.exitCode !== 0) throw new Error(String(run.stderr).trim() || `jevcomp exited with ${run.exitCode}`);
15 return JSON.parse(String(run.stdout));
16}
17
18function note($, text) {
19 $.ui.log(`jevcomp: ${text}`);
20}
21
22/** @type {import('claude-code').Register} */
23export const register = (on, options) => {
24 on('session.compact', async ($, e, next) => {
25 // A precomputed summary would be the model-written one this plugin exists to avoid.
26 if (e.trigger === 'precompute') return { skip: 'jevcomp compacts when the compaction happens' };
27 try {
28 const cut = await askJev($, e, options);
29 if (!cut.apply) {
30 note($, `Claude summary used (${cut.reason})`);
31 return next(e);
32 }
33 note($, `kept what still matters from old tool output (${cut.summary})`);
34 return { messages: applyJevCut(e.messages, cut) };
35 } catch (error) {
36 note($, `Claude summary used (${error instanceof Error ? error.message : String(error)})`);
37 return next(e);
38 }
39 });
40};
41dist/claude.js 32 lines1export function toJevMessages(messages) {
2 return messages.map((message) => ({
3 role: message.role,
4 text: message.text,
5 toolCalls: message.toolUses.map((use) => ({ id: use.tool_use_id, name: use.tool, input: use.input })),
6 ...(message.toolResults?.length ? { toolResults: message.toolResults.map((result) => ({ callId: result.tool_use_id, output: result.text, isError: result.isError })) } : {}),
7 }));
8}
9/** Messages Jev left alone go back as the engine's own objects; edited ones are rebuilt without the engine's handle. */
10export function applyJevCut(messages, cut) {
11 const dropped = new Set(cut.dropped);
12 const out = [];
13 for (const message of messages) {
14 const toolUses = message.toolUses
15 .filter((use) => !dropped.has(use.tool_use_id))
16 .map((use) => (use.tool_use_id in cut.truncated ? { ...use, text: cut.truncated[use.tool_use_id] } : use));
17 const toolResults = (message.toolResults ?? [])
18 .filter((result) => !dropped.has(result.tool_use_id))
19 .map((result) => (result.tool_use_id in cut.truncated ? { tool_use_id: result.tool_use_id, text: cut.truncated[result.tool_use_id], isError: result.isError } : result));
20 const changed = toolUses.length !== message.toolUses.length || toolResults.length !== (message.toolResults?.length ?? 0) ||
21 toolUses.some((use, i) => use !== message.toolUses[i]) || toolResults.some((result, i) => result !== message.toolResults?.[i]);
22 if (!changed) {
23 out.push(message);
24 continue;
25 }
26 if (!message.text.trim() && !toolUses.length && !toolResults.length)
27 continue;
28 out.push({ role: message.role, text: message.text, toolUses, ...(toolResults.length ? { toolResults } : {}) });
29 }
30 return out;
31}
32