dsh-handoff-doc
DeepSeek Harness 插件:把长会话交接成一份文件,而不是一条消息。
上下文快满时,会话在安全的 Step 边界停下,由会话自己的模型写一份带有效期、只有「当前快照」的交接文档
HANDOFF.md,上一版自动归档进CHANGELOG.md与history/<月份>.md,然后在同一工作区创建新会话、逐项继承 preset / 模型 / 权限 / Plan 模式,最后只投递一条指向该文件的短指令。
为什么要改(这是 dsh-session-handoff 的后继)
上一版把九段简报直接当作新会话的第一条消息投递。它能用,但有两个改不掉的毛病:
- 接手会话读很久还不知道什么是真的。 消息是散文:没有有效期、没有「这条现在还成立吗」的标记,§3「已完成」和 §8「未完成」可以互相矛盾。接手方分不清「事实」和「经过」,只能回去读日志——而这正是交接要省掉的开销。
- 它只会变长。 消息是追加的,没有地方写「这是上周的事」。每次交接都在上一次之上叠加,接手方读的是所有历史交接的总和。
这一版把内容搬进文件,并给文件立了三条规矩:
| 规矩 | 机制 |
|---|---|
| 只留「现在仍然成立」 | 提示词硬性规则 + §9 只允许一行指针 |
| 不允许追加 | 每次交接整份重写 HANDOFF.md |
| 历史不能丢 | 覆盖前先把上一版归档进 CHANGELOG.md + history/<YYYY-MM>.md |
| 必须有有效期 | 顶部写「到下一次提交为止(HEAD xxxxxxxx)」 |
| 长度必须可控 | 程序按字符预算裁剪,先删整节,再删长节末尾,并留下省略说明 |
目录结构
<工作区>/docs/handoff/<项目>/
├── HANDOFF.md # 当前快照,唯一要读的文件
├── CHANGELOG.md # 每次交接一行条目:上一版的目标 / 下一步 / 决策
└── history/
└── 2026-10.md # 被覆盖掉的交接文档原文,按月份归档
<项目> 默认取工作区最后一级目录名(D:/work/my-app → my-app)。monorepo 或目录名无意义时,用 handoffProjectFolder 钉住。
文档骨架(固定十节)
| 节 | 内容 | 谁写 |
|---|---|---|
| §0 30 秒自检 | 要跑的命令 + 预期输出 + 需要什么环境 | 命令由程序读,补充由模型写 |
| §1 目标与验收 | 用户原话 + 可勾选的验收条目 | 模型 |
| §2 当前有效事实 | 今天仍然成立的架构/口径/参数 | 事实行由程序写,正文由模型写 |
| §3 卡点与阻塞 | 现在挡住进展的具体问题,没有就写「无」 | 模型 |
| §4 下一步 + 验证映射 | 下一条动作 + 「改什么 → 必跑什么」 | 动作由模型,映射由程序 |
| §5 硬约束 / 禁忌 | 不能碰的东西 | 模型 |
| §6 文件地图 | 路径 → 职责(不写行号) | 模型 |
| §7 已否决方案 + 原因 | 别再试的路 | 模型 |
| §8 已验证 vs 未验证 | 有证据的才算已验证 | 模型 |
| §9 历史 | 一行指针,指向 CHANGELOG | 程序 |
缺任一必需小节(§0/§1/§2/§4/§6/§8)就放弃交接,不会创建新会话。上一版允许「9 段里至少 3 段有内容」,那是向「模型只看到配置事实」妥协的结果;现在模型能看到对话记录,缺 §2 或 §4 的文档等于让接手方重新做一遍。
新会话收到的第一条消息
不是文档正文,而是一条约 200 token 的指针:
# 交接:my-app
上一段会话已把当前状态写成一份交接文档,请先读它再动手:
- 文档:`docs/handoff/my-app/HANDOFF.md`
- 有效期:到下一次提交为止(本文件对应 HEAD 9f3c1ab7 @ main)
**读取顺序(不要整份通读):**
1. 只读 §0「30 秒自检」,照着跑里面的命令。
2. 自检通过 → 读 §2、§3、§4,从 §4 的第一条动作开始。
3. 自检不通过,或有效期已过 → 不要继续读,先告诉用户「交接文档已过期」,整份重写后再开工。
这样接手方的第一步是跑命令而不是读散文,而且文档可以按需重读、修订,或被下一次交接替换。
命令
| 命令 | 行为 |
|---|---|
/handoff |
立即把当前会话交接成文档并交给新会话 |
/handoff status |
阶段、上下文占用率、累计消费、阈值、最近结果 |
/handoff where(别名 doc) |
文档在哪、多大、记录的是哪个 HEAD、有没有归档 |
/handoff cancel |
取消进行中的动作(在下一个安全检查点生效) |
/handoff retry |
重试最近一次失败 |
命令不进入模型可见历史。
设置项
自动触发相关(与上一版一致):
| 字段 | 默认 | 说明 |
|---|---|---|
autoHandoffEnabled |
false |
自动交接总开关 |
autoHandoffTrigger |
context |
context / cumulative / either |
autoHandoffAtContextPercent |
85 |
占用率达到多少百分比交接 |
autoHandoffThreshold |
2000000 |
累计消费阈值 |
autoCompactEnabled |
false |
自动压缩开关 |
autoCompactAtContextPercent |
70 |
占用率达到多少百分比压缩 |
evaluateIntervalMs |
5000 |
两次测量的最短间隔 |
contextRearmDeltaPercent |
10 |
占用率滞回带 |
rearmDeltaTokens |
200000 |
累计触发后的重臂增量 |
maxAutoActionsPerSession |
3 |
单会话自动动作上限 |
summaryMaxTokens |
4096 |
模型单次输出预算 |
stepWaitTimeoutMs |
900000 |
等待边界多久算偏久(只提示) |
actionDeadlineMs |
1800000 |
动作总时限 |
openNewSessionOnHandoff |
true |
完成后切到新会话 |
archiveSourceAfterHandoff |
true |
交接后归档旧会话 |
文档相关(新增):
| 字段 | 默认 | 说明 |
|---|---|---|
handoffDir |
docs/handoff |
工作区相对目录,每个项目在其下独立成夹 |
handoffProjectFolder |
空 | 空 = 取工作区最后一级目录名 |
handoffDocMaxChars |
24000 |
文档字符上限(约 6k token) |
archivePreviousHandoff |
true |
覆盖前把上一版归档进 CHANGELOG + 月度历史 |
reusePreviousHandoff |
true |
提示词带上上一版,让模型「删改」而不是「重写」 |
硬规则:交接与压缩同时开启时,autoCompactAtContextPercent 必须低于 autoHandoffAtContextPercent,否则拒绝保存。
它绝不会做的事
- 绝不中断正在执行的工作。 代码库里没有任何
cancel()调用;停止只做两件事:设闩锁、等whenIdle()。 - 绝不吞掉用户消息。 被拒绝的 Step 里已 claim 的消息不会入库,所以只要这批消息里有人类输入,守卫就放行该 Step。
- 绝不删改原会话日志。 只读。
- 绝不假装成功。 每项继承、归档、裁剪都逐条报告;写文档失败就放弃交接,原会话不动。
- 绝不创建空会话。 空白会话拒绝交接。
- 绝不让文档无界增长。 整份重写 + 归档 + 字符预算,三道闸门。
- 绝不写出工作区之外。 所有写入经
workspace-fs.ts的resolveInside校验。
失败时的行为
| 失败点 | 行为 |
|---|---|
| 模型没给全必需小节 | 放弃交接,保留原会话,零写操作 |
| 读/写文档失败 | 放弃交接,原会话不动 |
| 归档失败 | 只记 warning,交接继续(文档仍然写成功) |
| 新会话创建失败 | 保留原会话,稍后可 retry |
| 某项继承失败 | 逐项降级并列出原因,迁移仍算成功 |
| 指针消息投递失败 | 不假装成功:返回已建会话 id,可手动继续 |
| 等不到 Step 边界 | 超时后释放闩锁、恢复原会话、标记失败 |
与 dsh-session-handoff 的关系
两个包可以同时装,但不要同时对同一个会话开自动触发——它们会抢同一个 Step 边界。
沿用下来的部分(原样复用,未改语义):度量(measure.ts)、触发引擎(trigger.ts)、软终止(graceful-stop.ts)、协调器(coordinator.ts)、会话迁移与配置继承(session-migration.ts)、脱敏(redaction.ts)、HTTP 状态通道(routes.ts)、设置卡框架(client/)。
换掉的部分:session-summary.ts 的简报渲染 → handoff-doc.ts 的文档骨架;新增 archive.ts、handoff-writer.ts、project-facts.ts、workspace-fs.ts。
安装
dsh plugin --profile <profile> add "file:D:/path/to/dsh-handoff-doc"
安装后重启 dsh。宿主与浏览器半都在同一个包里。
验证情况
见本仓库 docs/handoff/dsh-handoff-doc/HANDOFF.md。
已知限制
- 协调状态(阶段、闩锁、计数)只在内存中,重启后丢失;设置与已写出的文档不受影响。
custom权限预设没有名字可重放,只继承审批策略,并在报告中注明「sandbox 档位未继承」。- 模型不上报
contextWindow时,占用率触发不可用(累计触发仍可用)。 - 文档写入依赖原会话的
header.cwd;没有工作目录的会话无法交接(会明确报错)。 git事实通过子进程读取,超时 3 秒;不是 git 仓库时自动跳过,不影响交接。
许可
MIT
No comments yet. Be the first to write one.