前言¶
DeepSeek Harness(dsh)的上下文会满。满了之后常见的做法是「开个新会话」,于是上一轮聊定的东西、还没做完的事、对方提过的细节全没了。如果不清会话,每次都在越来越长的上下文里烧钱;如果清了,万一摘要失败没人发现,话就丢了。
这个插件把这件事变成一个有先后顺序的流程,并且把顺序当成硬规矩:先摘要,后清理。摘要不成功,一条都不许删。
这是什么¶
dsh-carryover 是一个给 DeepSeek Harness(dsh)用的会话交接插件。
一句话:开新会话之前先把上一段聊过的话写成笔记;笔记真的落到盘上了才允许删掉旧会话轨迹。
| 属性 | 说明 |
|---|---|
| 作者 | JackZo400 |
| 类别 | 记忆 |
| 许可证 | MIT |
| 仓库 | github.com/JackZo400/dsh-carryover |
安装¶
使用以下命令安装:
dsh plugin --profile web add github:JackZo400/dsh-carryover
安装后需确认三件事:
1. sessionsRoot 指向正确的轨迹目录(默认 ~/.dsh/sessions)。
2. summary.apiKey 有值(建议使用环境变量 DSH_CARRYOVER_API_KEY)。
3. 维护好映射文件(session-map.json),插件靠它知道谁该被交接。
配置¶
在插件配置中启用并设置参数:
- insert:
- id: carryover
name: dsh-carryover
config:
enabled: true
sessionsRoot: ~/.dsh/sessions
transcript: auto # 自动挑选版本最大的轨迹文件
workspace: . # 笔记存储目录(默认 memory/carryover)
inject: true # 新会话自动带上笔记
injectOrder: 40 # 注入顺序
maxInjectChars: 4000
summary:
baseUrl: https://api.deepseek.com
apiKey: ''
model: deepseek-chat
maxTokens: 900
映射文件(session-map.json):
这是插件工作的核心契约,默认路径为 <workspace>/tmp/session-map.json。它定义了通道与会话 ID 的对应关系:
{
"group:demo": { "sessionId": "3f2a...c1", "carried": "1a9b...7e" },
"c2c:alice": { "sessionId": "88d0...4b" }
}
sessionId:当前通道的会话 ID。carried:已经交接过的会话 ID。- key 可以自定义,但必须用来给笔记文件命名。
工作原理¶
交接流程(开新会话前)¶
- 从映射文件中查出当前通道的上一个会话 ID。
- 打开轨迹目录,解压
session.v*.jsonl.zstd,剔除系统注入内容,只保留正文。 - 将正文喂给摘要模型,生成便条。
- 将便条追加写入笔记文件,并读回来核对(会话 ID 和摘要内容是否都在)。
- 核对通过 → 删除旧轨迹目录;核对失败 → 保留旧轨迹,记录
KEEP日志。
接上流程(新会话开始)¶
新会话组装 system prompt 时,插件反查当前会话属于哪条通道,将对应的笔记挂进上下文。全文保留在磁盘上,注入时只带最近的一段。
用法¶
1. 宿主插件调用¶
在开新会话之前调用:
const carryover = ctx.get('carryover')
const r = await carryover.carryOver({ key: 'group:demo' })
if (r.status === 'kept') {
log('没交接成,旧轨迹留着:', r.reason)
}
status 状态包括:
- none:无可交接内容。
- done:交接成功,旧轨迹已删。
- kept:交接失败,旧轨迹未删。
2. Agent 工具调用¶
Agent 可以直接调用三个工具:
- carryover_status:查看交接状态和笔记位置。
- carryover_note:读取笔记内容。
- carryover_run:执行交接。mode=peek 只预测结果,mode=dry 真摘要但不写不删。
3. 读取笔记¶
其他插件或脚本读取笔记:
const note = ctx.get('carryover').readNote('group:demo')
4. 独立使用¶
插件不依赖 dsh 环境,可以在 Node.js 项目中独立调用:
import { createCarryover } from 'dsh-carryover/carryover'
const engine = createCarryover({
workspace: '/srv/app',
sessionsRoot: '/srv/app/.dsh-sessions',
summarize: async (dialogue) => myOwnSummarizer(dialogue),
})
await engine.carryOver({ key: 'group:demo' })
注意与限制¶
- 依赖 zstd 命令行:轨迹解压依赖
zstd,因为node:zlib只能解压第一帧。 - 不自己找会话:插件不自动猜测哪个会话是上一个,必须依赖映射文件维护。
- 不加密:笔记以明文 markdown 存储在工作区,敏感内容需自行注意。
- 轨迹版本:轨迹文件名带版本号(如
session.v4.jsonl.zstd),默认自动挑选最新版本,认不出文件时不会删除。 - 护栏机制:路径检查严格,
sessionsRoot之外的路径、包含..的路径均不允许删除,只记账。