DeepSeek Harness 的压缩事件有几个:文档三种、源码四个

2026-08-16

读一个陌生仓库的事件模型时,最省事的办法是先看子系统文档给的那张事件表,把名字抄下来当作词汇表。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/startcompaction/summarycompaction/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:22compaction/summary:33compaction/end:67compaction/prune:81

生成物这一侧。 仓库里有两处由脚本生成的事件清单,两处都是四个。一处是 packages/core/session/src/known-event-types.ts:29-32 这四行连着排:compaction/endcompaction/prunecompaction/startcompaction/summary;该文件头部注明由 scripts/gen-persistence-catalog.ts 生成、不要手改。另一处是 docs/persistence-catalog.md:299 有一节标题写着 #### compaction/prune — log-only

还有一处细节值得记下来:口径不一致的那一页文档,自己在下面又提到了这个事件。docs/subsystems/compaction.md:224pruneSession 的签名注释,那段注释里就出现了 compaction/prune

两处口径就摆在这里。以我们实读的仓库状态为准,声明的是四个。差异说到这里为止,我们不推断哪一处该改、也不由此评价这个项目。

compaction/prune 在源码里是干什么的

既然文档表格里没有它,只能回源码看。types.ts:74-86 给出的 payload 是三个字段:shadowedRangeshadowedSeqsshadowedTokenCount。注释把它的用途讲成一条协议——「影子定价」。

要理解这条协议,得先知道会话日志里 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-14DEFAULTS 里,三个键:

默认值
thresholdChars8192
headChars4096
tailChars1024

读这张表要注意三件事。

第一,被切掉的中段不是留空,而是换成一个写死的标记,config.ts:7PRUNE_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-155standard 预设显式写了同样的 8192 / 4096 / 1024packages/bundle/base/cordis.patch.yml:360-365 的 bundle 基线也写死了同一组。你改哪一层,取决于你是在改库的默认还是在改这份 composition。

为什么这不是一条无害的文档差异

如果只是看文档学架构,少一个事件名不影响理解。但会话日志这条线上有一条硬规矩,会把这件事放大。

docs/subsystems/persistence.md:94packages/core/session/src/known-event-types.ts:8-17 写的是:读日志时遇到不在已知类型表里的事件类型,拒绝重建——除非该事件的信封上带 ignorable: truedocs/subsystems/session.md:222-229 是同一条。

换句话说,「哪些事件类型算已知」在这个仓库里不是文档口径问题,是一份会决定日志能不能被读回来的清单。截至我们采集时,这份清单一共 44 个类型(known-event-types.ts:19-64docs/persistence-catalog.md 里数出的 #### 事件小节同样是 44 个,其中标 surface 的只有 3 个:assistant/messagetool/resultuser/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:15compaction/start 的 payload 写成 { turn }:17compaction/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)同样没有列出 compactionIdsourceCommandId

这一处文档自己给了处置口径:同页 :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 官方仓库(github.com/deepseek-ai/deepseek-harness)的 README、docs/ 下的 架构与子系统文档、以及 packages/ 下的源码整理,核对日 2026-08-16,对应仓库快照 47f9438(版本 0.1.0-rc.5)。 本文内容为仓库源码与文档口径,我们没有安装、也没有运行过这个项目, 因此不涉及界面外观、操作手感与运行速度的任何描述。 该仓库建立于 2026-08-13,README 自述处于开发者预览阶段并明确说明未来会有破坏兼容性的变更, 文中出现的命令、配置与默认值随时可能变动,请以仓库最新内容为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。