BLOG
fast-jev-compaction 拆解:让 Claude Code 的压缩放弃写摘要
技术拆解:解析 AI 技术框架——说明、分析、技术评估、价值判断、落地使用。作者:永亮
2026 年 9 月 19 日,tamaratran/fast-jev-compaction 在 GitHub 上有 3,206 个 star(截至 9 月 19 日)、TypeScript 写成、MIT 协议,仓库创建于 2026 年 9 月 17 日——两天前。npm 包版本 0.2.0,Claude Code 插件清单 0.3.0,全仓 25 个文件,源码加钩子加测试合计约 1850 行,核心 src 约 960 行,最大的单个文件 compact.ts 309 行。Claude Code 的上下文压缩(compaction)默认让模型写一段摘要替换掉老历史,摘要有损——文件路径、精确报错、约束条件都可能丢;这个插件不写摘要,只做删除,被删掉的内容要么整对消失,要么原文保留。这篇文章按六件事拆:是什么、痛在哪、机制怎么转、源码里值得看的三处、边界与代价、值不值。
一、这是什么
fast-jev-compaction 是一个 Claude Code 插件:接管上下文压缩这一步,把「写摘要」换成「逐对裁决」。压缩后的历史里不再有模型复述的段落,每一个工具调用的下场只有三种——完整保留、只留调用不留结果、调用和结果一起消失。判断依据不是一段文字,而是两个概率。
要理解它,得先认识它依赖的那个模型。Jev 是 TypeSafe 公司的商业模型,产品线叫「system one」:它不逐字生成文本,而是对给定的一串问题各输出一个概率,供 agent 快速分支。哪句话该接哪句话、哪个结果该留在上下文里,这类「是或否」的岔路口它直接给数。Jev 本身是三天大的新事物——这三天里 GitHub 上冒出了至少 5 个 jev 系仓库,browser-use/jev-ultrafast 拿到 5,487 个 star 是其中涨得最猛的之一。一个模型带出一串配套项目,说明「用概率模型替 agent 做轻量决策」这个方向正在起风,fast-jev-compaction 是这股风里最完整的一个工程样本。Jev 的内部设计 TypeSafe 没有公开,本文只讲这个插件怎么用它的接口,不替它编架构。
二、它要解决的真痛点
长会话 agent 的上下文窗口有限,快满时必须压缩。主流做法是 LLM 写摘要:让模型通读老历史,写一段浓缩版替换原文。摘要的本质是让模型替未来的自己记笔记——记什么、丢什么由一个生成过程决定,出错没有痕迹。文件路径可能被改写成近似路径,精确的报错信息可能被概括成「有个报错」,用户交代过的约束(「别动这个文件」)可能在浓缩时丢掉。更麻烦的是压缩之后没有任何手段核验丢了什么,等到三小时后模型基于错误摘要改错了文件,你很难把锅追回到那次压缩。
这个插件对痛点的拆解很冷静:上下文的大头不是聊天文本,是工具调用和工具结果——一次文件读取几千字符,一次测试输出上万字符,几十轮下来它们吃掉九成空间。而真正需要判断的,是「这条调用对接下来的事还重要吗」。这是一道选择题,不是一道写作题。选择题可以交给输出概率的模型答,答案还能逐条留档。写作会编,选择题至少诚实。
三、机制:从配对到重建
3.1 配对与 pinned
第一步是把对话拆成可裁决的单位。每个 tool_use 和对应的 tool_result 按 tool_use_id 配成一对——删除时调用和结果同进退,重建后不会出现一个没有调用的悬空结果。第一条消息和最新 preserveRecentMessages 条(默认 6)被标记为 pinned,永不处理,保证会话的开头和当下永远完整。
3.2 构建发给 Jev 的 state
发给 Jev 的 state 是完整对话,最老在前。所有工具结果被替换成一行短注(形如 ok, 4213 chars (omitted)),告诉 Jev「这里有一次成功的读取,原文 4213 字符」;工具输入完整序列化进 state;用户和助手的文本全量保留,不做摘要。state 自己也有体积上限 maxStateTokens(默认 25000),超限时分阶段降级:工具输入依次截到 1000、200、60 字符;长文本保留头 400 字符和尾 150 字符;还不够就从最老的消息开始折叠成 [… N chars omitted …],老的工具调用压成一行(形如 t12 Read file_path=src/a.ts → ok 480ch);pinned 的消息最后才动。所有阶段走完还装不下,直接抛异常,不硬塞。token 数是字符估算:每 6 个字母约 1 token,数字减半,其他符号各约 1 个——不是真 tokenizer,便宜,也不引入分词依赖。
3.3 两个 noul 问题与分批
每个非 pinned 的调用,插件问 Jev 两个问题。call_X:「知道这个调用被发起过、带着什么输入,对接下来的事还重要吗」。result_X:「这个结果需要原文保留吗,重跑一遍工具替代不了吗」。两个都是 noul 类型——Jev 不回文字,只对每个问题吐一个概率。
问题按体积分批。maxRequestTokens 默认 30000(Jev 单请求上限 32000),减去 state 占的额度再减 20 个 token 的请求信封开销,剩下的预算就是一批能带的问题数。state 每批都全量重发,批内所有问题合并进一个请求,各批并发发出、答案合并。state 越大,每批能塞的问题越少,极端情况下每几条问题就要发一个请求——这是设计自带的成本,边界一节展开。
3.4 决策与重建
决策规则一行能写完。decideCall 按顺序判:pinned 直接保留;keepResult 大于等于阈值(默认 0.5)整对保留;否则 keepCall 大于等于阈值,保留调用本身、结果截前 truncateHeadChars(默认 300)字符,并附一行说明——说明里写清截掉了多少字符、是不是报错、需要可以重跑工具;两个概率都不过,调用和结果整对删除。
重建由 applyDecisions 完成:内容全丢的消息整条移除,未动的消息原样返回(同一个对象,零拷贝),被部分修改的消息在原地替换。输入输出全程是结构化的决定加可复核的统计(每类决定的计数、压缩前后字符数、state 的估算 token 数、用了第几档降级、发了几个请求),不是一段散文。
四、源码里值得看的三处
4.1 compact.ts 的分批与决策函数
compact.ts 309 行,是仓库最大的文件,主干就四个函数。questionsFor(compact.ts:56)给每个调用生成两道 noul 题,题面把工具名、调用 id、结果字符数都编进去,Jev 判的时候有据可依。batchCalls(compact.ts:73)做分批,预算算法透明:maxRequestTokens 减 state 再减 20,塞得下就塞,单个调用的问题本身就超预算说明 state 太大,直接抛异常报错里写明「state 占了 30000 里的大约多少」。decideCall(compact.ts:101)是全书最短的一段——pinned、keepResult 阈值、keepCall 阈值、else 删除,四行。applyDecisions(compact.ts:149 起)负责重建,注释把不变量写得很白:删除的调用连同结果消失,删除结果保留有界的头部和说明,丢光内容的消息移除,没动的消息原对象返回。
4.2 request.ts 的端点与 noul 解析
request.ts 只有 80 行,是接口契约的全部。SYSTEM_ONE_URL 指向 https://api.typesafe.ai/v1/systemone,默认模型名 jev-latest。buildJevRequest 把 model、state、questions 打包成一个 POST。parseJevResponse 对返回体做苛刻校验:HTTP 不 ok 抛错,JSON 解析失败抛错,缺 answers 字段抛错。noulAnswer 取单个答案的 noul 字段,缺字段、不是数字、不是有限数,一律抛。整个文件没有一处静默容错——所有失败都变成异常往上传,由调用方决定兜底。
4.3 hook 的触发条件
hooks/fast-jev.ts 是接到 Claude Code 的薄适配层。function hooks 是 Claude Code 2.1.274+ 的早期功能,要先在 settings.json 里开 opt-in;开启后插件在 turn.complete 时被回调,检查当前上下文使用率,到 compactAtPercent(默认 60%)才触发压缩。触发后先预估一轮,压缩率不到 minReductionRatio(默认 25%)就不替换历史,白嫖一轮判断不划算的事不干。任何异常——Jev 服务挂了、答案畸形、缺 TYPESAFE_API_KEY、state 装不下——都不硬撑,回落到 Claude Code 内置的摘要压缩。配置可以全走插件的 userConfig:阈值、保留条数、截断长度、模型名逐项可改。
五、边界与代价
官方 Limitations 四条,照录口径。第一,只处理工具调用,文本消息在输出端永不删短(它们只在 Jev 看到的 state 里被缩写)——如果你的上下文是被聊天文本撑大的,这个插件帮不上忙。第二,token 数是字符估算,不是真 tokenizer,预算判断有偏差。第三,原话值得整句记住:「一个概率不是可以安全删除的证明,assistant 总能重跑工具」——0.5 阈值不是安全线,是经验线。第四,state 每个请求全量重发,历史接近上限时每几条问题就要一个请求,token 成本随历史长度线性上涨,重度用户这笔账要会算。
存疑三条,只写存疑不写死。决策质量完全押在 Jev 这一个商业模型的概率校准上,仓库没有任何集成测试的基准数字,README 没有 benchmark 表,压缩得好不好没有第三方可核的度量。插件生态和 Claude Code 版本紧耦合,function hooks 本身是 2.1.274+ 的早期功能,接口一动插件就得跟着动。仓库只有两天历史,生产环境的长会话表现没人验证过,两天 3,206 星涨的是概念热度,不是稳定性背书。
六、值不值与适用人群
这是「上下文压缩」这个老问题的一个新解法——把摘要写作题改成选择题,代价是把决策权外包给一个商业概率模型。适合的人很清楚:天天用 Claude Code 跑长会话、被摘要丢上下文坑过的人,装一个试试的成本很低,npm 包加环境变量的事;做 agent 工程、研究上下文管理的人,这个仓库 1850 行一晚上能读完,是把概率模型塞进 agent 决策环路的干净样本。不适合的人同样清楚:不想把对话数据发到第三方 API 的人——state 是全量对话,含你的代码和报错;会话本身不长、内置摘要够用的人;需要可验证压缩质量承诺的团队,README 没有 benchmark,这个承诺目前给不出。
作者观点:如果不用 Jev,这个思路能不能移植?大概率可以。noul 问题的本质是给一组选项打分,任何能输出 logits 的小模型都干得了——本地跑一个 3B 级别的小模型,对 call_X 和 result_X 两个问题输出「是」的概率,替掉 Jev 的远程调用,数据不出机,调用成本归零,代价是校准质量没人保证,阈值得自己调。这个项目真正值得带走的未必是插件本身,而是这个提问方式:压缩上下文不必让模型写作文,让它做选择题,再按概率阈值执行。
结论
fast-jev-compaction 用约 1850 行代码回答了一个问题:上下文压缩一定要写摘要吗?它的答案是否定的。工具调用占了长会话上下文的大头,「留还是删」是选择题,可以让 Jev 这种输出选项概率的模型来答。源码里这个判断落得很具体:调用按 tool_use_id 配对,第一条和最新 6 条 pinned 永不处理,state 完整构建、工具结果换成短注、超限分四档降级,每个调用问两道 noul 题,按 30000 token 预算分批并发,0.5 阈值裁决 keep / drop_result / drop_call 三档,重建时保证没有悬空结果;hook 侧在 turn.complete 检查 60% 使用率触发,压缩率不足 25% 就放弃替换,失败回落内置摘要。代价也明白:token 是估的,概率不是删除安全的证明,state 全量重发,质量没有基准数字。两天 3,206 星,涨的是概念热度,它和 Jev 这个三天大的新事物绑在同一条船上——对做 agent 上下文管理的人,这是一篇值得读的源码;对普通用户,等它跑过几个版本再说。
参考来源
- tamaratran/fast-jev-compaction README(定位、配置表、Limitations、Claude Code 插件说明、function hooks 版本要求)
- 源码(本地 clone):src/compact.ts(DEFAULT_OPTIONS、questionsFor、batchCalls、decideCall、applyDecisions)、src/state.ts(collectToolCalls、estimateTokens、fitState、INPUT_CHARS=[1000,200,60]、TEXT_HEAD=400/TEXT_TAIL=150)、src/request.ts(SYSTEM_ONE_URL、DEFAULT_MODEL、buildJevRequest、parseJevResponse、noulAnswer)、hooks/fast-jev.ts(compactAtPercent、minReductionRatio、异常回落)
- 仓库数据:3,206★、MIT、创建于 2026-09-17、npm 0.2.0、插件清单 0.3.0、25 文件约 1850 行(截至 2026-09-19)