前言¶
用 DSH 跑隔夜构建、多小时 agent 任务或者 goal 驱动的轮次时,最常见的崩溃方式不是模型出错,而是宿主进程没了:被 SIGKILL、OOM、超时,或者某次重启。进程一死,它带着的所有会话一起停在原地,会话顶部顶着一行 “Task stopped — The DSH process exited before this finished…”,之后就一直挂着,等有人重新发一条消息才继续。日志、工作区、模型上下文其实都是完好的,工作只是被中断,不是丢失。
已有的做法是早上起来手动点一下 Send。pinion05/dsh-neverdie 把这一步交给插件:DSH 重启之后,扫描一遍会话,找出哪些是进程死掉时正好在跑 turn 的,自动重新提示一句,让 agent 接着往下干。
这是什么¶
pinion05/dsh-neverdie 是一个 DSH host plugin,属于「工作流」分类,由 pinion05 维护,许可证 MIT。它解决问题的场景很窄:DSH 进程中途退出,留下一批挂在 “Task stopped” 状态的会话,插件在 host 重启后把它们唤醒,并把因重启而失效的 goal 重新挂上,让自主轮次继续跑。
包名是 @npmc_5/dsh-neverdie,仓库 pinion05/dsh-neverdie,版本 0.3.0,仓库公开可用,星标 0。它不是一个通用的调度器,也不是重启 DSH 的工具,只在 host 已经重新起来了之后做一次扫描。
检测逻辑¶
进程退出有两种方式,会话日志的收尾形态不同,插件据此区分「该不该醒」。
硬崩溃(SIGKILL、OOM)时日志停在一个还没闭合的 turn 里,重启恢复会给这段冷日志合成一个 reason 为 interrupted 的 turn/end 收尾。优雅退出或重启时,濒死的进程自己把正在跑的 turn 关掉,reason 是 aborted,cause 是 disposed,被中止的工具调用在对话末尾留下一行 “Error: tool call aborted”。
插件从日志尾部往前走,找到最后一个 turn/end,只有上面两种才算「尸体」:
- 没有更新的 turn、消息,也没有待处理的 inbox 输入;
- abort 的 cause 不是
user、parent或hook——也就是说被用户主动停掉的会话不会被唤醒; - 尾部如果是重启后追加的生成标记(例如
session/end-seed),会被跳过;冷日志则由sessionQuery.readSession在内存里合成interrupted收尾,因此无论会话事先有没有被重新打开,判定都是同一套。
还有一个容易漏的情况:重连的 web 客户端会把会话日志恢复出来,但并不恢复它的工作。这种「活着但空转」的状态同样按尸体处理。
判定通过后,插件对候选做 resolveAgent(唤醒冷会话),再重新挂 goal,最后把 revive 消息排队并写入持久化存储。每次扫描都会在 host journal 里打一条 [neverdie] 行,每条 revive 消息带 neverdie 这个 source kind,在 transcript 里可见,也可以从会话日志里数出来。
Goal 重新挂载¶
goal 的激活状态是进程级的,所以杀掉 turn 的那次重启同时也让 session 的 goal 失效了。插件走支持的 goals.resume 路径重新挂载,前提是 goal 处于 active 且 activation 为 disarmed。paused、blocked、complete 或者轮次已经用完的 goal 原样不动;轮次耗尽的 goal 会在日志里说明为什么不动。
配置¶
配置项一共六个,默认值如下:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled |
boolean |
true |
总开关 |
delaySeconds |
number |
10 |
host 启动后等多久再扫描,取值 0–600 |
maxAgeMinutes |
number |
60 |
只救中断时间在这个窗口内的会话,取值 1–1440 |
maxRevives |
number |
5 |
上一次真实用户消息之后,同一会话连续被 neverdie 唤醒的次数上限,取值 0–10 |
reviveMessage |
string |
见下 | 唤醒时注入的消息 |
resumeGoals |
boolean |
true |
唤醒期间是否重新挂载 active 但 disarmed 的 goal |
默认的 reviveMessage:
[neverdie] The DSH process exited before the previous turn could finish, so that turn was closed. Continue the interrupted task from where it stopped. Tool calls that were cut off have unknown outcomes — inspect their effects before retrying them.
这段消息本身是必要的:harness 不接受纯空白的 prompt,所以唤醒必须发一条真实的短消息。消息里提醒 agent 被斩断的工具调用结果未知,先检查现场再重试。
maxRevives 是防止崩溃循环的闸门。host 如果一直死,插件每次启动都会救,最多连着救 maxRevives 次;只要来了真正用户消息,预算就清零重算,不会让崩溃一直烧 token。
安装与启用¶
README 与 package.json 都没给出安装命令。仓库通过 dsh.bundle.patch 指向 ./cordis.patch.yml 声明了一个 Cordis 补丁,同包还导出 index.js、locale/*.json 和 icon.svg,并且声明了对 @deepseek-ai/cordis、@deepseek-ai/schemastery、@deepseek-ai/dsh-llm 的 peer 依赖。
因此这里不写安装命令。按名字拼出来的 dsh plugin add 形式并不来自资料原文,请以你本地 DSH 的插件管理方式为准,从 npm 包 @npmc_5/dsh-neverdie 或仓库 pinion05/dsh-neverdie 引入,装之前先确认 peer dependency 与你现有版本匹配。
典型用法¶
准备上生产时,先把宿主进程交给监管:
- 用 systemd 这类 supervisor 包住 DSH,配置
Restart=on-failure。插件自己不能重启 host,它只在 host 回来之后做事,「谁来重启」必须外部给。 - 增加
delaySeconds,给启动和冷日志恢复留足时间,避免扫描早于恢复逻辑。 - 如果预期 host 可能长时间离线,按离线时长调高
maxAgeMinutes,默认 60 分钟之外的会话不会被救。 - 看日志确认:journal 里出现
[neverdie] revived session ...和[neverdie] re-armed goal ...,说明对应会话被唤醒、对应 goal 被重新挂上。 - 出问题时按 transcript 里的
neverdie标记逐条核对,确认某次唤醒属于预期行为。
适用场景与注意¶
适合这类人:DSH 跑长任务、跑 goal 驱动轮次,且 host 由 systemd 之类的外部 supervisor 拉起的。它把「早上起来点 Send」这一步消掉,让被中断的任务在重启后自己接上。
几条硬约束:
- 只处理 root conversation。subagent 子会话交给父会话处理,父会话自己的唤醒会把它们对齐。
- 超过
maxAgeMinutes的会话保持死亡状态,不要把窗口拉得太大,否则会把早就该人工接手的任务自动续上。 - 插件以当前 dsh 进程的权限运行,装之前读一遍
index.js与cordis.patch.yml,确认补丁行为与许可证(MIT)都符合你的要求。 - 生态背景:DSH 的思路是「一切皆插件」,社区目录站点是独立站点,与 DeepSeek / 幻方没有官方从属关系,安装前自行核实来源。
结尾¶
这个插件没有扩大 DSH 的能力范围,它只是把一个本来需要人盯着的环节修好了:进程死了,重启之后,被中断的会话自己站起来接着跑,goal 也接着轮次。如果你已经在用 supervisor 拉 DSH,而你的长任务会在夜里被重启打断,值得试一下。
目录页:https://www.skillhub.cn/plugins/pinion05/dsh-neverdie
仓库:https://github.com/pinion05/dsh-neverdie