用 Jev 给 Claude Code 省上下文:Winnow 在门口拦,fast-jev-compaction 在压缩时清

测试环境:Windows 11,Claude Code(需 2.1.274+,函数钩子抢先体验功能),winnow 0.5.0、fast-jev-compaction 0.3.0,2026-09-27 ~ 10-10。

Claude Code 的上下文是消耗品:一次 Read 一个大文件、一次 grep 满屏结果,几万 token 就进去了,而其中大半跟当前任务无关。

TypeSafe 的 Jev 能回答”这个东西跟当前任务有关吗”这类判断题,而且一次只要一秒左右、成本几乎可以忽略。于是有人拿它做了两个 Claude Code 插件,我装了跑了 13 天:

  • Winnow:东西进上下文之前先过一遍筛子
  • fast-jev-compaction:压缩上下文时,用 Jev 删掉不再需要的工具调用,而不是让模型写一份摘要

先说清楚两者的分工

Winnow fast-jev-compaction
什么时候动手 每个大的 Read / Bash / Grep 结果进上下文之前 上下文用到 60% 触发压缩时(/compact 也走它)
动什么 把结果里”不相关”的块换成一个占位 stub 删掉历史里”不再需要”的工具调用和结果
不动什么 磁盘文件、命令本身、Claude Code 自己的会话记录 你和助手说的话,原文、原顺序全部保留
对谁提问 Jev:这一块对当前任务需要吗? Jev:这次调用还要留吗?它的完整输出还要留吗?

一个在门口拦,一个在清库存。都依赖同一个开关 CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1,也都需要一把 TypeSafe 的 Key。


Winnow:给每个大输出过一遍筛子

它怎么工作

每个超过 1500 字的 Read / Bash / Grep 结果,会被切成约 25 行一块,每一块向 Jev 问一个问题:”对当前任务,这一块需要吗?”拿到概率后:

  • 概率低于 0.1 → 整块隐藏,换成 stub
  • 介于 0.1 ~ 0.5 之间(不确定)→ 保留
  • 判断这份输出里有报错 → 一块都不隐藏

stub 长这样(官方示例):

1
2
3
[winnow] Lines 41-188 (148 lines) hidden: judged unlikely to matter for the current task (relevance <= 0.22).
[winnow] Summary: Argparse setup for the --export and --format flags, plus the license header.
[winnow] Full text cached as key a1b2c3d4e5f6. Call winnow_recall(key="a1b2c3d4e5f6", start=41, end=188) if you need it.

隐藏的原文完整存在本地 ~/.winnow/cache/,助手需要时调 winnow_recall 取回,所以是”暂时不花 token”,不是”丢了”。

它还偷偷做了另一件事

Winnow 有第二个钩子,在你每次提问时触发:把项目的记忆文件拿去和你的问题比对,挑最相关的几份(默认最多 3 份、共 8000 字符)直接塞进这轮对话,省得助手再去 Read。

13 天的真实数据

winnow stats 的输出(2026-09-27 装上,到 10-10):

指标 数值
被判断的输出 2286 次
实际被改写(隐藏了内容) 143 次(6.3%)
原样放行 2143 次
估算省下的 token 约 9.8 万(39 万字符)
判断本身的耗费 约 885 万输入 token,折合约 $0.37(估算)
单次判断平均耗时 1.14 秒
提问时注入记忆文件 234 次
被隐藏后又被召回 45 / 143(31%)

放行的 2143 次,原因分布是:

为什么没动 次数
Jev 认为每一块都需要 1761
值得隐藏的比例太低(不足 20%,不值得改写) 163
输出里有报错,按规则不动 218
判断请求本身失败 1

怎么读这组数字

它很保守,这是设计出来的。
94% 的输出原样放行,真正动手的只有 6%。这就是上面那两条安全规则的结果:不确定就保留、有报错就不碰。

便宜,但收益不大。
13 天省下约 10 万 token,判断花了不到 4 毛美元。成本几乎可以忽略,但也别指望它让你的额度翻倍。

31% 的召回率不能忽略。
143 次隐藏里有 45 次后来被助手要了回去,约三分之一的判断被”撤销”了。召回不一定意味着判错(助手也可能只是谨慎),但每次召回都多一轮工具调用,说明筛子还不够准。官方的建议是先开 WINNOW_MODE=shadow 跑一周,只记录不改写,看决策合理了再切到 active。

每个大输出多等一秒。
按平均耗时粗算,13 天累计约 43 分钟,不显眼但确实存在。

几个细节

  • 后台有个常驻小服务(127.0.0.1:47311)负责调 Jev,空闲 45 分钟自动退出。每次调用的开销是 16 毫秒,而不是每次起一个 Python 进程的 381 毫秒。
  • 我关掉了摘要(WINNOW_SUMMARY=0)。因为摘要要用 Anthropic 的 API Key,订阅登录的 Claude Code 没有这个。关掉后一切照常,只是 stub 里没有那段话。
  • 如果没有 Jev 的 Key,可以设 WINNOW_JUDGE=adapter,改用 Claude Haiku 4.5 来回答同样的问题,但概率没有校准过,且走 Anthropic 账户计费。
  • 常驻服务会占住启动它那个会话的工作目录:要删那个目录,得先 winnow serve --stop。

fast-jev-compaction:用”删”代替”写摘要”

传统压缩的问题

Claude Code 自带的压缩,是让模型把前面的对话写成一份摘要。摘要是有损的:一个文件路径、一条确切的报错、一个约束、一条命令,都可能在摘要里消失,而它们恰恰是后面需要的。

这个插件换了个思路:不改写任何东西,只删。 助手和你的话原样保留、顺序不变,只有”工具调用 + 工具结果”才是候选,Jev 判断哪些已经不再需要。

它怎么决定删什么

  1. 每个 tool_use 和它的 tool_result 按 id 配对。第一条消息和最新的 6 条消息被钉住,不动。
  2. 发给 Jev 的 state 是到目前为止的整段对话,旧的在前;每个工具结果被替换成一行说明(如 ok, 4213 chars (omitted)),工具的输入和所有文字原样保留。超过 25k token 的话分阶段裁剪。
  3. 对每一个没被钉住的调用,问两个是非题:这次调用还要留吗?它的完整结果还要留吗?
  4. 按阈值 0.5 决定:
判断 处理
结果要留 调用和结果都保留
结果不要留、但调用要留 保留调用,结果只留开头 300 个字符加一行说明
都不要留 调用连同结果整个删掉

什么时候触发、什么时候放弃

  • 上下文用到 60% 时自动请求压缩,手动 /compact 也走它。
  • 这些情况会退回 Claude Code 自带的摘要:Jev 请求失败、返回格式不对、没有 Key、历史太长塞不进 25k、或者**预计删掉的比例不到 25%**。
  • 结果会用一个提示告诉你:kept N/M messages, no summary (…) 表示这次用的是删减版,fallback to built-in summary (…) 表示退回了。

它做不到的

  • 只能删工具调用和结果。一个纯聊天、几乎没有工具调用的长对话,它帮不上忙,会直接退回内置摘要。
  • 旧的工具结果删掉后,信息就真没了,助手要么靠剩下的 300 个字符,要么重新执行一遍工具。
  • token 数量是估算出来的,不是真分词。
  • 每次请求都会把完整的 state 重发一遍,历史越接近上限,每个批次的开销越高(但按 Jev 的价格,25k token 一次也就约 0.1 美分)。

我这边的实测

做法:准备 5 个互不相关的日志文件(各约 9KB),连续 5 轮让助手 Read + Grep 找出一个标记值,然后手动 /compact。

项目 结果
工具调用 10 次,其中最新 2 次被钉住不动,其余 8 次交给 Jev 判断
Jev 的判断 8 次**全部判”可删”**(调用保留概率 0.090.11,结果保留概率 0.050.08)
压缩结果 消息 35 条 → 19 条,缩减 **91%**(插件自报),没有生成摘要
耗时 一次 Jev 请求 1.1 秒,整个压缩 1.1 秒;Claude Code 自带的摘要流程完全没有运行
发给 Jev 的内容 约 2043 token,一次请求搞定

留意这是最理想的场景:5 轮问题互不相关,每轮的答案都已经写在助手的回答里,被删掉的只是用完了的原始日志。更难的情况是后面的轮次还要回头用前面的输出,这种我没测,不能从这次结果外推。


装法和判断是否生效

两个都要先在 ~/.claude/settings.json 里打开函数钩子:

1
{ "env": { "CLAUDE_CODE_ENABLE_FUNCTION_HOOKS": "1" } }

然后各自 claude plugin marketplace add + claude plugin install。

判断有没有真正生效:看会话的调试日志(~/.claude/debug/<会话 id>.txt)里有没有插件的 loaded 记录,比如 fast-jev-compaction@fast-jev-compaction loaded … events: session.compact,turn.complete。命令行 claude -p --debug 的 stderr 是空的,没法判断。函数钩子类插件新开会话才加载。

Winnow 另外有两个自查入口:winnow doctor 检查环境,~/.winnow/decisions.jsonl 里每个决策是一行,可以看到每一块的概率。

在 Git Bash 里测试 claude -p "/compact" 要加 MSYS_NO_PATHCONV=1,否则 /compact 会被改写成 C:/Program Files/Git/compact。


小结

值不值得装
Winnow 低成本、保守、收益有限;31% 的召回率说明还有打磨空间,建议先 shadow 模式观察
fast-jev-compaction 思路更干净(只删不写);实测一次,8 次工具调用全部删除、消息减少 91%,但那是理想场景,且只对工具调用多的会话有用

两个都建立在同一个前提上:Jev 一次能回答几十个”需要吗”,而且足够便宜。如果你的会话本来就不长,这两个插件的价值很有限。