<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[把 Agent 挂成开机自启+自愈的常驻服务：launchd+watchdog+supervisor（丢 repo 给 AI 就行，GitHub 开源）]]></title><description><![CDATA[<h2>前言</h2>
<p dir="auto">我把 Hermes Agent 的网关挂后台跑了一两个月（default + trade 两个 profile 7×24 常驻），中间踩了不少坑：网关卡死没自动恢复、launchd 没托管导致重启后进程消失、<strong>日志静默被误判成故障导致无限重启循环</strong>、Telegram API 抖动时明明没事却被反复 kickstart 把正在跑的任务打断。</p>
<p dir="auto">现在这套「launchd 托管 + watchdog 探活 + supervisor 兜底」的三层方案跑了一两个月，<strong>我把整套源码开源在 GitHub</strong>。你不用手动抄那几个脚本——clone 下来交给你的 AI，跟它说一句「按我机器环境装上」，它就帮你改路径、生成配置、载入、验证，全自动。</p>
<h2>一、三层架构（先说清楚是干嘛的）</h2>
<p dir="auto">你的 Agent 若是跑一下退出，launchd 一条 <code>KeepAlive</code> 就够。但挂后台一直等消息的网关，真正的问题不是「进程挂了」，而是：进程活着但断线卡死、机器离线时所有探针失败、<strong>日志安静了你却以为它挂了</strong>。所以关键不是「重启」，是**「判断什么是真故障才动手」**。</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>层</th>
<th>谁负责</th>
<th>频率</th>
<th>干嘛</th>
</tr>
</thead>
<tbody>
<tr>
<td>托管层</td>
<td>launchd (LaunchAgents)</td>
<td>开机/常驻</td>
<td>保证进程活着，崩溃自动拉起</td>
</tr>
<tr>
<td>探活层</td>
<td><code>autoheal-watchdog.sh</code></td>
<td>每分钟</td>
<td>每分钟查 3 个信号，确认是真健康</td>
</tr>
<tr>
<td>兜底层</td>
<td><code>autoheal-supervisor.sh</code></td>
<td>60s 循环</td>
<td>兜底加载/重启，处理僵尸连接</td>
</tr>
</tbody>
</table>
<h2>二、最省事：把 repo 丢给你的 AI（推荐）</h2>
<p dir="auto">整套在 GitHub：<strong><a href="https://github.com/Wang200935/hermes-autoheal-launchd" rel="nofollow ugc">https://github.com/Wang200935/hermes-autoheal-launchd</a></strong></p>
<pre><code class="language-bash">git clone https://github.com/Wang200935/hermes-autoheal-launchd.git
cd hermes-autoheal-launchd
</code></pre>
<p dir="auto">把<strong>整个目录丢给你的 agent</strong>，跟它说这一段（把尖括号换成你的信息）：</p>
<blockquote>
<p dir="auto">用这个 hermes-autoheal-launchd 套装，按我本机环境把 <strong>&lt;我要守护的进程，比如 Hermes 网关&gt;</strong> 挂成开机自启 + 自愈的常驻服务。帮我改 <code>deploy-autoheal.sh</code> 里的 <code>SERVICES</code>（我的路径是 <strong>&lt;你的绝对路径&gt;</strong>、我的进程命令是 <strong>&lt;你的命令&gt;</strong>、我有/没有状态文件 <strong>gateway_state.json</strong>），然后跑起来，验证 watchdog 每分钟打出 <code>ok pid</code>。</p>
</blockquote>
<p dir="auto">你的 agent 会自动帮你：<strong>改 <code>SERVICES</code> → 生成 plist → 拷贝脚本 → bootstrap 载入 → 验证</strong>。你只需要告诉它守护谁、路径在哪。</p>
<h2>三、这套东西是啥（三个文件）</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>文件</th>
<th>角色</th>
<th>要不要改</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>deploy-autoheal.sh</code></td>
<td>生成器（生成 plist + conf + 拷贝脚本 + 载入）</td>
<td><strong>只改它里面的 <code>SERVICES</code> 数组</strong></td>
</tr>
<tr>
<td><code>autoheal-watchdog.sh</code></td>
<td>探活层，每分钟</td>
<td>不用改</td>
</tr>
<tr>
<td><code>autoheal-supervisor.sh</code></td>
<td>兜底层，60s 循环，防误重启</td>
<td>不用改</td>
</tr>
</tbody>
</table>
<p dir="auto">各自干啥：</p>
<ul>
<li><code>deploy-autoheal.sh</code> — <strong>你要碰的就这一个</strong>。里头改 <code>SERVICES</code> 数组，跑 <code>./deploy-autoheal.sh</code>，它帮你干剩下的：建 plist + conf、拷贝 watchdog/supervisor 到 <code>~/.hermes/scripts/</code>、<code>launchctl bootstrap</code> 载入。<strong>不联网、不跑 daemon</strong>——纯生成器，run 完就退。</li>
<li><code>autoheal-watchdog.sh</code> — 真干活的探活层。被 launchd 1 分钟拉一次，读 <code>autoheal-services.conf</code>，逐项判「进程活 + 命令匹配 + 状态文件正常」，不健康就 <code>launchctl kickstart -p</code>。<strong>啥都没干就别瞎重启</strong>（这条是踩坑 #1 的根因）。</li>
<li><code>autoheal-supervisor.sh</code> — launchd <code>KeepAlive</code> 常驻的兜底层。每 60s 检查：plist 都在 loaded、进程都在 pid、telegram 连接没卡住、顺手跑一遍 watchdog。它管的是「上一层都坏了还有没有救」，用 stale-lock 防自锁。</li>
</ul>
<p dir="auto"><code>SERVICES</code> 格式：<code>"label|workdir|state_file|command"</code>，一行一个进程，<code>state_file</code> 没有就填空串 <code>''</code>（会自动退化成只看进程+命令）。</p>
<p dir="auto"><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/26a0.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--warning" style="height:23px;width:auto;vertical-align:middle" title="⚠" alt="⚠" />️ <strong><code>command</code> 这一栏最容易踩：不要在 <code>SERVICES</code> 行里用 <code>|</code>（会被 zsh 当管道）。</strong> 空格分隔是可的（脚本会把它当数组），但<strong>别写含 <code>|</code> 的复合命令</strong>——真有需要就在脚本里另起一行调用，别塞 SERVICES 行。</p>
<h2>四、真跑的环境</h2>
<p dir="auto">PID 55346 是 default 网关、42888 是 trade 网关，每只每分钟被 watchdog 标记一次 <code>ok</code>：</p>
<p dir="auto"><img src="https://upload.lcz.me/uploads/89e7b05c-439f-42c7-bfbe-b8d991d8f000.png" alt="" class=" img-fluid img-markdown" /></p>
<pre><code>$ launchctl list | grep hermes
42888    1    ai.hermes.gateway-trade
23527    0    application.com.nousresearch.hermes...
55346   -15  ai.hermes.gateway
50602   75   com.hermes.dashboard
959     0    com.hermes.dashboard-proxy
-       0    autoheal.watchdog
</code></pre>
<p dir="auto">watchdog 日志（真跑的，每分钟一条 <code>ok pid=</code>）：</p>
<pre><code>2026-08-27 18:32:59 ai.hermes.gateway: ok pid=55346
2026-08-27 18:34:01 ai.hermes.gateway: ok pid=55346
2026-08-27 18:35:05 ai.hermes.gateway: ok pid=55346
</code></pre>
<h2>五、坑（让 AI 读 repo README 也能避开）</h2>
<ol>
<li><strong>「日志静默」不能当健康信号</strong> — 健康网关等消息时几小时不写日志，拿它当判据会让你每 10 分钟 kickstart 一次，不停弹「shutting down」把跑一半的任务打断。只认状态文件 + API 心跳。</li>
<li><strong>Telegram API 抖动别盲目重启</strong> — <code>getMe</code> 探针连不上 ≠ 网关挂了，可能是网络/API 抖动；探针失败就跳过重启。</li>
<li><strong>network fallback</strong> — 机器离线时 HTTP 探针会失败但进程没事；先 curl Telegram，失败再 ping 1.1.1.1/8.8.8.8，确认只是离线就保留。</li>
<li><strong>launchd 用 <code>bootstrap</code> 不是 <code>load</code></strong> — 先 <code>bootout</code> 再 <code>bootstrap</code>，否则报 <code>already loaded</code>。</li>
<li><strong>stale lock</strong> — 锁目录崩溃后会残留，5 分钟以上 + 确认没在跑才删锁重拿。</li>
<li><strong><code>SERVICES</code> 里 <code>state_file</code> 那栏别留空格</strong> — 留了空格会被当成文件路径判断，把健康的进程误判成 unhealthy。</li>
</ol>
<h2>最后</h2>
<p dir="auto">三层的好处是每层只干一件事、判断标准单一：launchd 只管进程在不在，watchdog 管「是不是真健康」，supervisor 管「兜底 + 防僵尸连接」。<strong>这套不用 GPU，纯 CPU，就三个文件</strong>，仓库已开源。</p>
<h3>装完立刻验（不用等一两周）</h3>
<pre><code class="language-bash"># watchdog 干没干活（看到 'ok pid=' 才算成）
sleep 65 &amp;&amp; tail -5 ~/.hermes/logs/autoheal-watchdog.log
# supervisor 有没在跑、加载了哪些
tail -10 ~/.hermes/logs/autoheal-supervisor.log
launchctl list | grep -E 'autoheal|gateway'
</code></pre>
<p dir="auto">第一条日志应该在 1 分钟内出现 <code>xxx: ok pid=&lt;数字&gt;</code>。<strong>没出现就回去查 plist 是否真 bootstrap 进 gui/&lt;uid&gt; 下</strong>——这是另一个常见坑：plist 拷到 <code>~/Library/LaunchAgents/</code> 但 <code>bootstrap</code> 的 domain 写错（比如写成 <code>system</code> 而不是 <code>gui/501</code>），它会「装上但从不跑」。</p>
<p dir="auto">想省事就 clone 下来丢给你的 agent，让它在你的机器上把活干完。</p>
]]></description><link>https://lcz.me/topic/1359</link><generator>RSS for Node</generator><lastBuildDate>Wed, 09 Sep 2026 22:53:39 GMT</lastBuildDate><atom:link href="https://lcz.me/topic/1359.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 27 Aug 2026 11:28:09 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to 把 Agent 挂成开机自启+自愈的常驻服务：launchd+watchdog+supervisor（丢 repo 给 AI 就行，GitHub 开源） on Fri, 28 Aug 2026 09:10:56 GMT]]></title><description><![CDATA[<p dir="auto">我弟完全生活在另一个纬度，帖子都是不明觉厉。</p>
]]></description><link>https://lcz.me/post/14591</link><guid isPermaLink="true">https://lcz.me/post/14591</guid><dc:creator><![CDATA[terry]]></dc:creator><pubDate>Fri, 28 Aug 2026 09:10:56 GMT</pubDate></item></channel></rss>