system-droid:一只自己巡检、自己修复我所有系统的看门狗
我的本地服务总是悄无声息地挂掉:早晨的简报不来了,令牌过期了,备份卡住了——而我总是最后一个知道。于是我做了一个 launchd droid:每两小时检查一切,能安全修的交给 Claude 智能体去修,只有真正需要人时才给我发消息。
TL;DR
system-droid 是一个小小的 Go 程序,launchd 每两小时启动它一次,Mac 每次从睡眠唤醒后也会再启动它。它逐项检查我所有的本地服务,一切正常时保持沉默,只有真出了问题才会发消息到 Telegram。每天发一条简短的“一切正常”消息,让沉默永远不等于“不知道”。而当某个服务真的出故障时,它会先自己动手修:一个独立的 Claude(Opus)智能体找到原因并修复,只有修不好时才来找我。
◆ Усі системи в нормі · 10/10 · 2026-08-17 09:00
— OpenClaw / Resonance
◆ styletts2-ua (tts) — локальний український TTS :8123
◆ openclaw gateway (:18789) — ядро агента Resonance (launchd + порт)
◆ openclaw hooks — hooks-ендпоінт увімкнено
— Інфраструктура
◆ cli-proxy Claude auth — робочий Claude-токен (валідність, авто-оновлення)
◆ system-droid — сам вартовий (надсилає ці звіти)
◆ otel-block — корпоративна телеметрія заблокована у firewall
◆ vault git backup — git-копія Obsidian-сховища на GitHub为什么要做这个
过去几个月里,我不知不觉攒下了一整套本地自动化:OpenClaw 智能体(“Resonance”)、乌克兰语 StyleTTS2 语音服务器、挡在 Claude 前面的 LLM 代理、Obsidian 的每日笔记生成器、笔记库的 git 备份、拦截公司遥测的防火墙规则。这些东西只有在正常运行时才有用。而它们坏掉的方式永远相同——悄无声息。
早晨的简报没有送达,我到晚上才发现。调度器杀掉了进程,可最后一次运行看起来仍然“成功”。授权令牌过期了,生成任务能靠应急兜底文本活上好几周。这些故障没有一个会自己发声:服务不会抱怨——它只是消失。我需要有个东西替我盯着。
它是怎么运作的
整套系统就是一个二进制文件加一份 config.json,里面是两类检查:HTTP 类(服务在 /health 上应答)和命令类(脚本以 0 退出)。launchd 按计划启动 droid,它并行跑完所有检查,然后决定到底需不需要打扰我。把一个新系统纳入监控,只需在配置里加一行——无需重新编译。
核心原则是节约注意力。没有需要打开的仪表盘,没有每两小时一条的“一切正常”刷屏。消息只有三种:每日“全部正常”汇总;“坏了——我已经修好”;以及“坏了——需要你”。除此之外,都是沉默。
自我修复
当某项检查失败时,droid 不会立刻来找我。它先启动一个 Claude 智能体,工具受限,只对特定目录有写权限。每次尝试之前,都会给所有工作目录拍一张 git 快照,智能体做的任何改动都能用一条命令回滚——快照哈希就写在消息里:
🔧 system droid — полагоджено автоматично, 1 спроба (2026-08-02 07:41)
Було зламано:
• news digest freshness — застарілий дайджест: 2026-07-31 (2 дні тому)
✅ Зараз усі перевірки зелені.
↩️ Відкат змін агента (git reset --hard на знімок):
• ~/Developer/system-droid @ f037cfb
• ~/Developer/obsidian-cron @ e98746c这不是玩具:这个智能体真的在一次夜间断网后重新生成过过期的摘要,自己重载过卡死的 launchd 任务,也自己诊断出:令牌的问题必须由我出面才能解决。在消息里我能看到坏了什么、智能体做了什么、结果如何。
它已经抓住了什么
七周里,droid 把好几起原本可能悄悄存在好几天的故障,变成了我第一时间看到的消息:
- 被杀死的简报生成器。二进制重新编译后,macOS 内核直接击毙了进程——早晨的简报根本不会发出,也不会有人察觉。
- 过期的 refresh token。代理不再续期访问令牌,所有 LLM 调用都返回 401。droid 说得很直接:这个只能重新登录解决——一条命令,两分钟。
- 兜底文本冒充分析。生成以“成功”告终,但笔记里是应急占位文本,不是 AI 分析。检查笔记内容而非退出码,第一天就抓住了它。
- 冻结的笔记库备份。git 插件还在本地不停提交,而向 GitHub 的推送早已悄悄失败——备份会就此停摆,等到最糟糕的时刻才被发现。
它刻意不修什么
这套系统里最重要的决定,不是自动化什么,而是不自动化什么。一部分检查被标记为“只报警”:所有同身份认证、别人的系统,以及我自己智能体的会话有关的东西。过期的 OAuth 不能用脚本“修好”——必须由人在浏览器里重新签发。智能体被明确禁止碰令牌、重启我正在使用的服务,或者为了让检查变绿而扭曲检查本身。
还有一条物理边界:修理工本身就是一个 Claude CLI,网络或认证一挂,它跟其他一切一起挂。搞垮系统的东西,同时也搞垮了修系统的那双手。这时诚实的做法不是逞英雄地反复尝试,而是清晰地说一句“需要你”,并附上确切的操作说明。
撑起一切的小细节
花时间最多的不是检查本身,而是看门狗自身的可靠性。第一:就地重新编译 Go 二进制会改变它的签名,而 launchd 缓存了签名要求——于是系统会直接杀掉下一次计划运行。现在每次构建都必然以重载任务收尾:
# Перезбирання бінарника на місці змінює його cdhash, а launchd кешує
# вимоги до підпису (LWCR) на момент bootstrap. Після збирання кеш
# застаріває — і наступний плановий запуск ядро вбиває (exit 9) або
# launchd відмовляється його запускати (exit 78 / EX_CONFIG).
# Тому збирання ЗАВЖДИ закінчується перезавантаженням задачі:
go build -o system-droid .
codesign --verify --strict system-droid
launchctl bootout "gui/$(id -u)/com.oleksii.system-droid" 2>/dev/null || true
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/com.oleksii.system-droid.plist
./check-self.sh # свіжий запуск має бути зеленим第二:对 KeepAlive 服务来说,上次运行的退出码是个骗人的指标。每次重启之后退出码都是 SIGTERM,一个活得好好的服务看起来像坏了。正确的问题不是“它怎么退出的”,而是“它现在跑着吗”:
# У KeepAlive-демона LastExitStatus після КОЖНОГО перезапуску
# ненульовий (15/SIGTERM) — тож перевірка "останній запуск успішний"
# хибно валить цілком живий сервіс. Правильне питання інше:
# чи працює він ПРЯМО ЗАРАЗ?
info=$(launchctl list "$1" 2>/dev/null) || { echo "not loaded"; exit 1; }
pid=$(printf '%s\n' "$info" | awk -F'= ' '/"PID"/{gsub(/[ ;]/,"",$2);print $2}')
[ -n "$pid" ] && [ "$pid" != "0" ] && { echo "running (pid $pid)"; exit 0; }
echo "loaded but not running"; exit 1第三:要检查系统真正依赖的那个对象。我最初的令牌检查算的是“签发日期加一年”——它一直亮绿灯,而真正的令牌已经整整一天都在返回 401:
# Календарна перевірка сяяла зеленим — річному токену ще жити й жити:
claude oauth token — OK: valid, 365d left
# А справжній робочий токен тим часом помер, і логи проксі це знали:
token refresh failed: {"error":"invalid_grant","error_description":"Refresh token expired"}
POST /v1/chat/completions → 401 "OAuth access token has expired. Re-authenticate."第四:凌晨三点一次只持续一个周期的断网,不值得把人叫醒。“需要干预”的警报现在只有当问题连续两轮都存在时才会发出。偶发故障自行消散;真故障照样送达——只是晚两个小时:
// Тривога "потрібне втручання" — лише якщо перевірка провалилася
// два запуски поспіль. Нічний обрив мережі, демон посеред перезапуску,
// пробудження зі сну — все це минає само до наступного циклу,
// і будити людину через таке не можна. Успішне лікування,
// навпаки, повідомляється одразу — це добра новина, а не шум.
counts := loadHealCounts() // ~/.system-droid-heal-state.json
for _, r := range stillFailing {
counts[r.Name]++
}
var persistent []Result
for _, r := range stillFailing {
if counts[r.Name] >= 2 { // поріг ескалації
persistent = append(persistent, r)
}
}
// сповіщення — лише якщо len(persistent) > 0给另一个智能体的眼睛
除了 Telegram,droid 还作为 MCP 服务器运行。这意味着我的本地智能体可以直接问它系统状态:我不用去读报告,只说一句“检查下是不是都正常”——智能体调用 get_health,把每项检查实时跑一遍,然后回答。监控不再是一个需要打开来看的页面,而成了一个可以随时询问的服务。
# Дроїд працює і як MCP-сервер — локальний агент питає його сам:
Resonance › виклич get_health у system-droid і скажи, чи все зелене
get_health →
overall: 🟢 OK — 10/10 green (2026-08-17 09:00)
🟢 openclaw gateway (:18789) — ядро агента Resonance (launchd + порт)
🟢 cli-proxy Claude auth — робочий Claude-токен (валідність, авто-оновлення)
...收获
技术上这里没有任何复杂的东西:Go、launchd、几个 shell 脚本、一个 Telegram 机器人。但正是这套简单的组合改变了整个自动化系统给人的感觉:我不再在脑子里维护一份“记得要检查”的清单。那现在是 droid 的工作。
七周来它大部分时间都很安静。它发出的每一条消息,要么是“已经修好”的好消息,要么是精确指出哪里需要我花两分钟。这就是省心的定义:一个守护其他系统的系统——并且尊重你的清静。
发现错误了吗?
本文里有事实错误、别扭的翻译,或者哪里读起来不对?告诉我——用你自己的语言。