前言¶
在 DSH(DeepSeek Harness)里跑长任务,迟早会遇到上下文被撑满的问题。默认的压缩策略是”快到窗口上限时,把最老的一段压成摘要”,这在长任务里有两个实际麻烦:触发太晚,等到窗口用了八成才动手,压缩本身要重放整段历史,请求已经贴着上限;压完之后新旧内容混成一个摘要,模型分不清哪些是刚才发生的事、哪些是十轮前的背景。
下面介绍的插件 btyg123/dsh-memory-ladder 换了一种组织方式:让上下文按新旧分层,每层有各自的预算,最新一轮永远有最大的空间,越远越薄。它由 BTYG123 维护,MIT 许可,分类是记忆。
这是什么¶
一句话定位:一个 DSH 的 bundle 插件,作为压缩后端替换默认策略。它继承 @deepseek-ai/dsh-compaction-basic 的 BasicCompactionEngine,只替换触发策略和 summarize(),压缩事务(括号锁、收缩校验、表面替换)仍来自上游。
它不是备份工具,也不是无损压缩。README 明确写的是”它省的是容量,不是信息本身”,二级细节会随层级下移被丢弃。
分层预算怎么算¶
设路由模型的上下文窗口为 T,各层预算如下:
| 层级 | 内容 | 预算 |
|---|---|---|
| 最新一轮 | 上次检查点之后的全部原始消息 | T/3 |
| 上一轮 | 上一轮压缩出的摘要 | T/6 |
| 上上轮 | 再往前的摘要 | T/12 |
| … | 每深一层减半 | T/(3·2^d) |
最新一轮与上一轮的比例因此稳定在 2:1。v0.2 还受 maxSummaryTokens 约束:当层级总预算超过摘要输出空间时,摘要各层按几何比例缩小,并保留检查点框架空间。
v0.2 起多了原文归档¶
新版保留原有分层睡眠事务,并在压缩前把用户、助手及工具消息的文本写入当前会话独立的归档。之后根据用户问题检索已经离开上下文的原文,经 Harness 的动态上下文接口提供给模型;不会直接改写冻结的模型请求。
关键行为:
- 默认归档位置是会话工作目录下
.dsh-memory-ladder/,文件名使用会话 ID 的 SHA-256。 - 原文保留消息 ID、角色和顺序。重复压缩不会重复归档同一消息。
- 系统指令、旧检查点和运行时上下文不归档。
- 归档是本地明文状态,不会自动上传到其他服务;被选中的摘录会随下一次模型请求发送给当前模型服务商。
archiveRecall: false同时停用归档写入和检索。恢复同一会话 ID 可继续读取归档;fork 到新会话 ID 不会自动复制父会话归档。
一次睡眠发生什么¶
按 README 的链路,一次睡眠有四步:
- 最新一轮的 token 数达到
T/3(或模型报告上下文溢出)。 - 一次模型调用把”旧阶梯 + 本轮原始记录”重写成新阶梯:旧 Tier 0 变成新 Tier 1,逐层下移,最深一层落进归档链接。
- 事务把被遮蔽的整段历史替换成一条用户消息(
surfaceOp: replace),原始事件仍留在会话日志里,可回放。 - 本轮从新阶梯继续。
/compact 手动命令复用同一条路径,只是跳过阈值判断。
安装¶
前置条件是 dsh 的插件命令依赖 pnpm。README 给出的官方命令是:
npm i -g pnpm
# 方式一:插件管理器(推荐,已实测)
dsh plugin --profile <你的 profile> add git+https://github.com/BTYG123/dsh-memory-ladder.git
装完会自动把 dsh-memory-ladder 加进该 profile 的 dsh.profile.bundles。
方式二是手动放进 profile 的 node_modules,并在 patch 里停用官方压缩后端、插入本插件:
- id: compaction-basic
disabled: true
- insert:
- id: memory-ladder
name: 'dsh-memory-ladder'
这一步不能省。ctx.compaction 只接受一个实现,两个后端同时挂载会在启动时报 service "compaction" has been registered。bundle 的 patch 已包含停用步骤,手动安装时要自己加。
配置¶
配置项按 README 的配置表列出,均为原文给出的字段与默认值:
# 睡眠触发线:最新一轮占窗口的比例
newestFraction: 1/3
# 阶梯最多描述多少层
tierCount: 6
# 更深层级低于此预算就并进归档链接
minTierTokens: 200
# 睡眠请求的输出上限
maxSummaryTokens: 8192
# 可压缩跨度小于此值直接跳过,避免白花一次模型调用
minCompactionTokens: 512
# 留空则沿用最近一次路由的 provider/model
summarizationProvider: ""
summarizationModel: ""
# false 只保留手动压缩
auto: true
# 压缩前归档原文,下一步按问题检索
archiveRecall: true
# 归档目录,相对会话工作目录,也可设绝对路径
archiveDirectory: .dsh-memory-ladder
# 每次检索上下文的 UTF-8 字节上限,含来源信息和框架
recallMaxBytes: 4096
兼容性¶
README 写的验证环境是 @deepseek-ai/dsh@0.2.1-rc.5 + Node 22/24,peerDependencies 已 pin 到 0.2.1-rc.5。需要注意两点:
- 本仓库的安装根通过
overrides.zod = 4.6.6修复已知的集合校验内存耗尽漏洞;npm 不会把依赖包内的 overrides 自动应用到宿主工程,作为 bundle 安装时仍需由 Harness 宿主统一管理 Zod 版本。 compactRegion()是公开契约,相对稳定;构造函数与基类字段属于实现细节,上游重构可能导致本包失效。
已知限制¶
这些是 README 明确列出的限制,落地前值得先看清楚:
- 单条超大消息压不动。一条 5 万 token 的 assistant 输出是不可分割单元,表面压缩无法修复,属于压缩接缝的固有限制。
- 归档写满后该会话不能再压缩。
archiveMaxBytes(默认 32 MiB)是硬上限,写满时抛ARCHIVE_CAPACITY,压缩事务拒绝替换原始消息。代价是该会话此后每次睡眠都失败、上下文一路增长,需要调大上限、轮转归档或关掉archiveRecall。长期运行的会话建议提前调到 64–128 MiB。 - 睡眠本身要花一次调用。若摘要比原文还长,事务拒绝提交并记录失败;密集睡眠会推高输入成本。
- 不是无损的。要无损就调大
newestFraction,或者不用本插件。 - 摘要质量取决于模型。本地无法校验事实是否被忠实保留。
实测数据¶
README 在一次 20 分钟的 CrowdStrike 事件调研上对比同一策略的两种窗口:
默认 T/3(1M 窗口) |
32k 等效窗口 | |
|---|---|---|
| 触发线 | 349,525 token | 10,905 token |
| 峰值上下文 | 185,359 token | 32,636 token |
| sleep 次数 | 0 | 10 |
| 会话表面节点 | 266 | 142 |
| 产出 | 41 KB / 23 个来源 | 16 KB / 10 个来源 |
在 12 个合成留出案例(每模型 96 题)中,Flash 从原版 76/96 提升到 90/96,Pro 从 84/96 提升到 91/96;费用相对原版增加约 32%/31%,相对完整历史降低约 28%/34%,完整历史仍为 96/96。这是特定压缩压力下的质量/费用取舍,不是所有长任务都更优的证明。README 同时保留了消融组的一次失败和逐题错误记录。
适用场景与注意¶
适合的场景:长任务中需要保留”最近发生了什么”的优先级,同时接受远端细节衰减,并且愿意为摘要调用付一笔额外模型费用。期望无损或期望压缩完全不出错的场景不适合。
另外,插件以当前 dsh 进程权限运行。它是 bundle,自带 cordis.patch.yml,会在启动时停用 compaction-basic 并插入自己的实现——安装前应该先读一遍源码和 patch,确认这符合你的 profile 预期。许可证是 MIT,归属声明见仓库 LICENSE。目录页是第三方社区站点,与 DeepSeek / 幻方无官方从属关系。
结尾¶
它的价值在于把”什么时候压、压多细”改成由分层预算决定,而不是等到窗口快满才一次性清空。代价是每次睡眠的模型调用、归档容量上限和远端细节衰减,三者都需要在配置里做取舍。
目录页:https://www.skillhub.cn/plugins/BTYG123/dsh-memory-ladder
仓库:https://github.com/BTYG123/dsh-memory-ladder