dsh-token-summary: Periodically archive long conversation context into handover-ready Markdown archives.

前言

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 的行为说明来介绍。

核心功能

  1. 按真实计费 token 触发归档。只统计 assistant/message 事件里 provider 返回的 usage 字段,不按字符估算、不按消息条数累加,避免压缩后历史被重写导致的重复计数。
  2. 增量归档。每次只总结「自上次归档之后产生的事件」,避免短阈值下第 N 份文件与前面几份大量重复。
  3. 全局计数器。阈值按跨会话累计用量推进,同时开几个会话做不同的事,归档节奏也按总体用量走。
  4. 绝对边界锚定。触发点锚在整数倍刻度(0 → 499,999、500,000 → 999,999),一次响应跨过多个边界时合并成一份报告,避免之后所有批次永久漂移。
  5. 压缩前落盘。归档发生在自动 compaction 之前,相当于在信息被不可逆丢掉之前先留一份完整快照。
  6. 失败也留痕。摘要调用失败(无路由 / 上游 502 / 超时)时仍然写文件,正文降级成统计兜底并附上失败原因,游标不推进,下次触发会重试。
  7. 后台通知。归档完成后用 agent.inject() 在会话里留一行通知但不唤醒空闲 agent,避免凭空多烧一次模型调用。
  8. 不污染会话日志。插件不新增会话事件类型,计数器放内存,唯一的日志写入是一条 user/message 通知(kind: 'token-summary', form: 'notice')。
  9. 输出可检索。归档目录下另维护一份 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 总开关

摘要用哪个模型,按以下优先级解析:

  1. 配置里成对出现的 provider + model;
  2. 该会话 requestHeader() 中记录的 route;
  3. 活跃 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 里的示例数据,不是实测指标。

几个常见用法:

  1. 长任务交接。重构跑了几个小时、几百轮工具调用,随时拿最新那份 handoff-*.md 交给同事或另一个 Agent 接着干,不用人工翻对话。
  2. 事后复盘。一周后想查「那次为什么把缓存层换掉」,直接看归档里的「关键决策与理由」一节。
  3. 压缩前的保险。自动 compaction 不可逆,归档发生在它之前。
  4. 团队证据留存。读索引 README.md 就能看到每批的时间、批次号、累计 token、会话 ID。

适用场景与注意

适合把 Harness 当日常主力工具、跑长周期任务、或需要在多会话之间追踪进度的开发者。如果会话普遍很短(几十轮内结束),这类归档的收益有限。

前置条件逐项确认:DSH >= 0.2.1-rc.2(需要 session/event 广播、ctx.llm.stream()、ctx.agents);Node >= 22;至少一个注册了 adapter 的 provider 用于摘要;对输出目录可写。

几点注意:

  1. 插件以当前 dsh 进程权限运行,能读会话日志、访问工作目录、调用模型路由。安装前应自行检查源码与许可证(MIT),确认没有超出预期的副作用。
  2. 计数器精度取决于 provider 上报的 usage,不同上游口径不一致时,归档里的累计 token 只能当参考值。
  3. 阈值调小会显著增加模型调用次数,每次归档都消耗一次摘要 call,成本与 intervalTokens 成反比。
  4. 摘要由模型生成,属于压缩而非逐字记录,关键路径和命令以源码为准,不要只信归档。
  5. 插件刻意不写自有事件类型以保护会话可重开性,如果改动源码绕过这个约束,可能导致会话无法重开。

关于社区目录页: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
羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye