OpenCode DCP 插件是什么?拆解 Dynamic Context Pruning 的机制与取舍
用 OpenCode 跑一个稍微复杂点的任务——比如在一个不熟悉的代码库里修一个跨三个文件的 Bug——你会发现一个很熟悉的节奏:前 20 轮顺风顺水,模型记得你之前说的每一句话;30 轮之后它开始重复读同一个文件;40 轮的时候它忘了你 10 轮前给的约束条件;然后某一轮,屏幕上闪过一段压缩摘要,模型说”根据之前的讨论……”——你知道它已经把你之前说的大部分东西都忘了。
这就是上下文窗口的现实。而 DCP(Dynamic Context Pruning)这个插件,试图用一个很有意思的思路来缓解它。
先说 OpenCode 自己是怎么处理这件事的
要理解 DCP 在做什么,得先知道 OpenCode 原生的上下文管理有多”简单粗暴”——不是贬义,是真的简单。
OpenCode 有两道防线。第一道是裁剪工具输出:从旧到新扫一遍工具调用的返回值,最近约 4 万 token 的输出不动,更老的替换成占位符。只动工具输出不动消息本身,伤害最小,但省出来的空间也有限。
第二道是全量压缩(Compaction)。当 token 用量逼近窗口上限(已用 > 上限 - 2万),OpenCode 掏出模型,让它把整个对话历史浓缩成一份结构化摘要——目标是什么、做了什么、下一步干什么——然后从这份摘要重新开始。
"compaction": {
"auto": true,
"keep.tokens": 15000,
"buffer": 20000
}
三个配置项,干净利落。
但问题也在这个”干净利落”里。
它只在快满的时候才压缩。 上下文从 10% 涨到 90%,这中间的漫长过程里,模型一直在一堆越来越陈旧的信息里找你最新说的那句话。等到终于触发 Compaction 的时候,上下文已经膨胀到模型注意力最分散的程度——偏偏这时候你要它写出一份高质量的摘要。
它是全量的。 压缩不分青红皂白,整个历史都变成一份摘要。你 5 轮前精心写的一段需求描述和 30 轮前的一次无用的 ls 输出,享受同等待遇。不能说”这块任务做完了,压缩掉吧,但那块正在做的别碰”。
嵌套压缩会稀释信息。 长会话中 Compaction 可能触发多次。对一份已经是摘要的内容再做摘要,细节不可避免地逐层丢失。这件事听起来理所当然,但当模型在第 60 轮需要引用第 5 轮的一个具体错误信息时,那条信息早就被摘要了两三遍,剩下的只有”之前遇到过一个相关的错误”这种程度的模糊描述。
DCP 的核心想法:让模型自己来
DCP 的思路说穿了就一句话——既然模型最清楚哪些上下文还有用、哪些已经没用,那干脆让模型自己来决定什么时候压缩、压缩哪些内容。
它通过 OpenCode 的插件系统工作。OpenCode 的插件可以在请求发给 LLM 之前通过 hook 拦截和修改消息列表——DCP 就是利用这个机制,在发请求前把它认为可以压缩的内容替换成占位符。你本地的实际会话历史不会被动。
安装就一行:
opencode plugin @tarquinen/opencode-dcp@latest --global
装完之后,模型的工具列表里多了一个叫 compress 的工具。接下来,就是让模型在合适的时机自己调用它。
Compress:不是等窗口满了再动手
想象你在做一个分三步的任务:先探索代码结构,再修改实现,最后写测试。在原生 Compaction 下,这三个阶段的所有细节都堆在上下文里,直到窗口满了一刀切。
DCP 的做法不同。当模型完成了代码探索阶段,它可以主动调用 compress,把探索过程中读过的文件内容、翻过的目录结构、走过的弯路全部压缩成一份技术摘要——“代码库使用 monorepo 结构,auth 模块在 packages/auth,依赖 Redis 做 session 存储……”——然后轻装上阵进入实现阶段。
Compress 有两种模式。Range 模式是默认的,压缩一段连续的消息范围。有意思的地方在于嵌套处理:如果这次压缩的范围覆盖了之前已经压缩过的内容,旧摘要会嵌套进新摘要里,而不是被丢弃重写。信息是分层保留的——第一层摘要保留了原始细节的 80%,第二层保留了第一层的 80%——递减但不是归零。这比全量压缩每次从头概括要好不少。
还有一个实验性的 Message 模式,可以精确到对单条消息做压缩。比如一次 cat 输出了一个 500 行的配置文件,你可以只压缩这一条,不影响周围的消息。像外科手术一样精确,但目前还标着 experimental。
当然,不是什么都能压缩。DCP 有一堆保护机制:write、edit 这类修改文件的工具输出默认不能压缩(你总不希望模型忘了自己刚写了什么);最近 4 轮的所有内容不动;用户消息可以选择性保护。这些保护是合理的——你当然不想模型压缩掉你 3 轮前下的指令。
Deduplication:同一个文件读了三遍,只留最后一遍
长会话里有个很常见的浪费:模型读了一个文件,做了些修改,然后又读了一遍看结果,过了 10 轮发现它又读了一遍。三次读取,参数完全相同(或者文件没变但位置不同),但三份完整内容都躺在上下文里占空间。
DCP 的去重逻辑很直接——识别参数完全相同的重复工具调用,只保留最近一次的输出。为了减少对 Prompt Cache 的影响,去重只在 compress 运行时顺带执行,不会单独触发。
Purge Errors:清理失败调用的废弃输入
这个功能解决的是一个不起眼但确实存在的问题。工具调用失败的时候,错误信息通常就一行——“permission denied”、“file not found”。但调用本身的输入可能很大,比如一整段要写入文件的代码。错误信息有价值(模型需要知道哪里失败了),但那段写失败的代码还留在上下文里就纯粹是浪费了。
DCP 在错误发生 4 轮后自动清理这些废弃输入,只保留错误消息本身。一次两次看不出差别,但在反复试错的调试会话中,这些废弃输入累积起来可以有几万 token。
模型不主动压缩怎么办?Nudge 系统
把压缩权交给模型听起来很美,但现实是——模型有时候不主动。它可能忙着写代码,根本没”想起来”要调用 compress,上下文就这么悄悄涨上去了。
DCP 用 nudge(提醒)来解决这个问题。它在三个时机往系统提示里注入提醒文本:
- 上下文超过 5 万 token(可配置的
minContextLimit)后,每隔 5 次 API 请求提醒一次 - 距上次用户消息超过一定轮数后提醒
- 模型连续自主运行超过 15 轮没有用户输入时提醒
提醒的措辞可以配成 "soft" 或 "strong"。但注意——这些只是提醒,不是强制。模型收到提醒后可以选择现在不压缩继续干活。这个设计是对的,因为有时候模型正在执行一个多步操作的中间,这时候打断去压缩反而影响工作质量。
(顺带一提,这些提醒的文本是可以自定义的。开启 experimental.customPrompts: true,DCP 会在 ~/.config/opencode/dcp-prompts/ 生成默认的提示词文件,你可以在 overrides/ 目录下放同名文件来覆盖。如果你觉得 DCP 压缩得太激进或太保守,先试试调提示词,比改配置参数更直接。)
所有人都在问的问题:Prompt Cache 怎么办?
每次聊到上下文压缩,都绕不开 Prompt Cache。原因很简单——LLM 的 Prompt Cache 基于逐字节前缀匹配,你改了中间任何一条消息的内容,从那个位置往后的缓存全部作废。DCP 压缩消息就是在改中间的内容。
这是一个真实的代价。DCP 作者给出的测试数据:启用 DCP 后缓存命中率约 85%,不启用约 90%。
但这个对比需要放在更大的图景里看。
一方面,缓存 miss 意味着你多付了一些缓存重建的成本。另一方面,DCP 声称能把长会话的整体 token 用量降低 50-70%。如果你的上下文从 20 万 token 被压缩到 8 万,即使这 8 万全部重新缓存一次,你省下的 12 万 token 的输入成本也远超缓存 miss 的代价。
不过这道算术题不是在所有场景下都成立:
DCP 明显划算的情况: 按 token 计费的 API 用户、上下文已经很长、使用不支持 Prompt Cache 的提供商(比如 Cerebras 按请求计费)。这些场景下 DCP 省 token 是净收益,基本没有 cache miss 的惩罚。
需要犹豫的情况: 短会话(DCP 还没省出多少就先 miss 了一次)、Anthropic 订阅用户享受 1 小时缓存 TTL(缓存的经济价值更高)、或者你只用模型做几轮问答而不是跑长任务。
Hacker News 上有人对此提出过更尖锐的批评:DCP 展示了 token 缩减的数据,但没有做过系统性的 eval 来证明”压缩后模型输出质量不受影响”。省了 token 但模型变笨了,这交易值不值?老实说,这个问题不只是 DCP 的——所有上下文压缩方案都有这个盲区。
和 OpenCode 原生方案的关系
两者不是二选一。DCP 运行在 Compaction 之前。如果 DCP 持续压缩把上下文控制在合理范围内,原生 Compaction 可能永远不会触发。但如果 DCP 没能及时压缩(模型没理会 nudge、或者任务确实需要那么多上下文),原生 Compaction 仍然是兜底的安全网。
用一个不太精确的类比:原生 Compaction 像 OOM Killer——等内存真快爆了才出手,一来就杀掉最大的进程。DCP 更像是你自己写的内存管理——在合适的时机主动释放不再需要的对象。两者可以共存,只是你希望后者足够聪明,让前者永远不需要出场。
一些值得注意的细节
DCP 的开发重心已经转移。 作者把新功能放到了 Sleev——一个本地代理,同时支持 Claude Code 和 OpenCode。DCP 本身还维护,但别指望它会频繁更新新特性了。如果你在意长期演进方向,关注 Sleev 可能更值得。
已知有一个阈值 Bug。 有用户报告上下文占用才 4% 就触发了压缩,即使 minCompressionThreshold 配成了 15%。系统级逻辑绕过了用户配置。遇到了看一下 GitHub Issues。
配置项非常多。 我前面只挑了关键的几个讲。DCP 的完整配置包括 20 多个参数,支持按模型设置不同阈值、保护特定文件模式、保护特定工具输出等等。如果你要认真用,直接看 GitHub README 会比在这篇文章里找到更新的信息。
该不该装
最后给一个简单的判断框架。
如果你的 OpenCode 使用场景是长任务、多阶段、按 token 计费,DCP 值得一试。它不是那种装上就能忘掉的东西——你需要花时间调 maxContextLimit 和 minContextLimit 来匹配你常用模型的上下文大小,可能还需要调 nudge 的激进程度。但调好之后,长会话的体验和成本确实能有明显改善。
如果你的使用场景是短对话、快速问答、或者用订阅账号不太关心 token 成本,OpenCode 原生的 Compaction 已经够用了。多装一个插件、多 20 个配置项、多一层压缩逻辑——这些复杂度未必能换来你感受得到的收益。
参考:Opencode-DCP/opencode-dynamic-context-pruning · OpenCode Compaction 文档 · OpenCode 插件系统 · Context Compaction Research