首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >#WorkBuddy# 让本地 Agent 网关 7×24 不倒:一个 200 行的体检+自愈看门狗(附完整脚本)

#WorkBuddy# 让本地 Agent 网关 7×24 不倒:一个 200 行的体检+自愈看门狗(附完整脚本)

原创
作者头像
用户12406184
发布于 2026-11-01 00:06:28
发布于 2026-11-01 00:06:28
1500
举报

让本地 Agent 网关 7×24 不倒:一个 200 行的体检+自愈看门狗(附完整脚本)

一、你会遇到的场景

本地跑着几个常驻服务:一个本地模型服务(Ollama)、一个本地 Agent 网关、一两个自建的小接口。白天用得好好的,第二天早上打开电脑,端口连不上了。或者更隐蔽——端口还通,但内存已经涨到几个 G,响应从几十毫秒变成十几秒,最后被系统杀掉。

我自己这台机器上,那个本地网关从 9 月 30 号起就没重启过,跑了大半个月。它不会主动告诉你"我快不行了",只会等你用到它的时候,给你一个转圈圈。

二、结论先行

别指望一个 nohup ... & 就等于"常驻"。 常驻 = 启动 + 探活 + 留痕 + 自愈,四件事缺一件都是裸奔。

我把这四件事塞进一个 200 行出头的 Python 脚本(零第三方依赖,只用标准库),每 30 分钟跑一次体检,连续 3 次不健康才动手重启,重启后有 10 分钟冷却期。适用于 macOS 上任何"用端口对外提供服务"的本地进程;不适用于必须人工确认才能重启的服务(比如正在跑长任务、或有状态的写入进程)。

三、完整操作步骤(macOS / Python 3.11+)

步骤 1:先想清楚「什么叫活着」

多数人的判断标准只有一条——端口通不通。不够。端口通不代表能响应,能响应不代表不慢,不慢不代表内存没泄漏。我用了 4 个判据:

判据

怎么测

抓的是什么问题

端口存活

socket.connect_ex

进程没了

HTTP 响应码 + 耗时

curl -o /dev/null -w '%{http_code}'

假死、超时

常驻内存 RSS

ps -o rss= -p <pid>

内存泄漏 / 膨胀

日志尾部异常数

读最后 200 行,匹配 ERROR/Traceback

反复报错但还没死

第三条最值钱。WorkBuddy 5.8.3(2026-10-19)专门修了"长期运行后内存膨胀导致卡死"的问题【实证,来源:WorkBuddy 官方更新日志】,说明这类问题不是个例。

步骤 2:写配置文件,别把服务名硬编码进脚本

代码语言:json
复制
{
  "fail_window": 3,
  "cooldown_sec": 600,
  "services": [
    {
      "name": "local-model",
      "host": "127.0.0.1", "port": 11434,
      "http_path": "/api/tags",
      "rss_limit_mb": 8000,
      "http_ms_limit": 3000,
      "start_cmd": "nohup ollama serve > /tmp/local-model.log 2>&1 &",
      "stop_wait": 3
    },
    {
      "name": "local-gateway",
      "host": "127.0.0.1", "port": 18789,
      "http_path": "/",
      "rss_limit_mb": 2000,
      "http_ms_limit": 5000,
      "start_cmd": "cd ~/myagent && nohup npm run start > /tmp/gateway.log 2>&1 &",
      "log": "/tmp/gateway.log"
    }
  ]
}

上面端口、路径、启动命令均为示例,请替换成你自己机器上的实际值。

步骤 3:核心脚本(可直接复制)

完整脚本我放在文末附件 agent_watchdog.py,这里只贴三个关键函数:

代码语言:python
复制
def spawn(cmd):
    """启动常驻进程:必须脱离父子进程管道,否则调用方会一直等到超时。"""
    subprocess.Popen(cmd, shell=True, start_new_session=True,
                     stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
                     stdin=subprocess.DEVNULL, close_fds=True)
    return 0

def port_open(host, port, timeout=2.0):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(timeout)
    try:
        return s.connect_ex((host, port)) == 0
    finally:
        s.close()

def rss_mb_of_pid(pid):
    rc, out, _ = run("ps -o rss= -p %s" % pid)   # macOS: rss 单位是 KB
    return round(int(out.split()[0]) / 1024, 1) if rc == 0 and out else None

步骤 4:抖动防护——别一见失败就重启

这是整个脚本最容易被忽略、也最容易出事的一段:

代码语言:python
复制
rec["fail_streak"] = 0 if ok else rec.get("fail_streak", 0) + 1
if args.repair and not r["ok"] and rec["fail_streak"] >= window and svc.get("start_cmd"):
    cd, left = in_cooldown(args.state, r["name"], cooldown)
    if cd:
        r["action"] = "skip: cooldown %ss left" % left
    else:
        ...

一次体检失败就重启,等于给自己埋雷:服务本来在正常冷启动,你把它杀了又拉起来,循环往复。必须"连续 N 次失败 + 冷却期"两道闸。

步骤 5:默认只读,自愈要显式开关

脚本不加 --repair 就只体检、只打印、只写留痕,绝不杀进程也绝不拉进程。这条是敢把它挂进定时任务的前提——你永远可以先跑一周只观察,看看告警是不是在误报。

代码语言:bash
复制
# 只体检(安全,可放心挂定时任务)
python3 agent_watchdog.py --config watchdog.json --check

# 体检 + 自愈(确认告警无误报后再开)
python3 agent_watchdog.py --config watchdog.json --repair

退出码 = 0 表示全部健康,非 0 表示有服务异常,可以直接被上层调度拿去判断。

步骤 6:挂到 WorkBuddy 自动化任务上

在 WorkBuddy 里建一个自动化任务,指令写死成一句话(实测这种"固定命令 + 固定配置"的任务最省积分,因为它不需要每次重新理解上下文):

代码语言:plaintext
复制
每隔 30 分钟,执行:cd ~/myagent && python3 agent_watchdog.py --config watchdog.json --repair
如果输出里出现 DOWN 或 rss 超过 limit,把这一行原样发给我,不要改写。

步骤 7:macOS 上别用 crontab,用 launchd 兜底

crontab 在 macOS 上常因为「完全磁盘访问权限」静默失败,排查起来很烦。用 LaunchAgent 更稳:

代码语言:xml
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
 "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key><string>com.example.agentwatchdog</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/bin/python3</string>
    <string>/Users/你自己的路径/agent_watchdog.py</string>
    <string>--config</string><string>/Users/你自己的路径/watchdog.json</string>
    <string>--repair</string>
  </array>
  <key>StartInterval</key><integer>1800</integer>
  <key>RunAtLoad</key><true/>
  <key>StandardOutPath</key><string>/tmp/agentwatchdog.out</string>
  <key>StandardErrorPath</key><string>/tmp/agentwatchdog.err</string>
</dict>
</plist>
代码语言:bash
复制
cp com.example.agentwatchdog.plist ~/Library/LaunchAgents/
launchctl load  ~/Library/LaunchAgents/com.example.agentwatchdog.plist
launchctl list | grep agentwatchdog   # 看第一列是不是 0(0=正常退出)

四、实测数据

以下全部是本机真实执行输出,环境 macOS(Apple Silicon)+ Python 3.13.12。

① 三服务只读体检(本机真实服务):

代码语言:plaintext
复制
SERVICE          PORT   PID     RSS(MB)   HTTP    MS     VERDICT
------------------------------------------------------------------------------
local-model      up     1954    22.0      ok      18     OK
local-gateway    up     1782    128.5     ok      28     OK
not-running      DOWN   -       -         -       -      port 19091 closed

其中 local-gateway 这个进程自 9-30 起连续运行 20 天,RSS 128.5 MB,HTTP 28 ms。

② 内存阈值告警(把 limit 故意设成 10 MB):

代码语言:plaintext
复制
ollama           up     1954    22.3      ok      14     rss 22.3MB > limit 10MB
退出码=1

端口和 HTTP 都是好的,只有内存判据抓到了——这正是单靠端口探活漏掉的那一类。

③ 崩溃自愈闭环(临时假服务,不碰真实进程):

阶段

结果

健康

up / pid 87212 / 31.9MB / http ok 12ms

kill -9 后

DOWN / port 18999 closed

第 1 轮 --repair

fail_streak=1,不动手

第 2 轮 --repair

fail_streak=2,执行 restart spawned in 4ms

复检

up / pid 87236 / 31.8MB / http ok 11ms

④ 脚本耗时:3 个服务体检,连续 3 次 real 0.14 / 0.14 / 0.14 秒。开销可以忽略,挂 5 分钟一次也不心疼。

⑤ 积分消耗:【待本人实测补充】——挂到 WorkBuddy 自动化任务后,单次运行的积分消耗我还没记账,跑满一周后补。

五、踩坑与边界

  1. nohup xxx & 会挂死 subprocess.run:我第一版用 run(shell=True, capture_output=True) 跑启动命令,直接 TIMEOUT after 15s——后台进程继承了 stdout/stderr 管道,run() 要等管道关闭才返回。改成 Popen + start_new_session=True + 三个 DEVNULL 后,15 秒超时 → 4 毫秒返回。这是本次最大的一个坑。
  2. 连续失败窗口别设 1:设 1 等于没有防护。服务冷启动期间必然"端口不通",一查就杀、一杀就重启,直接重启风暴。我设窗口 3、冷却 600 秒。
  3. RSS 阈值要按"常态峰值 × 1.5"来设,别拍脑袋。先只跑 --check 一周,拿到真实分布再定阈值。
  4. 有状态服务别自动重启:正在写文件、正在跑长任务、正在持有锁的进程,贸然 kill 会留下半截产物。这类服务只配 --check 告警,不配 start_cmd。
  5. 日志读不到要区分"没日志"和"没错误":脚本里日志不存在时返回 None 并在表格里显示 n/a,而不是当成 0 错误——否则你会以为服务很健康。
  6. macOS 的 ps -o rss= 单位是 KB,直接当 MB 用会差 1024 倍。另外 xargs -r 在 BSD xargs 上不支持,清理脚本里我改成了 PID=$(...); [ -n "$PID" ] && kill -9 $PID。

不适用的场景:需要人工输入密码/扫码才能启动的服务;启动依赖 GUI 会话的进程(LaunchAgent 在 SSH 登录时跑不起来);以及任何你没法承受"误杀"代价的进程。

六、小结

常驻不是 nohup 一下就完事。补上「探活 + 阈值 + 抖动防护 + 留痕」这四块,一个 200 行的脚本就能把本地 Agent 网关的可用性从"看运气"拉到"可控"。先只观察一周,确认无误报再开自愈——顺序反了会出事。


截图位(发布前必须补 3–6 张真实图)

  • ✅已标注
  • ✅已标注
  • ✅已标注
  • ✅已标注
  • ✅已标注

缺的图:5 张全部缺。其中截图位 2 最关键的——用 python3 agent_watchdog.py --demo 在本机重跑一遍,把「健康 → kill → 第1轮不动手 → 第2轮自愈 → 复检」这五段终端输出截下来,一篇文章的图片说服力就够了。

发布信息

  • 标题:#WorkBuddy# 让本地 Agent 网关 7×24 不倒:一个 200 行的体检+自愈看门狗(附完整脚本)
  • 标签:WorkBuddy、AI 办公、效率工具
  • 来源:原创
  • 专栏:AI 与大数据
  • 板块:实用文章
  • 附件:草稿/assets/agent_watchdog.py(完整可运行脚本)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 让本地 Agent 网关 7×24 不倒:一个 200 行的体检+自愈看门狗(附完整脚本)
    • 一、你会遇到的场景
    • 二、结论先行
    • 三、完整操作步骤(macOS / Python 3.11+)
      • 步骤 1:先想清楚「什么叫活着」
      • 步骤 2:写配置文件,别把服务名硬编码进脚本
      • 步骤 3:核心脚本(可直接复制)
      • 步骤 4:抖动防护——别一见失败就重启
      • 步骤 5:默认只读,自愈要显式开关
      • 步骤 6:挂到 WorkBuddy 自动化任务上
      • 步骤 7:macOS 上别用 crontab,用 launchd 兜底
    • 四、实测数据
    • 五、踩坑与边界
    • 六、小结
    • 截图位(发布前必须补 3–6 张真实图)
    • 发布信息
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档