dsh-plugin-heartbeat
给每个根 agent 装一套自主神经心跳:静默拍会 diff 会话的待办 / 团队任务板 / 目标,只有出现信号才开口(L1 汇报 / L2 提问 / L4 升级),只问不做。每条消息带 why-now 行、可选的一行生理签名(距你上次说话多久、今天第几次找你)与紧凑任务快照(800 字符上限);去重窗口、静默时段、每日预算、不应期 / 重启冷却与持久化生命体征共同限流,忙碌时 followup 注入排队不打断。它与 dsh-plugin-memory 共享一个建议性发言权(~/.dsh/proactive-cooldown.json,默认 180 秒)互不抢话,子 agent / teamm
安装
dsh plugin --profile web add dsh-plugin-heartbeat
沙箱测试结果
| 阶段 | 结果 |
|---|---|
| 1 安装 | ✓ |
| 2 列装 | ✓ |
| 3 挂载 | ✓ |
| 4 启动 | — |
四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。
README
dsh-plugin-heartbeat
中文 | [English](README.md)
<p align="center">
<a href="https://github.com/LittleBlackTong/dsh-plugin-heartbeat"><img alt="GitHub" src="https://img.shields.io/badge/GitHub-LittleBlackTong%2Fdsh--plugin--heartbeat-blue?logo=github"></a>
<a href="https://github.com/LittleBlackTong/dsh-plugin-heartbeat/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/LittleBlackTong/dsh-plugin-heartbeat/actions/workflows/ci.yml/badge.svg"></a>
<a href="https://www.npmjs.com/package/dsh-plugin-heartbeat"><img alt="npm" src="https://img.shields.io/npm/v/dsh-plugin-heartbeat?logo=npm"></a>
<img alt="license" src="https://img.shields.io/badge/license-MIT-blue">
<img alt="node" src="https://img.shields.io/badge/node-%3E%3D18-green">
</p>
DeepSeek Harness 的「自主神经」心跳:平时静默拍脉搏、读会话投影,只在真有变化时开口——L1 汇报、L2 问你要不要做、L4 升级提醒。没信号就不发消息。
English TL;DR — An autonomic heartbeat for DeepSeek Harness: it pulses silently, diffs the session's projections, and only opens a turn on a real change (L1 report / L2 ask / L4 escalate); no signal, no message.
v0.5 把心跳变成「懂任务」;v0.6.0 把它从节拍器升级成自主神经:每拍静默读一次会话、算出 arousal(唤醒度),有理由才开口;没东西可说时只记一条 vitals 脉搏、不注入任何消息。
注意:v0.6.0 默认行为变了
- 0.5.x:每个
intervalSeconds都注入一条消息,不管有没有事(任务快照被反复复述)。 - 0.6.0:默认是静默拍——没信号就不开轮、不注入。只有检测到「值得说」的变化时才按 L1/L2/L4 开口(见下面的等级表)。
- 配置文件升级为 v2:
intervalSeconds→baseIntervalSeconds,另加节律 / 静默时段 / 去重 / 预算 / L3 / 工作区字段。旧的 v1heartbeat.json在加载时自动迁移,不需要你动手。 - 运行态单独存
<dshHome>/heartbeat-vitals.json(与配置分离,手改配置不会污染运行态)。它跨重启保留;重启后的restartCooldownSeconds(默认 5 分钟)内即使有信号也保持安静。 - 设置侧栏的 label 跟随 App 语言——中文显示「心跳」、英文显示
Heartbeat,描边爱心图标不变。
怎么工作
Host 平面的 Cordis 插件,给每个 root agent(子 agent 跳过)挂一个 runtime —— 包括插件热挂载时已经存在的会话。
每一拍都走同样四步,插件内部不调用任何 LLM 做判断:
sensors(传感器)→ rhythm(节律)→ gate(门控)→ action(动作)
- 传感器(
lib/sensors.js):同步读会话的公开投影 ——todos、agentTeam、goal、userQuestions,外加contextPressure—— 并与上一拍看到的做 diff。只有变化才会变成信号,每个信号带权重,权重加总成 arousal(0–8)。每拍重复同一份待办清单,正是 v0.6 要消除的噪音。 - 节律(
lib/rhythm.js):下一拍间隔 =baseInterval × (1 + missed) × mood(arousal),钳制在 30 秒 – 24 小时;arousal 越高间隔越短(arousal 4 → 间隔缩到三分之一)。静默时段直接不排拍,投递后有 refractory 静默窗,另加一个确定性的小 jitter,避免多个会话在同一刻一起跳。 - 门控(
lib/gate.js):判定 L0(静默)/ L1(汇报)/ L2(询问)/ L4(升级),套用去重窗口与每日预算;你刚说过话、上下文压力过高、处于静默时段、被打盹/暂停时,都拒绝开口。 - 动作(
lib/runtime.js):L1/L2/L4 时先按需压缩长历史,再注入一条消息,内含 why-now(为什么现在叫你)、任务快照(taskNudge开着时)与该等级的指令。L0 拍不注入任何东西,只在 vitals 历史里记一行。
投递机制(沿用 v0.5):
- agent 空闲 → 立刻开新一轮;
- agent 忙 → 消息停在
next-turninbox,当前轮结束立刻被取走 —— 最多一条待处理,原地替换(inbox.replace),绝不堆积; - 连续没人回 → 达到
pauseAfterMissed(默认 3)后硬停;只有你的真实消息能复活它并重置相位; - 可选的滑动每小时上限(
maxBeatsPerHour)防「断续回复不断重置退避」的极端情况。
事件驱动(v0.6 新增):宿主提供 sessionProjections.onChanged 时,todos / agentTeam / goal / userQuestions 的变化会给会话打标记,2 秒防抖后额外跑一拍 —— 仍要过全部门控(去重、预算、静默时段)。没有该服务就退化为纯定时器模式。
自主等级(L0–L4)
等级 · 行为 · v0.6 默认
L0 · 静默拍:读、diff、记录 —— 不注入 · ✅ 所有无信号 tick 的默认
L1 · 开一轮,只汇报进展 / 风险 / 卡点 · ✅ 仅有进展类信号时
L2 · 开一轮并提问(ask_user_question):要不要现在做 / 怎么选 · ✅ 出现需要决策的信号时
L3 · 在已授权类别内自行推进,不必先问 · ⛔ 默认关闭(l3Allow: []),只做指令级授权
L4 · 与 L2 相同但要升级措辞:同一决策信号已经没人回应(missed ≥ 2)—— 只升级措辞,不加频 · ✅
需要决策(因而走 L2/L4)的信号:团队任务变可开始、团队任务变被阻塞、目标进入 blocked / paused、有待回答的用户提问。只是进展类的信号(团队任务完成、待办变化)走 L1。
一段话讲清 L3
L3 是一份按类别授权的清单 —— read(检索 / 读文件)、test(跑测试 / lint / 构建)、notes(写工作区文档 / 笔记)、continue(续写已约定草稿)。默认为空,所以 v0.6 默认行为就是「只问不做」。授权后,L2/L4 消息会多一行,说明这些类别内可以自行推进,其余一切仍然先问。L3 不是沙箱:发布、部署、删除、rm、git push、任何外部写永远不进白名单,不可逆操作仍由宿主 approval 拦截。授权只改指令,不改宿主的安全检查。
信号、去重与预算
信号 · 来源(投影 diff) · 去重键 · 权重
团队任务变可开始 · agentTeam · team:<id>:ready · +5
团队任务变被阻塞 · agentTeam · team:<id>:blocked · +4
目标受阻 / 暂停 · goal · goal:<id>:<phase> · +4
待回答的用户提问 · userQuestions · question:<callId> · +3
待办发生变化(有未完成项) · todos · todos:<signature> · +2
团队任务完成 · agentTeam · —(只汇报,不去重) · +1
arousal = clamp(Σ 权重, 0, 8);arousal 越高,下一拍间隔越短。
开口前会被这些条件抑制(都在 lib/gate.js):
- 你刚说过话(2 分钟内)—— 让位给人;
- 其他主动插件持有发言权 —— 对方 claim 后的
proactiveCooldownSeconds(默认 180 秒)内,本拍记L0 · proactive-cooldown让位;「立即一拍」是例外; - 子 agent / teammate 还在跑 —— root 只是在等委派出去的工作,本拍记
L0 · child-active、不注入、不压缩(v0.6.1;设quietWhileChildrenActive: false可恢复旧行为); - 上下文压力 > 0.9 —— 接近上下文上限时不额外开轮;
- 静默时段(默认
23:00–08:00,可配置,null关闭); - 禁用 / 打盹 / 暂停,或
mode: "silent"; - 去重:同一信号键在
dedupeWindowMinutes(默认 90)内已经问过 → 丢弃; - 每日预算:每个自然日最多
maxBeatsPerDay(默认 12,0= 不限)条主动消息,进程内跨会话共享计数。
唯一例外是面板上的 立即一拍(Beat now):手动拍跳过「礼貌闸门」(静默时段、预算、去重、你刚说过话、子会话正在跑),但仍会做等级判定,也仍要求 agent 真有可用的驱动面。
任务巡检(消息里的快照)
v0.5 起每拍可以携带一段任务快照;传感器复用同一批投影,所以「消息里说的」和「信号判的」不会互相矛盾。
投影 · 读到的内容 · 进快照的规则
todos · 会话待办清单(content / status) · 只留 in_progress / pending;completed 丢弃
agentTeam · Agent Team 团队任务板(subject / status / blockedBy) · 只留未完成条目,按「可开始 → 进行中 → 被阻塞」排序,阻塞会标明依赖
goal · 持久目标(objective / phase) · 只留 active / paused / blocked 三种相位
读之前要知道的两件事:
agentTeam投影只有启用 Agent Team 可选包时才注册;未启用时读不到 → 自动降级,不报错、快照里也没有团队任务段。todos投影每个 turn 开始会重置:心跳看到的是「最近一个 turn 内的待办」,不是会话级长期 TODO。
健壮性:
- 宿主没注册投影服务 / 会话未绑定 / 某个投影读取抛错 → 该信号或该快照降级;任何读取失败都不会让一拍丢失;
- 任务块有 800 字符硬上限;
taskNudgeMaxItems再限每类条目数(默认 5,1–20); - 插件对投影只读不写,不改你的待办、任务板或目标;
- 自定义
prompt不含{{tasks}}时,任务块自动追加。
生命体征与手动控制
设置 → Heartbeat 面板展示心跳自己的生命体征,数据来自 GET /api/heartbeat/status(每 10 秒刷新):
- 状态:运行中 / 打盹中 / 已暂停(含原因);
- 下次检查:按当前节律算出的 ETA;
- 今日预算:已投递条数 /
maxBeatsPerDay; - 最近脉冲:最近 5 行(时间 + 等级 L0–L4 + 原因)——「它刚才为什么没说话」就查这里。
按钮(POST /api/heartbeat/control):
操作 · 效果
立即一拍(Beat now) · 立刻强制一拍(手动:跳过礼貌闸门,仍判等级)
打盹 30 分钟(Snooze 30m) · 30 分钟内不拍(API 的 minutes 可配,1–1440)
暂停(Pause)
…(truncated)
以上内容来自 npm 上的 README,我们只做格式化,不改内容。
版本
npm 上共发布:12,最后更新 2026-10-30