fast-jev-compaction README

The project's README from github.com/tamaratran/fast-jev-compaction. As one markdown file: README.md.

fast-jev-compaction

Claude Code plugin that replaces the compaction summary with Jev decisions: every tool call and result is scored in one fast request, stale ones are dropped or truncated, everything kept stays verbatim. Also usable as an npm library.

What and why

Most context compaction asks an LLM to summarize old turns. A summary is lossy: a file path, exact error, constraint, or command can disappear even when it matters later. This library never rewrites anything. It only deletes tool calls and tool results Jev says are no longer needed, and it asks Jev while showing it the whole conversation. User and assistant text stays verbatim and in order.

The repository is both an npm package (src/) and a Claude Code plugin (hooks/, .claude-plugin/) that uses the package to replace Claude Code's built-in compaction summary with the original messages.

How it works

  1. Every tool_use is paired with its tool_result by tool_use_id. Calls in the first message or in the newest preserveRecentMessages messages are pinned and never touched.
  2. The state sent to Jev is the whole conversation so far, oldest first, with every tool result replaced by a short note (ok, 4213 chars (omitted)). Tool inputs are included, texts are included, nothing is summarized.
  3. The state is fitted into maxStateTokens (25k by default) in stages, each applied only if the previous one was not enough: tool inputs truncated to 1000, then 200, then 60 characters; long texts abridged to head + tail, oldest non-pinned messages first; old non-pinned messages collapsed to a [… N chars omitted …] note; old tool calls reduced to one line each (t12 Read file_path=src/a.ts → ok 480ch); old call-less messages left out; runs of old call-only messages folded into one entry. If it still does not fit, compaction throws. Tokens are estimated without a tokenizer (a word per six letters, half a token per digit, ~one per other symbol), calibrated to land a little above the counts Jev reports.
  4. For every non-pinned call Jev gets two noul questions: should the call stay (knowing it was made, with its input, still matters), and should the result stay verbatim (its contents are still needed and re-running the tool would not do).
  5. Questions are split into as many requests as needed so state plus questions stays under maxRequestTokens (30k by default, under Jev's 32k request limit). The same full state is resent with every request; requests run concurrently and their answers are merged.
  6. Decisions per call, against keepThreshold:
    • keepResult ≥ threshold → keep call and result;
    • else keepCall ≥ threshold → keep the call, truncate the result to its first truncateHeadChars characters plus a one-line note;
    • else → remove the call together with its result.
  7. The message list is rebuilt: a message that loses all its content is removed, untouched messages are returned as the same objects, and no result is ever left without its call.

Jev failures, malformed answers, a missing key, or a history that cannot be fitted throw; the caller (or the Claude Code hook) decides what to fall back to.

Install and usage

npm install fast-jev-compaction
export TYPESAFE_API_KEY=...
import { compactMessages, reductionRatio, type Message } from 'fast-jev-compaction';

const transcript: Message[] = [
  { role: 'user', text: 'Fix the failing test. Never edit src/generated.', toolUses: [] },
  {
    role: 'assistant',
    text: '',
    toolUses: [{ tool_use_id: 'toolu_1', tool: 'Read', input: { file_path: 'src/a.ts' } }],
  },
  { role: 'user', text: '', toolUses: [], toolResults: [{ tool_use_id: 'toolu_1', text: '…file…' }] },
  // …
];

const result = await compactMessages(transcript, { preserveRecentMessages: 4 });
console.log(result.messages, result.decisions, result.stats);
if (reductionRatio(result) < 0.25) {
  // not worth it: keep the original transcript, or summarize instead
}

Message is a subset of Claude Code's SessionMessage, so a session transcript can be passed in as is.

To bring your own transport, implement JevAsker (one ask(state, questions) method) and call compact(messages, asker, options); buildJevRequest and parseJevResponse give you the HTTP request body and response validation. The building blocks (collectToolCalls, fitState, batchCalls, decideCall, applyDecisions) are exported too.

apiKey defaults to process.env.TYPESAFE_API_KEY. Never commit the key or put it in a source file.

Options

Option Default Description
apiKey TYPESAFE_API_KEY TypeSafe API key (compactMessages/JevClient)
model jev-latest Jev model name
baseUrl https://api.typesafe.ai/v1/systemone System One endpoint
fetch native fetch Injectable fetch implementation for tests
goal last 3 user prompts Ongoing task description included in the state
keepThreshold 0.5 Minimum keep probability for a call or result to stay
preserveRecentMessages 6 Newest messages never touched (the first is always kept)
maxStateTokens 25000 Estimated token ceiling for the state
maxRequestTokens 30000 Estimated ceiling for state plus one batch of questions
truncateHeadChars 300 Characters of a dropped tool result retained before its note

result.stats reports message and character counts before and after, the per-reason decision counts, the state size in estimated tokens, which fitting stage was needed, and the number of requests.

Limitations

Claude Code plugin

The repository root is a Claude Code function-hook plugin: hooks/fast-jev.ts is a thin adapter that feeds session.compact transcripts through src/ and falls back to Claude Code's built-in summary on errors or insufficient reduction. See hooks/README.md for configuration and the Claude Code 2.1.274 type reference.

Install in Claude Code

Function hooks are an early-access Claude Code feature (2.1.274+), so the opt-in flag must be set wherever Claude Code runs, e.g. in ~/.claude/settings.json:

{ "env": { "CLAUDE_CODE_ENABLE_FUNCTION_HOOKS": "1", "TYPESAFE_API_KEY": "<your key>" } }

Then add this repository as a plugin marketplace and install the plugin, either from the shell or as slash commands inside a session:

claude plugin marketplace add tamaratran/fast-jev-compaction
claude plugin install fast-jev-compaction@fast-jev-compaction

The install prompts for the plugin options (API key, thresholds, truncateHeadChars, …); leave them at their defaults to use TYPESAFE_API_KEY from the environment. Restart Claude Code or run /reload-plugins. From then on /compact (and auto-compaction) goes through Jev: the toast reads fast-jev-compaction: kept N/M messages, no summary (…) when the pruned history replaced the built-in summary, or fallback to built-in summary (…) when Jev could not remove enough (short sessions, or when it fails).

To run from a checkout without installing: CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude --plugin-dir . from the repository root. No publishing step is required; the marketplace is just the repo's .claude-plugin/marketplace.json.

Development

npm install
npm run typecheck        # library + hook
npm test
npm run build
npm run validate:plugin  # claude plugin validate
TYPESAFE_API_KEY="$(cat ~/.typesafe_key)" npm run demo

The unit tests use a fake Jev and never contact TypeSafe. The demo is the live network check.

Animated demo (macOS)

demo/JevDemo is a small native SwiftUI app that plays a scripted, dramatized version of the compaction flow inside a Claude Code-style terminal: the tool calls of a canned transcript are scored, results and calls Jev lets go turn red and collapse away, and the rest stays verbatim. It never calls the API; it exists to be screen recorded.

demo/JevDemo/build.sh   # builds demo/JevDemo/build/JevDemo.app and launches it

Press space in the app to replay from the start.

fast-jev-compaction (tamaratran/fast-jev-compaction)

Searching Database…

这是什么?一个 Claude Code 插件,也可以当作 npm 库使用。它用 Jev 的决策来替代上下文压缩时生成的摘要:每一次工具调用及其结果都会在快速请求中打分,过时的内容被丢弃或截断,而保留下来的内容一字不差地原样保留。
那它到底是 Claude Code 插件还是 npm 库?两者都是。这个仓库既是一个 npm 包(src/),也是一个 Claude Code 插件(hooks/、.claude-plugin/),插件利用这个包,用经过裁剪的原始消息来替代 /compact 和自动压缩生成的摘要。
我怎么在 Claude Code 里把它跑起来?Function hooks 是 Claude Code 的抢先体验功能(2.1.274+):在 Claude Code 运行的地方设置启用开关(比如 ~/.claude/settings.json),把这个仓库添加为插件市场,安装插件,然后重启 Claude Code 或运行 /reload-plugins。
它需要 API 密钥或其他服务吗?需要 Jev 的 TypeSafe API 密钥:apiKey 默认取 process.env.TYPESAFE_API_KEY,安装时提示项全部保持默认就会使用这个环境变量。README 特别警告,千万不要把密钥提交到代码仓库,也不要写进源码文件里。
有什么需要留意的?只有工具调用及其结果才会被清理——普通文本消息永远不会被删除或缩短。Token 大小只是按字符数估算的,并不是真正的分词器;而且某条内容被留下并不等于删掉它一定安全:助手随时可以重新运行那个工具。
运行 /compact 之后,提示消息会告诉我什么?当裁剪后的历史替换了内置摘要时,会显示 'fast-jev-compaction: kept N/M messages, no summary (...)';当 Jev 删不掉足够多的内容时(比如会话本身就很短,或者处理失败),会显示 'fallback to built-in summary (...)'.
我不安装,直接从代码检出目录里运行行不行?可以:在启用 function-hooks 开关的前提下,从仓库根目录运行 claude --plugin-dir . 即可。不需要发布步骤,所谓的插件市场其实就是仓库里的 .claude-plugin/marketplace.json。
运行结束后 result.stats 里有什么?包括处理前后的消息数和字符数、各种原因的决策数量、按估算 token 计算的状态大小、用到了哪个拟合阶段,以及请求的次数。
JevDemo 这个应用是干什么的?一个 macOS 上的原生 SwiftUI 小应用,它在仿 Claude Code 风格的终端里播放一段按脚本演出的压缩流程:Jev 放弃的调用和结果会变红并折叠消失,其余内容则原样保留。它从不真正调用 API;按空格键可以重新播放。
Creator
tamaratran
Size
0.2 MB
Stars
7.7K
Updated
CommitSep 17
Age
1mo
Commits · 90 Days