DeepSeek Harness 的压缩事件有几个:文档三种、源码四个
读一个陌生仓库的事件模型时,最省事的办法是先看子系统文档给的那张事件表,把名字抄下来当作词汇表。DeepSeek Harness 的压缩子系统就有这么一张表,读完你会得到一个结论:压缩往会话事件里加了三种事件。
这个结论跟源码对不上。截至 2026-08-16 我们采集的快照 47f9438(版本 0.1.0-rc.5),源码里声明的是四种。多出来的那个叫 compaction/prune。
先把限定说在前面:这个仓库建立于 2026-08-13,我们采集时距离建仓只有三天;README 自述处于开发者预览阶段,并明确写了未来会出现破坏兼容性的变更。下面提到的每一个事件名、字段名、默认值都带着这层限定,随时可能变。
两处口径的确切位置
文档这一侧。 docs/subsystems/compaction.md:11 的原文是「Compaction extends SessionEventMap with three event types via declaration merging」,紧接着 :13-17 是一张三行的表格,列的是 compaction/start、compaction/summary、compaction/end,每行给出 payload 与角色。同一句里还强调这三个都是 log-only,SurfaceEventType 是被刻意不扩展的。
docs/subsystems/session.md:11 是同样的口径:讲 SessionEventMap 是 merge-extensible 时,举的例子就是「compaction seam 加了 compaction/start / compaction/summary / compaction/end」。
源码这一侧。 packages/compaction/compaction/src/types.ts 里只有一个 declare module '@deepseek-ai/dsh-session/types' 块,块内声明了四个成员:compaction/start 在 :22、compaction/summary 在 :33、compaction/end 在 :67、compaction/prune 在 :81。
生成物这一侧。 仓库里有两处由脚本生成的事件清单,两处都是四个。一处是 packages/core/session/src/known-event-types.ts,:29-32 这四行连着排:compaction/end、compaction/prune、compaction/start、compaction/summary;该文件头部注明由 scripts/gen-persistence-catalog.ts 生成、不要手改。另一处是 docs/persistence-catalog.md,:299 有一节标题写着 #### compaction/prune — log-only。
还有一处细节值得记下来:口径不一致的那一页文档,自己在下面又提到了这个事件。docs/subsystems/compaction.md:224 是 pruneSession 的签名注释,那段注释里就出现了 compaction/prune。
两处口径就摆在这里。以我们实读的仓库状态为准,声明的是四个。差异说到这里为止,我们不推断哪一处该改、也不由此评价这个项目。
compaction/prune 在源码里是干什么的
既然文档表格里没有它,只能回源码看。types.ts:74-86 给出的 payload 是三个字段:shadowedRange、shadowedSeqs、shadowedTokenCount。注释把它的用途讲成一条协议——「影子定价」。
要理解这条协议,得先知道会话日志里 surface 是怎么被改的。docs/subsystems/session.md:285-288 写了 SurfaceOp 只有两种形态:'append' 追加到尾部,{ op: 'replace'; start; end } 把 start..end(含端点)这段 surface 节点替换掉。压缩用的就是 replace。
问题在于:一段 surface 被替换掉之后,谁来记「被替换掉的那段值多少 token」?如果不记,下游想算当前上下文占用,就得自己保留每个节点的价格。协议给的答案是:一个 surface replace 由它紧邻前面的那个计价事件定价,而且替换必须在该计价事件之后同步追加。
摘要压缩这条主线上的事件顺序是查得到的。docs/subsystems/compaction.md:19 写明了锁的语义:先写 compaction/start,然后做摘要、写 compaction/summary、写替换用的 user/message,最后才写 compaction/end;这样中途崩溃留下的是一把可检测的孤儿锁,而不是一条谎称完成的 compaction/end。而同页 :11 已经说明,摘要本身不是 surface 事件,真正替换 surface 的是紧随其后那条带 surfaceOp: { op:'replace', start, end } 的 user/message。
compaction/prune 没有出现在这条主线的表格里,源码注释给它的定位是为被遮蔽的那段节点定价。它与摘要那条线具体怎么分工,我们只依据 types.ts:74-86 的注释和 compaction.md:224 的签名注释这两处记录,超出这两处的部分没有依据,不做推断。
再补一层坐标。docs/subsystems/compaction.md:5 写的是压缩本身按三段式切开:Service Definition dsh-compaction(挂在 ctx.compaction)、Provider dsh-compaction-basic、给人用的 Consumer dsh-command-compact;同一处还明确写着压缩是一个可选能力,不属于 agent-loop 主干。所以这块的事件词汇表本来就是「由插件往核心里加」的产物,而不是核心自带的一张封闭表。这也是为什么它的口径会分散在子系统文档、包内源码和生成物三个地方。
不调模型的那一层:工具结果裁剪
裁剪这一层住在 packages/compaction/compaction-tool-result-pruner。它的配置默认值在 src/config.ts:10-14 的 DEFAULTS 里,三个键:
| 键 | 默认值 |
|---|---|
thresholdChars | 8192 |
headChars | 4096 |
tailChars | 1024 |
读这张表要注意三件事。
第一,被切掉的中段不是留空,而是换成一个写死的标记,config.ts:7 的 PRUNE_MARKER 是 '\n\n[... tool result middle pruned ...]\n\n'。
第二,这三个值不是随便配的,加载期有一条不变式:headChars 加上标记本身再加上 tailChars,必须小于等于 thresholdChars,否则直接抛错(config.ts:55-63)。也就是说你把 headChars 往上调的时候,thresholdChars 得跟着动,不然启动就失败。至于这三个数该调成多少,取决于你的工具输出长什么样,项目没有给通用值。
第三,计量单位是 Unicode 码点,config.ts:27-29 用的是 Array.from(text).length,不是 UTF-16 code unit。docs/subsystems/compaction.md:213-215 也强调切割按码点走、不会切开代理对,但同一处明写仍可能切开字素簇。
还有一层要分清:这三个数在库里有默认值,同时在出厂配置里又被写死了一遍。apps/cli/config/agent-presets/standard/agent.cordis.yml:150-155 的 standard 预设显式写了同样的 8192 / 4096 / 1024,packages/bundle/base/cordis.patch.yml:360-365 的 bundle 基线也写死了同一组。你改哪一层,取决于你是在改库的默认还是在改这份 composition。
为什么这不是一条无害的文档差异
如果只是看文档学架构,少一个事件名不影响理解。但会话日志这条线上有一条硬规矩,会把这件事放大。
docs/subsystems/persistence.md:94 与 packages/core/session/src/known-event-types.ts:8-17 写的是:读日志时遇到不在已知类型表里的事件类型,拒绝重建——除非该事件的信封上带 ignorable: true。docs/subsystems/session.md:222-229 是同一条。
换句话说,「哪些事件类型算已知」在这个仓库里不是文档口径问题,是一份会决定日志能不能被读回来的清单。截至我们采集时,这份清单一共 44 个类型(known-event-types.ts:19-64,docs/persistence-catalog.md 里数出的 #### 事件小节同样是 44 个,其中标 surface 的只有 3 个:assistant/message、tool/result、user/message,其余 41 个是 log-only)。四个 compaction/* 都在这 44 个里面。
另外,SessionEventMap 是 merge-extensible 的——插件用 TypeScript 声明合并往里加事件类型,所以 docs/subsystems/session.md:247 明写 switch (event.type) 不允许用 assertNever。你写消费端时若照着文档表格写了一个只覆盖三个分支的 switch,语言层面不会拦你。
你可以自己怎么核
这类差异不需要相信任何人的转述,回仓库两步就能自己看到(以下命令按仓库中的文件路径组合,我们没有运行过,以仓库当前内容为准):
在 Linux/macOS 上:
grep -n "compaction/" packages/core/session/src/known-event-types.ts
grep -n "compaction/prune" docs/subsystems/compaction.md docs/persistence-catalog.md
在 Windows PowerShell 上:
Select-String -Path packages\core\session\src\known-event-types.ts -Pattern "compaction/"
Select-String -Path docs\subsystems\compaction.md,docs\persistence-catalog.md -Pattern "compaction/prune"
然后直接打开 packages/compaction/compaction/src/types.ts,在那个 declare module 块里数一遍成员数。数出四个,就跟我们看到的一致;数出别的数,说明快照已经变了——这个仓库变动很快,以你手上那份为准。
同一页还有第二处口径差
顺带记一笔,因为它跟上面是同一类。docs/subsystems/compaction.md:15 把 compaction/start 的 payload 写成 { turn },:17 把 compaction/end 写成 { turn, error? }。而源码 types.ts:22 是 { compactionId: CompactionId; sourceCommandId?: CommandId; turn: number | null },types.ts:67 是 { compactionId; sourceCommandId?; turn: number | null; error? }。compaction/summary 那一行(compaction.md:16)同样没有列出 compactionId 与 sourceCommandId。
这一处文档自己给了处置口径:同页 :23 写着「The payload table above is the catalog entry; follow the source link for the authoritative fields.」——表格是目录条目,字段以源码链接为准。
所以对这一整块的实用结论只有一句:拿文档表格当索引,拿 packages/compaction/compaction/src/types.ts 与生成的 docs/persistence-catalog.md 当字段来源。至于三与四哪个更该被改,不是我们能判断的,我们只记录 2026-08-16 这天两处分别写着什么。
延伸阅读
- 从头读起:DeepSeek Harness 是什么:建仓三天、13 万 star 的 Agent 框架
- 本专题共 45 篇,完整分组目录见专题页
- DeepSeek Harness 的会话生命周期:事件、投影与持久化三层
- DeepSeek Harness 里四个都叫 version 的数字:0、15、8、1
本文依据 DeepSeek Harness 官方仓库(github.com/deepseek-ai/deepseek-harness)的 README、docs/ 下的
架构与子系统文档、以及 packages/ 下的源码整理,核对日 2026-08-16,对应仓库快照 47f9438(版本 0.1.0-rc.5)。
本文内容为仓库源码与文档口径,我们没有安装、也没有运行过这个项目,
因此不涉及界面外观、操作手感与运行速度的任何描述。
该仓库建立于 2026-08-13,README 自述处于开发者预览阶段并明确说明未来会有破坏兼容性的变更,
文中出现的命令、配置与默认值随时可能变动,请以仓库最新内容为准。