前言¶
DSH 的理念是「一切皆插件」,但插件解决不了一个老问题:编程 Agent 跑长任务时,记忆只存在于上下文窗口里。自动 compaction 触发后,早期的路径、命令、报错信息会被压缩掉;会话关了、机器换了,之前做过什么、为什么这么决策,没人记得。人工翻几百轮对话来整理一份交接文档要十几分钟。
dsh-token-summary 就是针对这个场景的 host plugin。它监听会话事件、累加真实计费 token,每跨过一个阈值就把新增的这段会话交给模型生成一份结构化总结,写成独立 Markdown 文件,并在索引里留一条记录。下面介绍它的安装、配置和用法。
这是什么¶
插件名 dsh-token-summary,仓库 tech-zerolei/dsh-token-summary,维护者 tech-zerolei(package.json 的 author 字段为 zerolei),分类「记忆」,类型 Host plugin,状态 stable,许可证 MIT。当前 package.json 版本 2.0.1,要求 Node.js >= 22、DeepSeek Harness >= 0.2.1-rc.2。
一句话定位:把易失的上下文变成可长期保存的 Markdown 资产。归档文件里带任务目标、当前状态、已完成项、关键决策与理由、未完成事项,可以直接交给同事、交给第二天的自己,或作为背景交给另一个 Agent 接着干。
阈值数字需要先说明一处口径差异:仓库 README 开头写的是「每累计 50 万 token」并附有 500,000 token 的输出示例,package.json 的 description 写的是「每累计 1000 万 token」,社区目录页同样写 1000 万,而默认配置 intervalTokens 是 2000000(200 万)。几处不一致,实际触发点以你在 cordis.patch.yml 里配置的 intervalTokens 为准。本文按 README 的行为说明来介绍。
核心功能¶
- 按真实计费 token 触发归档。只统计
assistant/message事件里 provider 返回的usage字段,不按字符估算、不按消息条数累加,避免压缩后历史被重写导致的重复计数。 - 增量归档。每次只总结「自上次归档之后产生的事件」,避免短阈值下第 N 份文件与前面几份大量重复。
- 全局计数器。阈值按跨会话累计用量推进,同时开几个会话做不同的事,归档节奏也按总体用量走。
- 绝对边界锚定。触发点锚在整数倍刻度(
0 → 499,999、500,000 → 999,999),一次响应跨过多个边界时合并成一份报告,避免之后所有批次永久漂移。 - 压缩前落盘。归档发生在自动 compaction 之前,相当于在信息被不可逆丢掉之前先留一份完整快照。
- 失败也留痕。摘要调用失败(无路由 / 上游 502 / 超时)时仍然写文件,正文降级成统计兜底并附上失败原因,游标不推进,下次触发会重试。
- 后台通知。归档完成后用
agent.inject()在会话里留一行通知但不唤醒空闲 agent,避免凭空多烧一次模型调用。 - 不污染会话日志。插件不新增会话事件类型,计数器放内存,唯一的日志写入是一条
user/message通知(kind: 'token-summary',form: 'notice')。 - 输出可检索。归档目录下另维护一份
README.md索引,记录每批的时间、批次号、累计 token、会话 ID,形成可审计的时间线。
触发与增量读取的核心逻辑:
ctx.on('session/event', (session, event) => {
if (event.type !== 'assistant/message' || event.data.usage === undefined) return;
const delta = usageTokens(event.data.usage);
if (delta <= 0) return;
totalTokens += delta;
if (totalTokens >= nextThreshold) onThreshold(session);
});
const events = session.ownEvents();
const since = sessionCursor.get(session.id) ?? 0;
const fresh = events.slice(since); // 只要新增的
上面第二段里的游标 sessionCursor 只在文件写成功后才推进,所以摘要失败时那一段内容不会被吞掉。另外当历史超过 maxTranscriptChars 时,插件从最新的对话往回填预算,最早的内容先丢——总结的价值集中在「事情最后做成了什么」。
安装与启用¶
先确认环境:Node.js >= 22,DeepSeek Harness >= 0.2.1-rc.2。插件需要对输出目录有写权限,默认输出目录是会话工作目录下的 .dsh-summaries/。
推荐用本地 bundle 方式安装:
dsh plugin add /path/to/dsh-token-summary
或用 profile 的包管理:
# 在 profile 目录内
pnpm add link:/path/to/dsh-token-summary
然后在 package.json 的 dsh.profile.bundles 中加入 "dsh-token-summary"。
方式二是直接编辑 cordis.patch.yml 手动插入配置:
- insert:
- id: token-summary
name: 'dsh-token-summary'
config:
intervalTokens: 2000000
outputDir: ''
notifyInSession: true
enabled: true
安装前应先查看 cordis.patch.yml 和源码,确认配置项与当前 DSH 版本兼容。审查时留意:除摘要调用外插件不发起外部网络请求,但摘要本身会走模型路由,若会话里用的是有配额或付费的 provider,归档会额外消耗该模型的 token。
配置¶
所有字段都可选,在 cordis.patch.yml 的 token-summary 行下覆盖即可:
| 字段 | 默认值 | 说明 |
|---|---|---|
intervalTokens |
2000000 |
一个批次覆盖的 token 数(全局累计) |
outputDir |
'' |
输出目录;留空写入 <会话工作目录>/.dsh-summaries,填绝对路径则所有会话集中写入该目录 |
provider |
'' |
摘要用的 provider 路由;必须与 model 同时填写 |
model |
'' |
摘要用的精确模型 id |
maxSummaryOutputTokens |
2048 |
摘要请求的输出 token 上限 |
maxTranscriptChars |
120000 |
送入模型的历史字符上限,超出时从最早的对话开始丢弃 |
timeoutMs |
180000 |
单次摘要请求的端到端超时 |
notifyInSession |
true |
归档后是否在会话里留一行通知 |
enabled |
true |
总开关 |
摘要用哪个模型,按以下优先级解析:
- 配置里成对出现的
provider+model; - 该会话
requestHeader()中记录的 route; - 活跃 agent 的
options。
provider 和 model 只填一个时会被忽略并输出警告,路由回退到该会话最近一次请求的 route。如果会话从未发过请求、也没有配置路由,就无法确定摘要路由,此时插件仍会写一份统计兜底文件,但拿不到摘要正文。
输出目录有两种取舍。outputDir 留空时归档跟着会话工作目录走,每个项目一个 .dsh-summaries/:
/root/work/project-a/.dsh-summaries/handoff-订单重构--txlsf8-0001-500000-....md
/root/work/project-b/.dsh-summaries/handoff-缓存优化--3auzbq-0001-500000-....md
需要统一检索、备份或纳入同一个仓库时,填绝对路径:
- id: token-summary
name: dsh-token-summary
config:
outputDir: /root/.dsh/summaries
要注意切换 outputDir 不会搬动已有文件,旧归档留在原目录,索引 README.md 里的路径也只是写入时的快照。想保留历史就手动迁移。
典型用法¶
一个配置好路由的例子,阈值调到 50 万、指定摘要模型、输出到集中目录:
- insert:
- id: token-summary
name: 'dsh-token-summary'
config:
intervalTokens: 500000
outputDir: /root/.dsh/summaries
provider: api23
model: deepseek-flash
notifyInSession: true
enabled: true
装好之后正常使用会话即可,无需额外操作。插件监听 session/event,触发时读取自上次归档后的新事件,折叠成转录送给模型,然后把结果写入 handoff-*.md 并更新索引。归档文件头部是统计表,README 给的示例如下:
# 工程交接文档 · 第 2 版(每累计 500,000 token 更新)
| 项目 | 值 |
| --- | --- |
| 生成时间 | 2026-11-05T01:30:00.000Z |
| 会话 ID | `session-3fbd1fea-...` |
| 会话标题 | 缓存重构 |
| 工作目录 | `/root/work/my-project` |
| 本批序号 | 第 2 版 |
| 本批区间 | 500,000 → 999,999 token |
| 已覆盖事件数 | 42 |
| 累计 token | 1,000,000 |
| 工具调用 | 188 |
| 工具失败 | 0 |
正文按固定小节输出:任务全景、当前状态、已完成工作、关键决策与理由、文件与路径清单、命令配置与数据、遇到的问题与解决、未完成事项与下一步、风险与注意。该示例是缓存层改造任务,第 2 版记录了把单层 LRU 改成两级缓存、Redis 封装、熔断策略,以及下一步「灰度 10% 流量观察 24 小时」。注意这是 README 里的示例数据,不是实测指标。
几个常见用法:
- 长任务交接。重构跑了几个小时、几百轮工具调用,随时拿最新那份
handoff-*.md交给同事或另一个 Agent 接着干,不用人工翻对话。 - 事后复盘。一周后想查「那次为什么把缓存层换掉」,直接看归档里的「关键决策与理由」一节。
- 压缩前的保险。自动 compaction 不可逆,归档发生在它之前。
- 团队证据留存。读索引
README.md就能看到每批的时间、批次号、累计 token、会话 ID。
适用场景与注意¶
适合把 Harness 当日常主力工具、跑长周期任务、或需要在多会话之间追踪进度的开发者。如果会话普遍很短(几十轮内结束),这类归档的收益有限。
前置条件逐项确认:DSH >= 0.2.1-rc.2(需要 session/event 广播、ctx.llm.stream()、ctx.agents);Node >= 22;至少一个注册了 adapter 的 provider 用于摘要;对输出目录可写。
几点注意:
- 插件以当前 dsh 进程权限运行,能读会话日志、访问工作目录、调用模型路由。安装前应自行检查源码与许可证(MIT),确认没有超出预期的副作用。
- 计数器精度取决于 provider 上报的
usage,不同上游口径不一致时,归档里的累计 token 只能当参考值。 - 阈值调小会显著增加模型调用次数,每次归档都消耗一次摘要 call,成本与
intervalTokens成反比。 - 摘要由模型生成,属于压缩而非逐字记录,关键路径和命令以源码为准,不要只信归档。
- 插件刻意不写自有事件类型以保护会话可重开性,如果改动源码绕过这个约束,可能导致会话无法重开。
关于社区目录页:skillhub.cn 是独立站点,与 DeepSeek / 幻方无官方从属关系。本环境里目录页正文未抓取到可核对的额外细节(页面渲染为空),所以本文只采用了插件名与归属信息,星标、版本等信息均以 README 与 package.json 为准,未采用任务线里未经复核的数字。
结尾¶
dsh-token-summary 的价值是把 Agent 上下文里的记忆,在压缩和会话结束之前先固化成可长期保存、可直接交接的 Markdown。代价是每次触发多一次模型调用,并且要接受归档是模型生成的摘要而非逐字日志。想试试的话,先看仓库 README 与 cordis.patch.yml 确认配置,再决定阈值取多大。
- 插件目录页:https://www.skillhub.cn/plugins/tech-zerolei/dsh-token-summary
- GitHub:https://github.com/tech-zerolei/dsh-token-summary