reliable-automation

v2026.09.24

让自动运转的 agent 团队不静默失败:定时/事件任务跑了没有、跑成功没有(watchdog 查产物与心跳)、重跑不重复副作用(idempotency)。适用:常驻 agent 任务、L3 人工闸门、失败可见性。

GitHub
安装命令
npx skhub add aaaaqwq/reliable-automation
Markdown
SKILL.md

Reliable Automation

让自动运转的 agent 团队不静默失败。补的是两件基础设施:失败可见性与 幂等键约定。

主线论断:

"持久稳定" ≠ "不失败",= 失败了你知道 + 状态不丢 + 能重跑。

对 agent 团队来说,这三件事分别意味着:

支柱缺了会怎样
失败了你知道定时任务跑了没有 / 跑成功没有,你只能靠"我好像没收到消息"判断
状态不丢会话上下文一关,这次 run 等于没发生
能重跑定时器重叠触发或失败重试,同一件事做两遍、token 花两份

核心洞察:进程活着 ≠ 在工作,run 结束 ≠ 有产出,任务自己说成功 ≠ 成功。 下面的检查一律围绕"最后一次成功产出",而不是 PID,也不是任务的自我声明。

真实事故(完整记录见 references/design.md):

  • 引擎的 except Exception: return None 吞掉全部异常,一次 SSL 失败后系统 6 小时零产出,而错误日志是 0 字节;
  • 推送脚本的 subprocess.run 包在 except: pass 里、路径写死指向一个已不存在的 文件,--telegram 看起来在工作,实际什么都没发;
  • 机器上 6 个常驻进程全部在跑,但没有任何一个写"我还在工作"的信号, 也没有 watchdog。agent 侧的同一形态:run 跑完没产出、或跳过没说出口, 人类看到的只有"没消息"。

三个工具

脚本作用一句话
scripts/watchdog.py按声明式注册表检查"该发生但没发生"只读、零侵入;不读任务的自我声明,只看产出
scripts/heartbeat.py给任务写/读"我还在工作"原子写入;给没有稳定产物的长任务用
scripts/idempotency.pyclaim(key) → 是否首次让重复触发/重试不产生第二份副作用

只用标准库,兼容 Python 3.10+(脚本本身在 3.9 上也能跑)。不引入任何 pip 依赖。

什么时候用它 / 不用它

适用:

  • 团队已经有明确角色(例如本仓库的 14 个 C-suite + 92 个叶子),你想让它们 自动运转而不是每次都要有人在会话里喊一句;
  • 任务由定时器或事件触发,需要回答"跑了没有、跑成功没有";
  • 有不可逆动作(发布、资金、法律、对外声明)需要 L3 人工闸门;
  • 发现过"我好像没收到消息"这类事故,但事后才反应过来。

不适用:

  • 一次性的、交互式的会话工作——没有"持续产出"就没有可检查的新鲜度;
  • 想用它替代监控平台(Prometheus / Grafana)——这里是单机、零依赖的轻量方案;
  • 想让工具替你决定"哪一级动作需要人批准"。闸门的判级是治理决策, 见 references/design.md 的原理 4;
  • 想让工具自动修复问题。本 Skill 只做发现,不做自愈。

三分钟接入

第一步只需要一份注册表。注册表按"任务该产出什么"来描述,而不是按"任务怎么实现"—— 所以对现有 agent 任务零侵入。 examples/watchdog.registry.json 是一份可直接改的样例 (里面的路径是示例占位,代表一类常见布局,接入时换成你自己的):

{
  "tasks": [
    {
      "name": "daily-review",
      "kind": "heartbeat",
      "path": "~/.local/state/heartbeats/daily-review.json",
      "max_age_seconds": 90000,
      "severity": "high",
      "note": "每天定时触发的 agent 任务;90000s≈25h,留出跳过一天的余量"
    },
    {
      "name": "weekly-report",
      "kind": "artifact",
      "path": "~/agent-sessions/latest/weekly-report.md",
      "max_age_seconds": 604800,
      "severity": "high",
      "min_bytes": 512
    }
  ]
}

跑一次:

python scripts/watchdog.py --registry examples/watchdog.registry.json --verbose

输出(人类可读表 + --json 机器可读 + --verbose 附 detail/note):

TASK           KIND       STATUS        AGE  LIMIT  SEVERITY  DETAIL
daily-review   heartbeat  config-error  -    25.0h  high      注册表指向的路径不存在:…
weekly-report  artifact   ok            2.0h 7.0d   high      最后写入距今 2.0h

  合计:healthy=1,stale=0,empty=0,config-error=1,failed=0

config-error 在第一次接入时大量出现是好事——它精确指出"注册表说的路径 和磁盘上的现实不一致",通常就意味着某个任务的产出根本不存在。

watchdog.py

三种 kind

kind判定依据侵入性用在哪
artifact已有产物的 mtime零:不用改任何现有 agent 代码有稳定落盘产物的任务(优先选它)
heartbeat心跳文件里的 ts任务每轮调用一次 heartbeat.py beat长任务、事件驱动任务、没有稳定产物的场景
command命令退出码需要一条判断命令只有"在不在"能用命令表达的场景

状态词表

status含义该怎么办
ok新鲜什么都不用做
stale超过 max_age_seconds 没更新任务故障,去查
empty产物小于 min_bytes(抓"文件在但被清空/写成空报告")任务故障,去查
failedcommand 退出码非 0 或超时任务故障,去查
config-error注册表与磁盘现实不符:路径不存在、指向目录、不可读、JSON 坏、字段缺失改注册表或改产出,不是任务故障

config-error 与 stale 必须分开报:混在一起会让一次路径写错变成永久告警, 进而导致告警疲劳——那正是"6 小时静默"能发生的真正原因。 默认"路径不存在 = config-error";如果某个任务确实是"缺失即故障", 显式写 "on_missing": "stale"。

退出码(给 launchd / cron 判读)

码含义
0全部健康
1有任何异常(含 config-error;注册表本身读不到也算 1)
2命令行用法错误(argparse 的标准行为)

告警出口

默认不推送。 有异常时用 --alert-cmd 交给使用者自己的推送脚本:

python scripts/watchdog.py \
  --registry ~/.local/state/agent-tasks/watchdog.registry.json \
  --alert-cmd 'printf "%s" "$(cat)" | /path/to/my-notify.sh'
  • 告警全文同时从 stdin 传入,并替换命令里的 {msg} 占位符; 用 stdin 的写法不带 {msg} 也可以,watchdog 会提醒一句但不影响执行;
  • 告警命令的退出码会被检查:它失败本身也会让 watchdog 退出 1;
  • 没有异常时不会执行告警命令。

挂到 launchd / cron 时建议 5 分钟一次。macOS 用 StartInterval 或 StartCalendarInterval,不要用 KeepAlive——watchdog 是一次性进程。

heartbeat.py

给没有稳定产物的长任务 / 事件驱动任务用。任务每完成一轮写一次:

from heartbeat import write_heartbeat  # 或直接调用 CLI
write_heartbeat("daily-review", note="第 120 轮")
python scripts/heartbeat.py beat daily-review
python scripts/heartbeat.py check daily-review --max-age 90000
python scripts/heartbeat.py list
  • 写入是原子的(临时文件 + os.replace),读到的永远是完整 JSON;
  • 目录优先级:--dir > RELIABLE_HEARTBEAT_DIR > $XDG_STATE_HOME/heartbeats > ~/.local/state/heartbeats(所以心跳落在 agent 会话之外,会话结束不影响它);
  • 心跳名只允许 [A-Za-z0-9._-],避免路径穿越;
  • check 退出码:0 新鲜 / 1 过期或缺失,可直接用在 shell 的 && 链里。

idempotency.py

让重复触发与失败重试变成安全动作。关键约定:

<task_type>:<entity>:<time_bucket>
例子含义
daily-review:inbox:2026-09-16今天这个收件箱只复盘一次
publish:weekly-report:2026-W38这周这份报告只发布一次
approve:publish-2026-09-16:human这条 L3 放行只记一次(闸门凭证)

先领凭证,再干活:

python scripts/idempotency.py \
  --ledger ~/.local/state/agent-tasks/ledger.jsonl \
  claim 'publish:weekly-report:2026-W38' \
  && ./publish-report.sh

退出码:0 = 首次(去干活)/ 1 = 已做过(跳过)。set -e 下天然安全。

Python 里:

from idempotency import claim
if claim("daily-review:inbox:2026-09-16", ledger_path, ttl_seconds=86400):
    run_daily_review()
  • 账本是 JSONL 追加写,用 O_APPEND + 单次 os.write + POSIX flock, 并发调用不会撕裂行、也不会同时领到同一张凭证——定时器重叠触发时这一点很关键;
  • ttl_seconds 给周期性任务:同一键在 TTL 内只允许一次,过期后可再来;
  • time_bucket 由调用方决定:取粗会漏做,取细会重复做;
  • gc 子命令可以清理过期键与坏行。注意它是"写临时文件 + os.replace"(换 inode), 只应在没有并发写入时运行(例如 cron 的安静时段)。

和 agent 团队治理的接口

  • 闸门(L3):自动化只能准备到"待批准"。批准这件事本身也应该是磁盘上的事实 (用 claim('approve:<action>:<bucket>') 记一次),否则会出现"人批了 agent 不知道" 或"agent 以为批了"两种失败。
  • 不能自证:watchdog.py 是外部检查者,不解析任务的自我声明。 重要完成声明的独立复核属于 Governor 的职责(本仓库 agents/governor/ 的岗位契约), 不属于执行者自己。详见 references/design.md 的原理 5。
  • 台账优先:如果台账/队列文件本身停了,单个 run 的成功没有意义—— 所以注册表里应该有一条检查台账新鲜度的 artifact 项。

边界与不做的事

  • 不做自愈:不自动重启、不自动补单。没有护栏和停止条件的自动重启, 会把"静默故障"升级成"静默重启循环"。
  • 不判级:"什么算 L3"是团队治理决策,工具只负责把异常算出来。
  • 不替代监控平台:这是单机、零依赖的轻量方案。
  • 不实现常驻循环:skills/ 不是 runtime;watchdog 由 launchd / cron 拉起。
  • 不是运行证据:结构检查通过不代表你的 agent 团队真的在健康运转, 阈值和注册表要按自己的任务调。
  • watchdog 自己失联是已知盲区:它没被拉起、或告警通道坏了,异常仍无人知晓。 缓解方式见 references/design.md。

阈值怎么定

max_age_seconds 建议取正常产出间隔的 3~5 倍,并把跳过场景算进去 (周末不跑的任务按周设,不要按天设)。定太紧会抖动误报,误报直接导致告警疲劳—— 宁可放宽,也不要制造噪声。

测试证据

三份标准库 unittest 测试,共 90 例,覆盖健康/过期/缺失/配置错误/坏 JSON、 心跳写入读取与原子性、幂等键的首次/重复/TTL 过期/并发追加/多进程竞争:

python scripts/test_watchdog.py
python scripts/test_heartbeat.py
python scripts/test_idempotency.py

这些是结构证据:它们证明脚本的行为符合约定,不证明任何具体 agent 任务 或守护进程的健康状况。

参考

发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

Sep 24, 2026

分类

未分类

许可证

MIT

源路径

skills/reliable-automation

默认分支

main

最新提交

b996aac

Tree SHA

07e787b