Skip to content

dsh-tool-compact

Verified

dsh-tool-compact · v0.3.0 · MIT

DeepSeek Harness 插件:给模型一个可自行调用的 compact 工具,在它准备开启新项目或新步骤时当场压缩较早的历史,并把摘要作为工具结果返回。

Install

dsh plugin add dsh-tool-compact

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Published to npm without a public repository. Inspect the package contents before installing.

Tags

Readme

dsh-tool-compact

2026-10-24 当天停用又挂回。 当天一度下线,想全交给「戏子」(dsh-actor)开新会话承担; 按用户意见挂回,因为同一件事中间要腾空间、又不想换会话时只有它能救场。 现在的分工:同一件事中间腾地方用 compact;换阶段、换身份用 actor。 两边判据都写在协作模式系统提示词的「上下文与交接」一节里。

给模型一个能自己调用的压缩工具:它当场把当前会话较早的历史合成一条摘要, 摘要直接作为这次工具的结果回到模型手里。

为什么要有它

DSH 自带的压缩只有三条触发路径,没有一条由模型决定:

  • 自动阈值:@deepseek-ai/dsh-compaction-basic 在每次请求前量一次 token, 越过 floor(min(窗口 × 0.8, 窗口 − 输出预留 − 65536)) 才压;
  • 上下文溢出:模型报 CONTEXT_WINDOW_EXCEEDED 后强行压一次;
  • 手动命令:用户敲 /compact,且只在空闲时可用。

@deepseek-ai/dsh-compaction 的 README 明确写着「不会注册面向模型的压缩工具」, 开发备注也把「面向模型的压缩工具」列为尚未决定的事项。这个插件补的就是这一块。

光有工具不够:LangChain Deep Agents 的实测里,模型拿到压缩工具后一次都没主动用过。 所以本插件把时机判断直接写进工具描述——工具描述本身就是模型可见的提示词, 不用改系统提示。这是 Deep Agents 那篇的核心做法。

判据为什么放在「开头」而不是「结尾」

前两版都把时机写成「一段活干完就压」,一版写成可选的优化,一版写成硬性要求, 两版都一次没被调用过。根因不在措辞软硬,在判据本身错了:

「这段活算不算结束」要由用户拍板,模型确认不了。 只要判据是结尾, 模型就会一直停在「还没结束吧」的状态里自我否决,于是永远不动手。

改成判「开头」就通了:「我准备开始一件新事」是模型自己就能确定的,不需要谁批准。 每要换一个任务、换一个阶段、开启一条新思路,先压一次再动手。

为什么是同步执行

工具是在助手回合中间执行的,那时会话还占着。压缩服务有两个入口:

入口 要求 能否在回合中间用
compactNow(agent, signal) 必须会话空闲 不能,会同步抛 busy
compactIfNeeded(agent, 'context-overflow', signal) 回合内可用 能,自己选范围

0.1.0 和 0.2.0 都绕开了这件事,改成「登记任务、等会话空闲再压」,代价是结果回不到模型—— 模型调完工具什么也没拿到,下次照样不敢用。0.3.0 改走 compactIfNeeded,当场压、当场返回。

关键前提是选段规则:DSH的 selectCompactableRange 只允许在「工具调用与结果成对」的边界切, 并且会把尚未闭合的当前这一步排除在压缩范围之外。所以工具正在跑的时候压,压掉的必然是这一步之前的历史, 不会把正在进行的调用切开。

用溢出恢复那一档(内部 retainTokens 传 0),语义就是「能压多少压多少」,正对应「开启新项目前清场」。

它怎么工作

  1. 模型调用 compact;
  2. 插件调 ctx.compaction.compactIfNeeded(agent, 'context-overflow', signal);
  3. 压缩服务自己选范围、调摘要模型、把摘要写进会话表面的检查点节点;
  4. 插件把摘要正文和三组数字(遮蔽节点数、释放的 token 数、状态)作为工具结果返回;
  5. 模型接着干活,下一步请求里历史的较早部分已经换成那条检查点。

一切压缩逻辑都复用DSH现有后端,插件不碰摘要提示词、不碰范围选择、不写会话记录。

失败不抛出:压缩抛错会被包成 { status: 'failed', detail } 交回模型, 让模型看得见原因(多半是已经有一次压缩在进行中)。没有可压缩的历史时返回 { status: 'nothing-to-compact' },不算失败。

摘要请求的缓存

摘要请求由DSH的 summarizeWithLlm 发出,构造方式是:把被压缩区域的消息原样重放 (会话自己的系统提示、请求头的工具定义、该区域逐条消息),最后追加一条 role: 'user' 的压缩指令。也就是说系统提示和上下文前缀与上一次真实请求完全一致, 只多了一句指令——这是一个真正的前缀,能命中服务商的 KV 缓存,不会把缓存打掉。

这条是DSH既有行为,本插件没有改它,也不应该改。

什么时候模型会用它

描述里给的时机:

  • 用户交来一个新任务或新项目,要开始动手了;
  • 上一件事已经收尾,正要转去做另一件;
  • 接下来要吞进大量新内容(读大文件、长网页、跑长命令),先把旧的压掉;
  • 上下文里大半内容对接下来要做的事已经没用了。

明确写出来不该用的两种:还在同一件事中间(前面的代码、报错、用户原话马上要用); 会话本来就很短(工具会回「没有可压缩的历史」,不算失败)。

怎么挂载

在 profile 的 package.json 里加依赖和 bundle:

"dependencies": {
  "dsh-tool-compact": "link:E:/models_home/.dsh/dsh-tool-compact"
},
"dsh": {
  "profile": {
    "bundles": ["dsh-tool-compact"]
  }
}

改完源码必须重启DSH:bundles 是启动时读的,改插件源码不会触发热重载 (dsh-hmr 只响应 profile 补丁和 bundles 列表的变化,且默认不碰 node_modules 下的包,link 安装的包在那边也是软链)。装上 dsh plugin --profile web add <包> 这类动 bundles 的操作才会当场重新组合。

已知限制

  • 摘要格式是DSH定的:模板含用户需求、技术概念、文件与代码、报错与修复、待办、 当前进度、下一步、关键上下文八节;「摘要与后续动作一致」这条校验DSH没做,本插件也没补。
  • 摘要会重复一份:检查点已经写进会话表面,工具结果里又带了一份正文, 目的是让模型当场看得见。下次压缩时这份正文会随历史一起被压掉。
  • 压缩范围止于当前步骤:工具执行中压不到当前这一步,这是DSH的配对规则,绕不过去。
  • 要求后端支持:依赖 compactIfNeeded。换成别的压缩后端而没有这个方法时, 工具返回 status: 'unsupported',不会崩。