Carry の Blog Carry の Blog
首页
关于
  • Nginx
  • Prometheus
  • Iptables
  • Systemd
  • Firewalld
  • Docker
  • Sshd
  • DBA工作笔记
  • MySQL
  • Redis
  • TiDB
  • Elasticsearch
  • OpenClaw
  • Hermes Agent
  • Claude Code
  • MySQL8-SOP手册
  • MySQL实战45讲学习笔记
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Carry の Blog

好记性不如烂键盘
首页
关于
  • Nginx
  • Prometheus
  • Iptables
  • Systemd
  • Firewalld
  • Docker
  • Sshd
  • DBA工作笔记
  • MySQL
  • Redis
  • TiDB
  • Elasticsearch
  • OpenClaw
  • Hermes Agent
  • Claude Code
  • MySQL8-SOP手册
  • MySQL实战45讲学习笔记
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • OpenClaw

  • Hermes-Agent

    • Hermes Agent 概述
    • Hermes Agent 实战 01|架构总览:用一个 Agent 管一整个机房
    • Hermes Agent 实战 02|多 Profile 与超管模型:一个 Agent 安全地管十几台机器
    • Hermes Agent 实战 03|Gateway 运维:systemd、裸进程,和一个 Telegram token 撞车
    • Hermes Agent 实战 04|模型路由实战:config 全解、thinking 注入与 401/503 源码级根因
    • Hermes Agent 实战 05|技能工程:写、去重、pin,与每周自我审计
    • Hermes Agent 实战 06|让 Agent 自己上班:cron 驱动的无人值守巡检
    • Hermes Agent 实战 07|数据库实战:可直接抄走的 SQL Server 巡检脚本
    • Hermes Agent 实战 08|量化交易助手:持仓盈亏、网格减仓,与「没开单」的真相
    • Hermes Agent 实战 09|接入 OpenWebUI:把每个 Profile 暴露成一个「模型」
    • Hermes Agent 实战 10|升级不翻车,与给上游提 PR:一个被冲掉三次的修复
    • Hermes Agent 实战 11|踩坑合集:当「手动 rm」从来不是真正的修复
    • Hermes Agent 实战 12|工具链外延:用 AI 运维 AI,与这个系列的诞生
    • Hermes Agent 实战 13|旗舰篇:让 Agent 从零部署并灾难恢复一个 7 节点生产集群
    • Hermes Agent 实战 14|跨 Profile 消息路由与自托管服务巡检:两个被忽略的边界
    • Hermes Agent 实战 15|告别临时 RAG:用 Karpathy 的 LLM Wiki 给 Agent 装上可生长的长期记忆
    • Hermes Agent 实战 16|给 AI Agent 平台做可观测:多 profile 服务的指标、日志与告警设计
      • 1. Aborting 的 Telegram 与沉默的 systemd
      • 2. 为什么多 Profile 架构让可观测更难
        • 2.1 服务形态碎片
        • 2.2 故障模式的多样性
        • 2.3 心跳文件的局限性
      • 3. 指标体系:三层信号模型
        • 3.1 进程层(systemd 提供)
        • 3.2 引擎层(Gateway 自身提供)
        • 3.3 日志层(行为信号)
      • 4. 日志与追踪现状
        • 4.1 分散的 271M 日志
        • 4.2 shutdown-diag 的设计决策
      • 5. 告警设计:从噪声中提取信号
        • 5.1 无效信号(已验证)
        • 5.2 有效信号
        • 5.3 为什么没上 Prometheus
      • 6. 方案 A vs 方案 B:日志汇聚的取舍
        • 方案 A:原地标准化(journald 统一)
        • 方案 B:旁路汇聚(fluent-bit → Loki/ES)
        • 我的取舍:分阶段走
      • 7. 坑与对策
        • 坑 1:平台级故障的无感知
        • 坑 2:日志分散的查询成本
        • 坑 3:数值的时间绑定
      • 8. 可复用要点
      • 9. 下一步缺口
  • Claude-Code

  • AI-Agent
  • Hermes-Agent
Carry の Blog
2026-08-24
目录

Hermes Agent 实战 16|给 AI Agent 平台做可观测:多 profile 服务的指标、日志与告警设计原创

# 给 AI Agent 平台做可观测:多 profile 服务的指标、日志与告警设计

系列第 16 篇。一次真实故障做引子:2026-07-27 09:11:36,tradex gateway 的 Telegram 平台 宣布 Aborting,进程却一直活着,systemd 全程绿灯,没有任何告警。这篇从这 41 秒讲起, 把多 profile Agent 平台的可观测拆成三层信号(进程 / 引擎 / 日志), 说清楚哪些信号是噪声、哪些才是真信号,最后给出日志汇聚方案 A/B 的对比与我的取舍。

# 1. Aborting 的 Telegram 与沉默的 systemd

gateway.log, line 5412:

2026-07-27 09:11:57,210 INFO ... Telegram polling restarted after network error
1

gateway.log, line 5407-5411:

2026-07-27 09:10:55,612 WARNING ... Telegram network error: Bad Gateway
...
2026-07-27 09:11:36,164 WARNING ... Telegram polling reconnect failed: Timed out
1
2
3

errors.log, line 1:

2026-07-27 09:11:36,092 ERROR telegram.ext: Network Retry Loop ... Aborting.
1

2026-07-27 09:11:36,tradex gateway 的 Telegram 平台宣布 Aborting。从 09:10:55 收到第一个 Bad Gateway,到 09:11:36 彻底放弃重连,窗口期 41 秒。

gateway 进程呢?继续跑着。09:11:57 它甚至还打印了「polling restarted」——只是 Telegram 平台礼貌地通知了 Aborting,然后自己恢复。

systemd 在整个过程中毫无反应。systemctl status hermes-gateway-tradex 一直是绿的,因为进程从未退出。没有告警,没有短信,没有 Slack。直到用户发现消息发出去没反应,我们才知道出事了。

这就是 AI Agent 平台的可观测陷阱:进程活着,不代表业务活着。

# 2. 为什么多 Profile 架构让可观测更难

# 2.1 服务形态碎片

10 个 gateway 服务分布在各自的 profiles:

维度 现状(来源:运行时取数)
进程类型 Python (8) + Node.js (2)
运行时长 2 days ~ 3 weeks(截至 2026-08-24)
日志分散 10 个目录 / 271M / 36,786 行 errors

没有一个全局视图能回答「整个平台健康吗」。

运行命令验证:

$ systemctl --user list-units --type=service --state=running | grep gateway | wc -l
7

$ du -sh ~/.hermes/profiles/*/logs/ 2>/dev/null | awk '{sum+=$1} END {print sum}'
271M

$ wc -l ~/.hermes/profiles/*/logs/errors.log 2>/dev/null | tail -1
36786 total
1
2
3
4
5
6
7
8

# 2.2 故障模式的多样性

从 errors.log 提取的 7 类真实故障:

类型 症状 进程状态 systemd 感知
Telegram 网络中断(07-27) 平台 Aborting 后自恢复 存活 ❌ 无
API 兼容崩溃 工具调用失败 存活 ❌ 无
Token 冲突 无法连接 崩溃重启 ⚠️ 重启计数++
端口占用 API server 起不来 退出 ✅ 有
SIGTERM 正常停止 停止 ✅ 有
WebUI 慢查询 请求 5s+ 存活 ❌ 无
Memory 溢出 记忆写入失败 存活 ❌ 无

4 类故障(占 57%)发生时 systemd 完全无感知。

# 2.3 心跳文件的局限性

ticker 心跳文件 (cron/ticker_heartbeat + ticker_last_success) 由 cron/scheduler 每 60 秒刷新一次(来源:cron/jobs.py:99)。

它能检测:gateway 进程是否存活、cron 循环是否卡住。

它不能检测:Telegram 平台 Aborting、平台恢复、消息队列积压。07-27 的故障期间,只要 gateway 进程在,心跳文件就会继续刷新——完全无法感知 Telegram 平台的 41 秒中断。

验证命令:

$ cat ~/.hermes/profiles/tradex/cron/ticker_last_success | xargs -I{} date -d @{} "+%Y-%m-%d %H:%M:%S"
2026-08-24 11:33:35
1
2

# 3. 指标体系:三层信号模型

# 3.1 进程层(systemd 提供)

运行命令获取的当前状态:

$ systemctl --user status hermes-gateway-tradex --no-pager | grep -E "(Active:|Main PID:|Memory:)"
     Active: active (running) since Fri 2026-08-21 16:13:08 CST; 2 days ago
   Main PID: 1346054 (hermes)
     Memory: 386.4M
1
2
3
4
  • NRestarts:重启次数(全部为 0)
  • Memory:RSS(当前观测值)
  • Uptime:运行时长
  • 局限:只能检测崩溃,无法检测业务僵死或平台中断

# 3.2 引擎层(Gateway 自身提供)

信号 当前状态 源码出处
event-loop 心跳 state/gateway.heartbeat 30s 刷新 shutdown_watchdog.py:16-17
shutdown-diag SIGTERM 时抓进程树+loadavg shutdown_forensics.py:8-12
/v1/models 探活 缺失 -
平台级健康 缺失 -

07-27 的故障暴露了关键缺口:Telegram 平台 Aborting 后 gateway 仍能写心跳,但没有任何信号表明「Telegram 不可达」。

# 3.3 日志层(行为信号)

gateway.log 中的模式本身就是指标:

  • Network Retry Loop → Telegram 健康度
  • polling restarted → 平台自恢复
  • Slow WebUI request → 会话索引性能

验证(errors.log 行数):

$ wc -l ~/.hermes/profiles/*/logs/errors.log 2>/dev/null
default: 3027
superdba: 6923
tradex: 6434
1
2
3
4

# 4. 日志与追踪现状

# 4.1 分散的 271M 日志

Profile errors.log gateway.log 总大小(截至 2026-08-24)
superdba 6,923 行 4.3M 84M
tradex 6,434 行 900K 24M
aws-us 3,027 行 2M 62M

没有跨 profile 的 trace_id,无法回答「一个用户的请求经过了哪些 profiles」。

# 4.2 shutdown-diag 的设计决策

shutdown_forensics.py 的设计原则(来源:文件 docstring):

"Anything that needs to wait (e.g. shelling out to ps aux) belongs in the async helper, never in the synchronous probe."

解决方案:

  • snapshot_shutdown_context():同步快速抓取 /proc 基础信息(<10ms)
  • spawn_async_diagnostic():fire-and-forget 子进程抓 ps aux + dmesg

07-25 14:55:56 的 shutdown-diag 抓取了当时的进程树和负载(gateway.log 中 SIGTERM 记录可见)。

# 5. 告警设计:从噪声中提取信号

# 5.1 无效信号(已验证)

  • NRestarts:无法检测「平台 Aborting 但进程存活」类型故障
  • 心跳文件:07-27 故障期间 tradex 的心跳持续刷新

验证:

$ systemctl --user show hermes-gateway-tradex -p NRestarts
NRestarts=0
1
2

# 5.2 有效信号

信号 来源 阈值
ticker_last_success 滞后 cron/jobs.py > 5min Warning, > 15min Critical
Telegram Network Retry Loop gateway.log 出现即记录
WebUI 慢查询 gateway.log P99 > 3s

# 5.3 为什么没上 Prometheus

Q9 决策点:真实决策回溯

现状验证(源码证据):

  • gateway/platforms/api_server.py:152:DEFAULT_PORT=8642
  • shutdown_watchdog.py:16:已存在 event-loop 心跳文件
  • cron/jobs.py:91-94:已存在 ticker 心跳文件

决策逻辑:

  • 已有:进程级监控(systemd)、循环级监控(ticker 心跳)、信号级监控(shutdown-diag)
  • 缺失:平台级监控(Telegram/Discord 健康)、业务级监控(消息队列深度、SSE 连接数)
  • 现实:07-27 的 41 秒 Telegram 中断被日志记录,但未被任何人实时消费

真实选择:混合方案

  • 基础层:systemd + ticker 心跳(已部署,成本已支付)
  • Signal 层:shutdown-diag SIGTERM 现场捕获
  • 业务层:日志后置分析,未部署实时消费
  • 未选:Prometheus + /metrics 端点(新增运维成本,1 人维护 10 profiles)

代价:07-27 式故障依赖用户反馈发现,MTTD 不可控。

# 6. 方案 A vs 方案 B:日志汇聚的取舍

# 方案 A:原地标准化(journald 统一)

做法:

  • gateway 写入 journald 而非文本文件
  • journald 作为本地日志聚合点
  • 查询:journalctl --user -u hermes-gateway-*

优点:

  • 零新增依赖
  • 时序统一、结构化输出
  • 与 systemd 原生集成

缺点:

  • 仍需登录机器查询
  • journald 在容器/多机场景下仍是单点
  • 跨 profile 联合查询需要额外工具

# 方案 B:旁路汇聚(fluent-bit → Loki/ES)

做法:

  • 每个 profile 部署 fluent-bit/vector 本地 agent
  • 监听 logs/*.log 变化
  • 转发到中央 Loki 或 Elasticsearch

优点:

  • 跨 profile 联合查询
  • 实时检索(Loki 标签匹配)
  • 与 Grafana 原生集成

缺点:

  • 需维护 Loki/ES 集群
  • 新增网络出口(日志传输)
  • profile 扩容时需同步部署 agent

# 我的取舍:分阶段走

第一阶段(当前):原地标准化

  • 将 gateway 日志输出改为 journald
  • 统一日志格式为结构化 JSON
  • 保留 ticker 心跳作为 backup

理由:

  1. 成本优先:1 人维护 10 profiles,无法承担 Loki 集群运维
  2. 渐进可行:journald 零新增依赖,即刻可用
  3. 风险可控:日志不离开本机,无网络传输风险

第二阶段(未来):当 profile 数量 > 20 或团队规模 > 3 人时

  • 引入集中化日志汇聚
  • 优先评估 Loki(比 ES 轻量)

一句话结论:现阶段选方案 A(journald 原地标准化),因为运维人力不足;日志分散问题用统一 journald 格式缓解,而非引入新的基础设施负担。

# 7. 坑与对策

# 坑 1:平台级故障的无感知

07-27 09:11:36 Telegram Aborting,gateway.log 记录了,但:

  • systemd 状态:绿(进程在)
  • 心跳文件:正常刷新(loop 在)
  • 人工感知:0(直到用户反馈)

对策:平台适配器暴露健康端点(如 Telegram getUpdates 连通性)。

# 坑 2:日志分散的查询成本

07-27 故障的完整时间线需要 grep 2 个文件(gateway.log + errors.log),tradex 只是 10 个 profiles 之一。

对策:日志聚合(journald 统一)或至少是 profile 级别的日志目录统一。

# 坑 3:数值的时间绑定

把「今天的内存」当成「07-27 当时的内存」——时间线错乱。

对策:历史场景只用当时的日志时间戳,现状盘点用今天的观测值。

# 8. 可复用要点

  1. 进程心跳 ≠ 平台健康:07-27 Telegram Aborting 期间,gateway 进程心跳正常
  2. 日志是最后的黑匣子:shutdown-diag 设计捕获了 SIGTERM 时的完整现场
  3. 平台级监控是缺失的环节:systemd 和 ticker 都看不到 Telegram 平台的 Aborting
  4. 多 profile = 多份日志 = N 倍查询成本:10 profiles 的日志分散问题

# 9. 下一步缺口

  • [ ] Telegram 平台健康探活(getUpdates 连通性检查)
  • [ ] 日志 journald 统一输出(原地标准化)
  • [ ] 日志聚合查询(当 profile > 20 时评估 Loki)

🤖 Agent 可直接解析的元数据块(点击展开)
{
  "_meta": {
    "doc_version": "2026-08-24",
    "article_id": "hermes-agent-16-observability",
    "profile_context": "blog",
    "estimated_setup_time": "2h"
  },
  "evidence_sources": {
    "gateway_log": "~/.hermes/profiles/tradex/logs/gateway.log:5407-5412",
    "errors_log": "~/.hermes/profiles/tradex/logs/errors.log:1",
    "shutdown_forensics": "~/.hermes/hermes-agent/gateway/shutdown_forensics.py:8-12",
    "cron_jobs": "~/.hermes/hermes-agent/cron/jobs.py:91-94",
    "shutdown_watchdog": "~/.hermes/hermes-agent/gateway/shutdown_watchdog.py:16-17",
    "api_server": "~/.hermes/hermes-agent/gateway/platforms/api_server.py:152"
  },
  "key_metrics": {
    "profiles_count": 10,
    "logs_total_mb": 271,
    "errors_lines": 36786,
    "telegram_outage_window_seconds": 41
  },
  "q9_decision": {
    "choice": "方案 A(journald 原地标准化)",
    "reason": "运维人力不足(1人×10 profiles),无法承担 Loki 集群运维成本",
    "trade_off": "日志仍分散在各机,但格式统一、零新增依赖",
    "future_trigger": "profile > 20 或团队规模 > 3 人时评估方案 B"
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#AI Agent#Hermes#SRE#可观测性#监控
上次更新: 8/24/2026

← Hermes Agent 实战 15|告别临时 RAG:用 Karpathy 的 LLM Wiki 给 Agent 装上可生长的长期记忆 Claude Code 概述→

最近更新
01
关于这个博客
08-24
02
Browserless 使用笔记:把 Chrome 变成一个可以被并发调用的网络服务
08-07
03
ORDER BY 配合 LIMIT 触发的索引选择陷阱 原创
08-07
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式