dsh-plugin-heartbeat
Verifieddsh-plugin-heartbeat · v0.8.0 · MIT · Web UI
Autonomic heartbeat for DeepSeek Harness: silent pulses diff the session's todos / team board / goal, then speaks only on a signal (report / ask / escalate) with dedupe, quiet hours and a daily budget; carries a heart mark in the settings sidebar.
Install
dsh plugin add dsh-plugin-heartbeat Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Readme
dsh-plugin-heartbeat
中文 | English
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) | 这个会话的心跳停到恢复为止 |
| 恢复(Resume) | 解除暂停 / 打盹,按当前节律重新排拍 |
| 重置(Reset) | 清掉打盹与暂停并恢复 |
路由(回环、插件自建):
| 方法 | 路径 | 用途 |
|---|---|---|
| GET / POST | /api/heartbeat/config |
读 v2 配置 / patch 合并(校验后热应用) |
| GET | /api/heartbeat/status |
配置摘要、今日预算、每会话生命体征 + nextBeatAt + 最近 5 拍 |
| POST | /api/heartbeat/control |
{action: "beat-now" | "snooze" | "pause" | "resume" | "reset", sessionId?, minutes?} |
指定 sessionId 时只作用于该会话,省略则作用于全部存活会话。
面板文案走 DSH 自己的语言层:client 半注入 locale、注册 heartbeat 命名空间的中英语言包,并在 settings section 上声明 locale: 'heartbeat'——所以面板跟随「设置 → 通用设置 → 语言」,切换 App 语言即时重渲染(不靠浏览器语言猜)。侧栏 label 走同一套语言包(中文「心跳」/ 英文 Heartbeat),语言切换后爱心图标的认领会自动重跑,标记始终落在当前那一行。
诊断(v0.8.0)
面板能直接回答「它刚才为什么没开口」,不用翻日志:诊断区列出最近 5 拍的时间 / 等级 / 原因(如 没有新信号、静默时段、proactive-cooldown(让位给 memory)、child-active),并显示当前发言权持有者(来自 status 路由新增的 lock 字段)——一眼就能解释一段安静。L3 区也从只显示勾选框改为同时给出「已授权类别」摘要。
与其他插件的发言权仲裁(v0.7.0)
同一段对话里可能有多个主动插件都想说话。v0.7 加了一套极小的共享协议,避免两个插件抢话:
- 投递前先 claim
${DSH_HOME:-~/.dsh}/proactive-cooldown.json——{ version: 1, speaker: "heartbeat" | "memory", at: <epoch ms> },tmp+rename 原子写; - 若记录在
proactiveCooldownSeconds(默认 180,0= 关闭)内且属于其他 speaker,本拍让位并记L0 · proactive-cooldown(让位给 …),之后某一拍再试; - 立即一拍(Beat now) 绕过仲裁 —— 手动拍是你的显式请求;
- 模块零 import 自 memory 插件:双方各自独立实现同一协议,任一方可单独安装 / 升级 / 卸载;读不到文件按「无锁」处理,写失败降级为「放行」(是礼貌,不是硬依赖)。
生理签名(v0.7.0)
心跳不是 cron:它是有身体的。L1/L2/L4 消息可以在 why-now 之后带一行很短的「状态」,例如 〔状态:距你上次说话 2h · 今天第 3 次找你〕:
- 由
lib/physiology.js渲染,硬上限 60 字;physiologyLine(默认true)是总开关,自定义 prompt 可以用{{physiology}}占位符指定位置; - vitals 新记
lastUserMessageAt/lastTurnEndAt/busyMsToday,所以这行跨重启仍成立; - 用户 20 分钟内出现过 → 不显示(是观察,不是打扰);措辞随时段变化(早上好:/ 傍晚了:/ 状态:),深夜更短更轻、不带计数。
userActiveSuppressSeconds(默认 120,0 = 不抑制)把「你刚说过话就静默」的窗口做成可配置;它用持久化的 lastUserMessageAt,跨重启也生效。
子智能体执行中胶囊(v0.7.0)
后台子智能体还在跑时,会话头部可能看起来已经结束。
这里规避的已知宿主行为:DSH 会话列表的 running 只在 agent 创建 / 销毁时重算,子会话 api-session/status 按 session 资源转发——父会话空闲时,它的视图可能拿不到子会话运行状态的变化,于是头部显示「N 个子智能体」而不是「正在运行」,主会话看起来已完成。
现在插件在 GET /api/heartbeat/status 的每个会话上报告 children: { total, running, runningIds }(来自 subagentCatalog / agentTeam 的 wire view 加活 agent registry——和 runtime 保持静默用的是同一份宿主真值),客户端在 conversation.session.header.actions 注册头部胶囊 「子智能体执行中 · N」,每 5 秒轮询;子任务结束自动隐藏,并跟随 App 语言。浏览器标签页隐藏时暂停轮询,回到前台立即恢复。纯插件层,零宿主改动。

安装
dsh plugin --profile <profile> add dsh-plugin-heartbeat
(包内置 dsh.bundle manifest,dsh plugin add 会把它自动挂进 profile 的 bundles 层;dsh-market 里的一键安装同此通道。)
重启 DSH 后生效。之后在 设置 → Heartbeat 面板里随时改模式、间隔、静默时段、去重窗口、预算、L3 授权与任务巡检,保存即热应用(无需重启)。
⚠️ 不要再往 profile 的
cordis.patch.yml里手写- insert: {id: dsh-heartbeat, ...}: 那会与 bundle manifest 的自动挂载产生两条同名 entry,整个 profile 会以duplicate loader entry id "dsh-heartbeat"启动失败(2026-08-18 实机事故, 当时撞的是 memory 插件的同名条目)。运行期配置走<dshHome>/heartbeat.json; 覆盖 composition 键(如prompt)用不带 insert 的 id 覆盖条目,见下。
设置侧栏图标
设置侧栏里的这一项带一个描边爱心标记:16×16 网格、stroke-width: 1.3(与宿主 ICON_MEDIUM_STROKE 一致)、纯黑描边只提供 alpha,可见颜色由 mask-image + currentColor 决定,深浅主题都跟随。规格与 dsh-plugin-memory 的图标完全统一。官方 settings.section 槽只投影 id / order / label,没有 icon 字段,所以插件按当前语言的 label 文本(中文「心跳」/ 英文 Heartbeat)精确认领自己那一行,并用 MutationObserver 跟随语言切换(认领键随 label 变化)与面板重挂载重新认领,卸载时清理干净。
版本要求
- DSH:官方 0.2.1-rc.5 系列。
package.json已声明engines.dsh: "^0.2.1-rc.5"——宿主用 semver(includePrerelease: true)做引擎闸门,注意 prerelease 语义:^0.2.1/>=0.2.0都匹配不上0.2.1-rc.5,必须写^0.2.1-rc.5才认;它同时覆盖将来 0.2.1 正式版,而 0.3.0-rc.1 会被判不兼容。 - session format v4(官方 0.1.7+)。心跳消息的 source 使用 producer-owned kind
plugin:heartbeat;v4 的原生准入拒绝 v3 的{kind:'plugin', plugin:'heartbeat'}wrapper。v4 与 v3 白名单互斥、无法两全。 - 0.4.2 是最后一个 v3 兼容版本;0.5.0 起只支持官方 DSH 0.2.1-rc.5 系列。0.3.x 暂不支持。
- 请用 0.8.0:已发布的 0.6.1 误读会话投影——
stateOf()返回的是原始 state,普通会话的subagent身份是{},被判成「这是子会话」→ 心跳完全挂不上,child-active 闸门也永不生效;0.6.2 修了这个问题,0.7.0 加了发言权仲裁、生理签名与子智能体胶囊,0.8.0 再加诊断面板、面板内可调仲裁/生理签名配置,以及更省电的胶囊轮询。 - 实机验证(DSH Desktop 0.2.1-rc.5,重启后加载 v0.6.0 构建):
GET /api/heartbeat/status→ 200,返回 2 个 live session(含sessionId/cwd/nextBeatAt/ 预算 / history);/api/heartbeat/config返回 v2 配置;- 已有的 v1
heartbeat.json自动迁移到 v2(intervalSeconds→baseIntervalSeconds,新增 mode / 静默时段 / 预算等字段),并自动创建heartbeat-vitals.json; - 静默拍生效:临时把间隔调到 60 秒等一拍,两个会话的 vitals 各记录一条
L0 · 没有新信号 · delivered: false,每日预算仍为 0(零注入);随后已恢复 600 秒 / jitter 60; - 热插拔:运行中
dsh plugin remove使/status从 200 变 401(路由消失、无崩溃),add回来恢复 200 且会话恢复; - 控制路由:未知 action 返回 400,
snooze让snoozeUntil生效,reset清零恢复; - 侧栏爱心图标与
Heartbeat区块正常渲染。 未单独实机验证(仅单测覆盖):事件驱动时延(≤10 秒)、pre-beat 压缩、真实的 L2 提问投递。v0.6 自主层由 186 个单测覆盖。
配置(v2)
用户配置在 <dshHome>/heartbeat.json(面板可写,也能手改;用 schemastery 校验)。composition 配置是基线层,用户文件覆盖它,匹配到的 workspaces 条目再覆盖两者。
| 键 | 默认 | 含义 |
|---|---|---|
enabled |
true |
总开关 |
mode |
"assist" |
"assist" = 有信号就开口;"silent" = 永不开口(照常拍脉搏并记录) |
baseIntervalSeconds |
600 |
基础拍间隔(30–86400 秒);退避档位以它为基准 |
pauseAfterMissed |
3 |
连续 N 拍没人回 → 硬停(0 = 关闭);你的下一条消息复活它 |
backoffSeconds |
null |
显式退避档位数组(null = 自动推导 [base, 2×base, 3×base]) |
compactBeforeBeat |
true |
开口前先把长历史压成 checkpoint(尽力而为,失败不影响投递) |
maxBeatsPerHour |
0 |
滑动每小时投递上限(0 = 关闭) |
maxBeatsPerDay |
12 |
每日投递预算(0 = 不限),进程内跨会话共享 |
dedupeWindowMinutes |
90 |
同一信号键在该窗口内不重复问(0 = 关闭) |
refractorySeconds |
300 |
投递后至少安静这么久才允许下一拍 |
jitterSeconds |
60 |
确定性的每会话抖动(0–600 秒),让多个会话错开 |
restartCooldownSeconds |
300 |
重启后即使有信号也先安静这么久 |
quietHours |
{"start":"23:00","end":"08:00"} |
该时段内不开口(支持跨午夜;null = 关闭) |
quietWhileChildrenActive |
true |
子 agent / teammate 还在跑时保持静默(本拍记 L0 · child-active、不注入);手动「立即一拍」是例外(v0.6.1) |
proactiveCooldownSeconds |
180 |
与其他主动插件的共享发言冷却(0 = 关闭;手动「立即一拍」绕过) |
userActiveSuppressSeconds |
120 |
用户最后一条消息后这么久内不开口(0 = 不抑制) |
physiologyLine |
true |
心跳里带一行生理签名(自定义 prompt 可用 {{physiology}} 指定位置) |
taskNudge |
true |
是否在心跳里带任务快照 + 提问指令 |
taskNudgeMaxItems |
5 |
任务快照每类条目上限(1–20) |
l3Allow |
[] |
L3 授权类别:read / test / notes / continue |
prompt |
null |
自定义提示词模板({{time}} / {{tasks}} / {{reason}} / {{physiology}});null = 内置分级提示词 |
workspaces |
{} |
按会话绝对 cwd 的工作区覆盖 |
workspaces 条目可写:enabled、mode、baseIntervalSeconds、quietHours(对象或 null)、maxBeatsPerDay、l3Allow。优先级:composition 配置 < 用户配置 < 命中的工作区覆盖。
v1 → v2 迁移:v1 文件(intervalSeconds、无 version)在加载时迁移——intervalSeconds 变成 baseIntervalSeconds,其余字段语义不变,缺失字段取 v2 默认值——并回写成 v2。坏配置文件会被忽略(内存里保留上一份好配置,下次启动用默认值);无法识别的键不会写回。运行态是另一个文件:<dshHome>/heartbeat-vitals.json。
自定义心跳提示词(composition 配置,不带 insert,避免与 bundle 层重复):
- id: dsh-heartbeat
config:
prompt: |
【心跳】{{time}} —— 为什么现在叫你:{{reason}}
{{tasks}}
有实质进展/风险就简短汇报;要动手先问。
{{time}}、{{tasks}}、{{reason}} 会被替换。模板里没有 {{tasks}} 时任务块追加到末尾;没有 {{reason}} 时 why-now 行追加到末尾。
为什么不用 settings 面板的通用 namespace?DSH 的 settings wire 只服务一张 硬编码白名单(
WEB_SETTINGS_NAMESPACES),插件无法把自有 namespace 暴露给 浏览器写入。本插件因此在 host 自建了/api/heartbeat/*路由, 设置面板里的 Heartbeat 区块直连这些路由,写入即热应用。
开发
node --test
无参 node --test 会自动发现 test/*.test.mjs;测试覆盖 runtime、tasks、sensors、rhythm、gate、vitals、config-store-v2、prompt-levels、wiring-v06、settings-nav-icon、client-half、proactive-lock、physiology、prompt-physiology、activity,当前共 186 个用例;CI 用 .github/workflows/ci.yml 跑同一套。
设计与实施记录在仓库里(不进 npm 包):docs/specs/2026-10-30-heartbeat-v0.6-design.md、docs/plans/2026-10-30-heartbeat-v0.6.md。跨插件共享发言协议见 docs/PROACTIVE-PROTOCOL.md;贡献与安全策略见 CONTRIBUTING.md、SECURITY.md。
已知边界
- 心跳只在 DSH 进程活着时存在(app 关了就停)。
- 每个 root agent 各自一条 runtime;子 agent 会话永不挂心跳(按身份守卫,见下)。
- 默认安静:没有变化就不说话。存在感现在靠面板的生命体征与「立即一拍」,而不是按点叨叨——想让它更活跃就调
mode/ 去重 / 静默时段相关配置。 - 不打断委派工作(v0.6.1):子 agent / teammate 在跑时,root 只是因为等它才 idle;此时心跳若开新一轮,会在工作中间插入一条消息——客户端把「正在运行子会话」展示为当前活跃 turn 的一部分,新 turn 会把它顶掉,你就看不出后台还在跑了(pre-beat 压缩还可能把活跃 turn 折进摘要)。所以这类拍只记
L0 · child-active、不注入、不压缩,直到子任务全部结束;手动「立即一拍」仍可强制触发。 - 头部胶囊才是「仍在跑」的可靠指示(v0.7.0):子任务运行期间,宿主会话头部可能把父会话本轮显示为已结束(见上方已知宿主行为);插件胶囊会在同一个头部里把运行状态一直显示到所有子任务结束。
- 永不挂到子 agent 会话(v0.6.1):teammate / subagent 是
mode: "continuable"的子会话,用户可能从侧栏把它当顶层会话重新打开 / resume;按宿主语义这种 resumed child 可能被算作 root(无 owner),所以attach()会先读该会话的subagent投影身份,非空就跳过不挂(日志heartbeat: skip subagent session);运行中的 runtime 也会在每次 tick 复查身份,一旦发现自己是 subagent 会话就dispose()摘除。真机排查:当前实例 3 个运行时全是 root 用户会话,逐个扫 teammate 子会话日志心跳注入 0 条(那些日志里的plugin:heartbeat字样是工具输出 / 文档内容,不是注入消息)。另有一个不属于插件职责的边界:fork 型子 agent 会继承父会话历史,fork 前父会话里已有的心跳消息会随 seed 带过去——这是 fork 语义本身;spawn 型子 agent 不受影响。 - L3 不是沙箱:只改指令;不可逆操作仍由宿主 approval 拦截。
- 运行态(
heartbeat-vitals.json)是进程内的,原子写 + 短防抖;两个 DSH 进程共用一个 home 时是「尽力而为」,不是强一致。 - 事件驱动需要
sessionProjections.onChanged;没有该服务时退化为纯定时器模式(门控相同,只是反应没那么快)。 - 插件在会话已打开后才热挂载时,会对现有 root agent 做一次幂等补挂;之后新开的会话仍走
agent/created。 - 插件依赖
agent.followup/agent.inbox等 agent-loop 驱动面——不属于 DSH 公开 API catalog,已锁定 DSH 0.2.x;宿主若换实现,最多丢一拍并打一条 warn。 taskNudge: false会同时去掉任务快照与巡检 / 提问指令。- 与 dsh-plugin-memory 互相独立、零依赖;两者同时安装时消息在同一个 inbox 队列里排队,不冲突。
- 版本要求:官方 DSH 0.2.1-rc.5 系列、session format v4;
engines.dsh: "^0.2.1-rc.5",细节见上方「版本要求」。
License
MIT