心跳医生 · Agent 自愈监测仪
"Agent 不是部署完就完事,是活着才算数。"
长任务 Agent 最大的敌人不是模型笨,是卡死、死循环、静默失败——看起来在干活,其实卡在同一个错误上重复 20 次。没有监测,Agent 就是玄学系统。
何时用
- 用户问"Agent 最近有没有卡过 / 死循环 / 静默失败"
- 长任务跑完要复盘"这段为什么没跑出结果"
- 想让失败沉淀下来,重启后不再踩同一个坑
- 定期体检 Agent 健康度(可挂自动化周跑)
- 触发词:心跳医生、Agent 体检、卡死、死循环、静默失败、错误级联、墓碑、自愈
命令(v0.2a)
# 1. 全量体检(默认只读、不写任何东西)
python scripts/heartbeat_doctor.py --out out/体检报告_$(date +%Y%m%d).txt
# 2. 单条 trace 深度诊断
python scripts/heartbeat_doctor.py --trace "<trace 文件绝对路径>" --verbose
# 3. 沉淀墓碑(只有加这个参数才落盘;默认 dry-run 只列候选)
# 落盘时每个 L3 墓碑同时收口进 agent-asset-loop 中央注册表(failure_case, weight 1.5)
python scripts/heartbeat_doctor.py --write-tombstones
# 不想收口进注册表时(仅保留 heartbeat-tombstones 明细副本):
python scripts/heartbeat_doctor.py --write-tombstones --no-registry
# 指定其他注册表路径(演示/隔离):
python scripts/heartbeat_doctor.py --write-tombstones --registry /path/to/assets.jsonl
# 4. 晋升预览:扫描墓碑、过晋升闸门,列出可晋升候选(dry-run,不写)
python scripts/promote.py
# 5. 晋升注入:把通过闸门的墓碑送进用户级长期记忆(每次会话自动注入,跨 agent 生效)
python scripts/promote.py --apply
> 晋升链纪律:检测/沉淀 = 只读,--write-tombstones / --promote-apply 才落盘;晋升默认人工确认。promote.py 默认目标记忆文件是 ~/.workbuddy/MEMORY.md(每次会话开局自动加载),可用 --home / --memory 指向别处(演示或隔离时用)。
> 墓碑单一总账:每个 L3 墓碑落盘时,同时收敛为一条 failure_case 资产(权重 1.5,最高 ROI)写入 agent-asset-loop 的中央注册表 ~/.workbuddy/asset-registry/assets.jsonl——这就是「避免两套总账」的实现:heartbeat-tombstones/ 只是带完整 trace 证据的明细工作副本,跨会话真正被 retrieve_assets/diagnose/collect_all 消费的 ledger 是资产注册表。去重靠由墓碑 fingerprint 稳定派生的 asset_id(ast_hb+sha1[:12]),与日期无关,跨天重跑不产生重复资产。
回归套件(改动阈值或检测逻辑后必须跑,它是唯一能证伪本检测器的东西):
python scripts/selftest.py # 当前 85 项断言全绿(含 v0.2a 的 bad_param / premature_stop / 晋升链 / 墓碑收口注册表)
检测什么
| 模式 | 触发判据 | 默认档 |
|---|---|---|
edit_undo_cycle |
同一文件 + 完全相同的编辑内容 重复 ≥3 | L1->L3 |
search_spiral |
同一检索调用(同 tool + 同输入)重复 ≥3 | L1->L3 |
permission_hammer |
同一条失败命令重试 ≥3 | L1->L3 |
error_cascade |
连续 ≥5 次工具失败 | L2/L3 |
no_artifact_longrun |
跑 ≥300s、≥20 次工具调用、零产出物 | L2 |
silent_death |
trace 报错 且最后一个工具调用就是失败的 且无产出 | L3 |
bad_param (v0.2a) |
工具因 路径/权限/参数/命令 类错误失败,把"为什么失败"翻译成可补的协助项 | L1 |
premature_stop (v0.2a) |
矛盾式:trace 声称 ok 但末步报错(status 在撒谎)-> L2;空心式:ok+无产出+≥15 次调用 -> L1(标 [启发式]) | L2/L1 |
stall |
相邻 span 空窗 ≥120s | INFO |
unreliable_input |
输入已达采集截断上限,无法判重 | INFO |
context_cliff |
上下文压力 | v0.1 不启用(见下) |
> premature_stop 与 silent_death 刻意不重叠:silent_death 要求 trace 本身 status∈(error,cancelled)(诚实报错);premature_stop 管的是"trace 撒谎说完成"。两者同时只会命中其一。
分级决策(核心)
L1 轻度偏移 -> 可自我纠正:nudge 一条具体提示,换手段
L2 中度偏移 -> 人工介入:需要人看一眼再决定
L3 严重卡死 -> 写墓碑 + 升级:重启会踩同一个坑,必须先沉淀
INFO -> 只记录不升档:空窗、不可靠输入等"自身不是罪证"的信号
双闸门规则(防误杀的核心):循环类判定必须同时满足
- 次数闸门:同一指纹重复 ≥ min_repeat(默认 3)
- 无进展闸门:这些重复之间没有出现新的指纹
任一单独成立最高只到 L2。只按次数升级会误杀良性重试(长任务里"重复读同一份规格"完全正常),而误杀的代价高于漏报。
自愈策略库
每个模式都绑定了一套"换什么"而非"更用力"的动作,见 heartbeat_doctor.py 的 HEAL_PLAYBOOK。设计原则:卡住时换手段,不是加强度。
墓碑 + 晋升链(让坑被别的 agent 学到)
> v0.2a 核心问题:墓碑文件躺在 ~/.workbuddy/heartbeat-tombstones/ 里,没有 agent 会主动去读它。写经验 ≠ 别的 agent 学到经验。本版本把"墓碑 -> 晋升 -> 注入长期记忆"整条链打通。
整条链路:
L3 检测到退化
| --write-tombstones
v
墓碑 ~/.workbuddy/heartbeat-tombstones/<指纹>.json (明细工作副本,含完整 trace 证据)
+-- 同时收口 --^
| v
| agent-asset-loop 中央注册表 <- 唯一被 retrieve/diagnose/collect_all 消费的 ledger
| ~/.workbuddy/asset-registry/assets.jsonl (failure_case, weight 1.5, 去重靠 asset_id)
| ----------- ^ 这就是「避免两套总账」
|
| promote.py --apply
v
过晋升闸门(可复现 ∧ 条件化 ∧ 可操作) <- 系统性模式单次即过;其余需 hit_count≥2
|
v
注入用户级 ~/.workbuddy/MEMORY.md 专节
"## Agent 踩坑经验(心跳医生晋升 · 跨会话防重踩)"
| (每次会话开局自动注入 -> 别的 agent 下一轮就能读到)
v
墓碑打 promoted 标记 -> 不重复晋升
为什么这条链是必须的:用户级 MEMORY.md 是 WorkBuddy 每次会话开局自动加载的通道,只有送进这里,经验才会被"投递"给未来的 agent。墓碑本身是给人/Agent 手动读的,不读就等于没写。晋升链把"值得长期记的坑"自动搬运过去,并靠稳定 entry_id 去重(同一 模式+条件 只进一次)。
晋升闸门(三道,缺一不可)
- 可复现:不是偶发(
hit_count≥2,或属SYSTEMATIC_PATTERNS= edit_undo_cycle / search_spiral / permission_hammer / error_cascade,这类单次即具推广性) - 条件化:能表达成"某类条件下会踩",而非"这次这个文件特殊"(由
CONDITION_TEMPLATES翻译) - 可操作:有明确的规避动作(墓碑的
avoid/try_instead非空)
三道全过且 --apply 才落盘;偶发无动作、或重复不足 -> 默认跳过,不污染记忆。
墓碑条目结构
{
"schema": "heartbeat-doctor/v1/tombstone",
"pattern": "edit_undo_cycle",
"severity": "L3",
"hit_count": 1,
"evidence": { "trace_id": "...", "span_ids": ["..."], "detail_label": "..." },
"avoid": "停止对同一文件做第 N 次盲改,先回滚到最近一个 ok 检查点",
"try_instead": ["把当前内容完整读出再判断", "把改动拆成更小的一处"],
"expires_at": "...",
"promoted_at": "...", "promoted_id": "hd-promo-xxxxxxxxxxxx"
}
被晋升进 MEMORY.md 的条目长这样
- [2026-09-13] [类型:经验|Agent防重踩] [标签:心跳医生|edit_undo_cycle] [对任一文件做'内容完全相同'的重复编辑(真 undo 循环) -> 规避: …] [实测] <!-- hd-promo-6bbed8d7dced -->
行尾 <!-- hd-promo-… --> 是隐藏去重标记,渲染对人不显示,但保证幂等(重复晋升自动跳过)。
诚实边界(不要越过)
- 只能事后复盘,不能实时拦截。实测 trace 文件 mtime 与
trace.endedAt一致 -> 整段跑完才落盘。要做实时拦截必须补埋点通道。 - 不要用
sessions/*.json的lastHeartbeat做存活心跳。实测它只在会话启动时写一次(会话开始后 111 秒再读,值仍停在启动时刻)。 generationspan 的输入不可用于判重。实测被采集层按 10 万字符截断并复用:长度齐刷刷落在 100000/99993/99994,同一输入指纹对应数十种不同输出。若纳入循环池,单条 trace 就会误报 64 次。- Context Cliff v0.1 刻意不启用。缺 token 真值(
trace.totalTokens实测恒为 0),硬凑的字符数代理指标会对几乎每个会话误报,等于没有信息量。 - 阈值已基于 91 条真实 trace 标定过一轮,但 L3 墓碑通路在真实数据上尚未被触发(仅经合成用例验证)——这是已知缺口,不是已验证能力。
bad_param/premature_stop为 v0.2a 新增。bad_param只定性+攒"需你协助清单",单次参数错不升档(反复同性质由 error_cascade / permission_hammer 负责)。premature_stop的"矛盾式"(status 撒谎)确定性高判 L2;"空心式"标[启发式],切勿据此升级(纯分析任务可能合法)。- 晋升链只搬运"值得长期记"的坑。墓碑≠长期记忆;偶发、无规避动作的坑不过闸门,不进
MEMORY.md,避免记忆被一次性噪声污染到没人再信。
文件
heartbeat-doctor/
+-- SKILL.md
+-- scripts/
| +-- heartbeat_doctor.py # 核心检测器 + 报告 + 晋升链逻辑(零依赖,Python 3.8+)
| +-- promote.py # 晋升链 CLI(墓碑 -> 过闸门 -> 注入用户级 MEMORY.md)
| +-- selftest.py # 回归套件 85 项断言
+-- references/
| +-- design-rationale.md # 设计依据 / 四份材料取舍 / 埋点路线图 / 四本书理论落地映射
| +-- tombstone-schema.md # 墓碑条目结构 + SRE 复盘模板 + Resilience 包络理论
| +-- healing-playbook.md # 每个检测模式 -> 自愈策略 + 四条书目出处
+-- out/ # 报告输出
> 理论底座(已读完抽取):自愈策略与分级判据已逐项锚定到
> — Site Reliability Engineering(Beyer, Jones, Petoff, Murphy 等,O'Reilly 2016)
> — Release It! 2nd ed.(Michael T. Nygard,Pragmatic 2018)
> — Observability Engineering(Majors, Fong-Jones, Miranda,O'Reilly 2022)
> — Resilience Engineering in Practice(Hollnagel, Pariès, Woods, Wreathall 编,Ashgate 2011)
> 详见 references/healing-playbook.md 与 references/tombstone-schema.md。
版权与授权
本技能由 七仔的AI工具箱 独立开发,方法论参考上述列出的 observability 与 resilience engineering 文献,未直接引用原文内容。
授权方式:MIT License
Copyright (c) 2026 七仔的AI工具箱
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
作者与联系方式
本 skill 由「七仔的AI工具箱」维护。欢迎就技能使用、问题反馈与二次分享交流:
- 微信公众号:七仔的AI工具箱
- 抖音号:七仔的AI工具箱
- CSDN 博客:七仔的AI工具箱
本 skill 可免费使用与二次分享;转载请注明出处「七仔的AI工具箱」。