Carry の Blog Carry の Blog
首页
  • Nginx
  • Prometheus
  • Iptables
  • Systemd
  • Firewalld
  • Docker
  • Sshd
  • DBA工作笔记
  • MySQL
  • Redis
  • TiDB
  • Elasticsearch
  • OpenClaw
  • Hermes Agent
  • Claude Code
  • MySQL8-SOP手册
  • 分类
  • 标签
  • 归档
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手册
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • OpenClaw

  • Hermes-Agent

    • Hermes Agent 概述
    • Hermes Agent 实战 01|架构总览:用一个 Agent 管一整个机房
      • 1. 先说结论:它现在长什么样
      • 2. Hermes Agent 是什么(只讲你需要知道的部分)
        • 2.1 一段源码把循环讲清楚
      • 3. 一个 runtime,五种「面孔」
        • 3.1 启动顺序铁律(这条我用三次惨痛教训换来)
      • 4. 数据都放在哪:~/.hermes 布局
        • 4.1 真正看一眼 state.db 长什么样
        • 4.2 关键路径必须走 get_hermes_home(),别硬编码
      • 5. 最关键的一个设计:Profile 隔离 + 超管穿透
        • 5.1 十分钟克隆一个新 Profile
      • 6. 这个系列接下来讲什么
      • 7. 适用读者与声明
      • 8. 第一次部署?跑一遍这份自检
    • 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 节点生产集群
  • Claude-Code

  • AI-Agent
  • Hermes-Agent
Carry の Blog
2026-06-21
目录

Hermes Agent 实战 01|架构总览:用一个 Agent 管一整个机房原创

# 架构总览:用一个 Agent 管一整个机房

这是《Hermes Agent 实战》系列的第一篇。整个系列不讲概念、不抄文档,全部来自我自己这套跑了几个月、积累了上万次真实会话的部署——它管着我手上十多台机器、一个数据库巡检团队、一个量化交易监控、一台 WAF、还有一整套自建 AI 基础设施。

# 1. 先说结论:它现在长什么样

我的主力机是一台 CasaOS 小主机,上面跑着 一个 Hermes Agent runtime(~/.hermes)。就这一个 runtime,对外却分裂成 12 个互相隔离的「数字员工」——在 Hermes 里它们叫 Profile。每个 Profile 有自己的人格、自己的后端机器、自己的会话历史、自己的技能集和记忆。

Profile 它负责什么 后端
default 跨 Profile 超级管理员,SSH 进所有机器办事 本地 + SSH
casa CasaOS 主机:容器健康、Cloudflare Tunnel、OpenWebUI 本地
superdba / sqlserver 数据库 DBA:慢 SQL、索引碎片、P0 故障复盘 SSH
freqtrade 量化交易监控:持仓、盈亏、网格减仓 本地容器
safeline 雷池 WAF 服务器运维 SSH
cloudflare Tunnel / DNS / 域名解析 本地
<INTERNAL_BRAND> / fn-windows 飞牛私有云、Windows 机器(走 SSH 后端) SSH
aws-us 海外 GPU 机器上的 AI 网关与推理服务 SSH
oneapi API 网关与模型路由 SSH

这些 Profile 加起来,到写这篇文章时已经沉淀了 上万次会话、近十万条消息。这个系列要讲的,就是这上万次会话里踩过的坑、定下来的规矩、和最后跑顺了的那套方法。

# 2. Hermes Agent 是什么(只讲你需要知道的部分)

Hermes Agent 是 Nous Research 开源的一个 Agent 运行时。抛开宣传词,对运维来说,你只要记住三件事:

  • 它的核心就是一个同步的「对话-工具」循环:调用大模型 → 如果模型要调工具就执行 → 把结果塞回上下文 → 再调模型,直到任务完成。消息走 OpenAI 格式。
  • 它的能力来自工具(tools)和技能(skills):终端、文件、Web、定时任务、记忆、委派子 agent……工具是底层能力,技能是把能力打包成「怎么做某件事」的说明书。
  • 它能接到任何聊天入口:命令行、Telegram、Discord、网页 Dashboard、VS Code。同一个 Agent,多个面孔。

# 2.1 一段源码把循环讲清楚

hermes-agent/run_agent.py 里的 run_conversation() 就是整个 agent 的全部奥义——一个同步 while 循环,OpenAI 消息格式:

# ~/.hermes/hermes-agent/run_agent.py(伪码,行为等价)
def run_conversation(messages, tools, *, max_iterations=90):
    for i in range(max_iterations):             # 默认 90 轮硬上限
        resp = client.chat(messages, tools=tools)
        if not resp.tool_calls:                 # 模型不再要工具 → 自然结束
            return resp.content
        for call in resp.tool_calls:
            result = handle_function_call(call) # tools/* 派发执行
            messages.append({"role": "tool", "tool_call_id": call.id,
                             "content": result})  # 结果塞回上下文
    raise MaxIterationsError(max_iterations)
1
2
3
4
5
6
7
8
9
10
11

四个值得记的副作用:

  • 工具由 tools/registry.py 自动发现——任何一个 tools/*.py 里写 @registry.register(...) 的处理函数就被纳入候选集;候选集再被 toolsets.py 里的 _HERMES_CORE_TOOLS 收敛成「这个 agent 能用什么」。这是后面第 05 篇「技能会失控增殖」的根。
  • Profile 解析在 _hermes_home = get_hermes_home() 这行模块级缓存完成——一个进程一辈子只认一个 Profile,这就是第 02 篇那个 SSH 泄漏 Bug 的根因:进程没「重置」过。
  • 消息格式就是 OpenAI messages——意味着任何兼容 OpenAI 的下游(OpenWebUI / 第三方客户端)都能直接调它,这就是第 09 篇「Profile = 一个模型」的依据。
  • Python 模块在 import 时缓存——所以你改了 tools/*.py,必须重启对应网关进程才生效。光改文件不重启 = 没改。

模型这层我用的是自建的 newapi 网关(kimi-k2.5 打底,deepseek 兜底),后面有一整篇专门讲模型路由,这里先不展开。

# 3. 一个 runtime,五种「面孔」

同一套 Hermes,我用五种形态在用它,各管一摊。每一张脸都是同一个可执行入口——hermes 这个 console-script,只是命令行参数不同:

# ~/.hermes/hermes-agent/ 的可执行入口(pip install -e . 之后全部进 PATH)
hermes                 # hermes_cli/cli.py  →  交互式 CLI(我排障用得最多)
hermes gateway <p>     # gateway/run.py     →  把 Profile 接到 IM(长驻)
hermes --tui           # ui-tui/            →  Ink 写的终端 UI
hermes-acp             # acp_adapter/       →  接 VS Code/Zed/JetBrains
hermes-webui           # 单独仓库           →  系统级 systemd 服务(见 3.1)
1
2
3
4
5
6
形态 入口 跑法 谁在管
CLI hermes 前台 我手动
Gateway hermes gateway <p> 后台 部分 user 级 systemd,部分裸进程
WebUI hermes-webui 后台 /etc/systemd/system/hermes-webui.service(系统级,与 gateway 分离)
TUI hermes --tui 前台 我手动
ACP hermes-acp 后台 IDE 拉起

注意:不是每个 Profile 都有 systemd 服务——只有少数几个装了 user 级 systemd unit,其余就是裸进程。这个区别后面会咬人(见第 03 篇)。

# 3.1 启动顺序铁律(这条我用三次惨痛教训换来)

# 改了源码或配置 → 对应服务必须重启(Python 模块内存缓存)
systemctl --user restart 'hermes-gateway-*'   # 先网关
sudo systemctl restart hermes-webui           # 后 WebUI(它启动时连 Gateway)

# WebUI 是系统级服务(独立仓库 /etc/systemd/system/),别和 user 级搞混
sudo systemctl status hermes-webui
1
2
3
4
5
6

一句话记住依赖关系:Gateway 要先于 WebUI 启动(WebUI 启动时会去连 Gateway);改了源码或配置,对应服务必须重启——Python 把模块缓存在内存里,光改文件不重启等于没改。

# 4. 数据都放在哪:~/.hermes 布局

整套东西的状态全在 ~/.hermes 下,没有隐藏的魔法:

~/.hermes/
├── config.yaml        # 全局配置:模型、agent、gateway、安全、cron…
├── .env               # 密钥
├── active_profile     # 当前激活的 Profile 名(一行文本,粘性状态)
├── state.db           # 会话/消息库(SQLite + FTS5 全文检索)
├── profiles/&lt;name>/   # 每个 Profile 自己的 config / state.db / skills / logs
├── skills/            # 技能库
├── memories/          # 持久记忆
├── cron/              # 定时任务
└── logs/              # agent.log / errors.log / gateway.log
1
2
3
4
5
6
7
8
9
10

会话不是存在一堆 JSON 文件里,而是 SQLite 的 sessions / messages 两张表,带 FTS5 全文索引——这也是为什么我能把几个月、上万次对话一次性捞出来做成这个系列(怎么捞的,系列最后一篇讲)。每个 Profile 还会各自再有一份独立的 state.db,互不串扰。

# 4.1 真正看一眼 state.db 长什么样

-- ~/.hermes/state.db(精简到业务必需字段;FTS5 虚拟表同名镜像)
CREATE TABLE sessions (
    id              TEXT PRIMARY KEY,
    title           TEXT,
    started_at      REAL,                -- unix epoch
    message_count   INTEGER,
    tool_call_count INTEGER
);
CREATE TABLE messages (
    session_id   TEXT,
    role         TEXT,                   -- user / assistant / tool
    content      TEXT,
    tool_name    TEXT,
    timestamp    REAL
);
CREATE VIRTUAL TABLE messages_fts USING fts5(content, content='messages');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

可现场跑的命令(直接验证你的库是不是这个结构):

# 1. 全库会话计数
sqlite3 ~/.hermes/state.db \
  "SELECT COUNT(*) FROM sessions;"

# 2. 全文搜:捞出所有提到「checkpoint」的会话 id
sqlite3 ~/.hermes/state.db <<'SQL'
SELECT s.id, s.title, s.started_at
FROM   sessions s
JOIN   messages m ON m.session_id = s.id
JOIN   messages_fts f ON f.rowid = m.rowid
WHERE  messages_fts MATCH 'checkpoint'
GROUP  BY s.id
LIMIT  10;
SQL

# 3. 一个 Profile 的库同样适用
sqlite3 ~/.hermes/profiles/superdba/state.db \
  "SELECT title, message_count FROM sessions ORDER BY started_at DESC LIMIT 5;"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Hermes 自己也是这么读的——hermes_state.py 的 SessionDB.search_fts(query) 走的就是这条路径。hermes logs --session <id> 走的是 messages WHERE session_id=? ORDER BY timestamp。

# 4.2 关键路径必须走 get_hermes_home(),别硬编码

源码里所有 Profile-aware 的路径都从一个函数出:

# hermes-agent/hermes_constants.py
HERMES_HOME = Path(os.environ.get("HERMES_HOME", Path.home() / ".hermes"))
PROFILES_DIR = HERMES_HOME / "profiles"

def get_hermes_home() -> Path:
    """Profile-aware: the *current* profile's HERMES_HOME, not the root."""
    # 1) env 覆盖  2) `active_profile` 文件  3) 默认根
1
2
3
4
5
6
7

写自己的插件/工具时,永远走 get_hermes_home(),不要 Path.home() / ".hermes"——后者永远指向根,跨 Profile 时必错。这也是为什么这个系列里几乎每个工具示例第一行都是 get_hermes_home()。

# 5. 最关键的一个设计:Profile 隔离 + 超管穿透

整套部署的灵魂在两点,理解了它,后面所有篇章才看得懂:

  1. 隔离:每个 Profile 是一个独立沙盒——独立人格、独立后端、独立会话库、独立技能。sqlserver 这个 DBA 不会知道 freqtrade 在干嘛,也连不上它的机器。这是安全边界。
  2. 穿透:唯独 default 是超级管理员,它通过 ~/.ssh/config 里的别名(<INTERNAL_BRAND> / fn-windows / aws-us / oneapi…)SSH 进每一台机器办事,本地的事就用本地终端。但它不能「切换」成别的 Profile——delegate_task 委派子 agent 也跨不了 Profile 边界。

这个「隔离 + 穿透」的组合,恰恰是 Bug 的高发区。比如切换 Profile 时,上一个 Profile 的 SSH 后端连接会「泄漏」到下一个 Profile 的会话里——我曾经在 superdba 的会话里,发现它的命令跑到了 safeline 的机器上。这个跨 Profile SSH 泄漏我修过、提了 PR、还在每次升级时盯着它别被冲掉——单独一篇讲(第 10 篇)。

# 5.1 十分钟克隆一个新 Profile

新建 Profile 最稳的路是「克隆一个最像的、改三处」:

# 进入交互式 CLI
hermes
> /profile create fn-windows --clone <INTERNAL_BRAND>        # 从 <INTERNAL_BRAND> 克隆一份
> /profile list                                   # 看到 fn-windows 已建好
> /exit

# 改三处:终端后端 / 密钥 / 人格
$EDITOR ~/.hermes/profiles/fn-windows/config.yaml # 1. terminal.backend: ssh
$EDITOR ~/.hermes/profiles/fn-windows/.env        # 2. 这个 Profile 自己的密钥
$EDITOR ~/.hermes/profiles/fn-windows/SOUL.md     # 3. 它的人格(可选)

# 立即验证:基础对话是否正常
hermes --p<PASSWORD> fn-windows                       # 选一个无副作用的任务跑一下
sqlite3 ~/.hermes/profiles/fn-windows/state.db \
  "SELECT COUNT(*) FROM sessions;"                # >0 = 这个 Profile 已能独立落库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

最后一条 sqlite3 是立即验证——Profile 一旦开始用会话,就一定会有 state.db 写入。没创建 = 路径不对;创建了但 sessions 为 0 = 没有对话成功落地。下一章第 02 篇把这条「Profile 怎么搭」完整展开讲。

# 6. 这个系列接下来讲什么

这上万次会话里,真正值钱的不是「成功跑通」的部分,而是它怎么失败、为什么失败、最后怎么治住的。后面 11 篇都围绕真实事故和真实方法展开:

篇 主题 一句话钩子
02 多 Profile 与超管模型 一个 Agent 怎么安全地管十几台机器
03 Gateway 运维 systemd 还是裸进程?一个 Telegram token 撞车搞了我半天
04 模型路由 哪些模型真支持 thinking、辅助模型怎么配、401/503 根因
05 技能工程 技能怎么写、怎么去重、怎么每周自动审计
06 让 Agent 自己上班 cron 驱动的无人值守巡检
07 数据库实战 慢 SQL、索引碎片、一次放大 42 倍的 P0 复盘
08 量化交易助手 持仓盈亏与网格减仓分析
09 接入 OpenWebUI 把 Hermes 暴露成一个「模型」
10 升级与给上游提 PR 升级不丢本地补丁;那个 SSH 泄漏 Bug 的完整复盘
11 踩坑合集 checkpoint 膨胀吃满磁盘、Profile 目录莫名混乱…
12 工具链外延 Claude Code / opencode / ACP / 自动更新 webhook

举个后面会细讲的真实例子,先感受下「真实」是什么味道——有一次我只说了三个字:

记录这个

Agent 没动手,反而去加载了一堆笔记类技能,然后弹出一串「你要存成 Memory 还是文件还是 Obsidian?」的选择题,最后什么都没记下来。这是一个我后来反复治理的反模式:上下文已经很清楚时,Agent 应该自己推断默认值直接干,而不是把模糊当成借口反过来盘问用户。这种「澄清瘫痪」,以及一堆「网络层失败了却假装成功」的幻觉,才是真正要写进笔记的东西。

# 7. 适用读者与声明

  • 本系列假设你对 Linux、Docker、SSH、SQL 有基本概念,但不要求你用过 Hermes Agent。
  • 所有内容来自我个人自建的部署,不是 Hermes 官方文档;命令和路径以我这套为准,你的环境可能不同。
  • 文中所有 IP、密钥、令牌、内网域名、业务名均已脱敏处理。

# 8. 第一次部署?跑一遍这份自检

如果你打算自己起一套,下面是一份「装完应该是什么样」的可执行清单——任何一项不对,部署就没真正完成:

# 1) 五个入口都能起(CLI 至少能开,TUI 装好 node 后 npm run dev)
hermes --version                      # 应输出 commit hash + 版本
hermes gateway --help                 # 应列出子命令
systemctl --user list-units 'hermes-gateway*'    # 至少有你要长期跑的 Profile
sudo systemctl status hermes-webui    # 系统级服务 active

# 2) Profile 隔离确实生效
ls ~/.hermes/profiles/                # 看到 N 个 Profile 目录
for p in ~/.hermes/profiles/*/; do
  echo "== $(basename $p) =="
  sqlite3 "$p/state.db" "SELECT COUNT(*) FROM sessions;" 2>/dev/null \
    || echo "  (no sessions yet)"
done

# 3) state.db 全文搜索可用
sqlite3 ~/.hermes/state.db \
  "SELECT name FROM sqlite_master WHERE type='table' AND name='messages_fts';"
# 必须输出 messages_fts

# 4) FTS 真的能搜到东西(替换成你 Profile 里出现过的关键词)
sqlite3 ~/.hermes/state.db <<'SQL'
SELECT COUNT(*) FROM messages_fts WHERE messages_fts MATCH 'gateway';
SQL

# 5) 至少有一个 Profile 接到了 IM(或者你自己 CLI 跑了一次对话)
hermes logs --level WARNING --limit 20   # 没有堆红色 = 健康

# 6) Profile 切换不会泄漏(手工验证一次)
#    a) 在 superdba 里跑:echo "I-am-supperdba" > /tmp/marker && ssh <INTERNAL_BRAND> 'cat /tmp/marker'
#    b) 切到 safeline 跑同一条命令,应该拿不到这个 marker(或 SSH 重连)
#    失败 = 撞上了第 10 篇那个 SSH 泄漏 Bug 的旧版本
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
29
30
31

任意一条不过,回到第 02 篇(Profile 怎么搭)、第 03 篇(Gateway 怎么跑)、第 10 篇(升级后补丁还在吗)对应章节逐个排查。别跳过这条直接「能跑就行」——上面的六条覆盖了至少四个我真栽过的坑。

下一篇,我们把镜头拉近到那个最危险也最有用的 default 超级管理员,看一个 Agent 到底怎么做到「安全地」管十几台机器。

#AI Agent#Hermes#架构设计#运维
上次更新: 6/21/2026

← Hermes Agent 概述 Hermes Agent 实战 02|多 Profile 与超管模型:一个 Agent 安全地管十几台机器→

最近更新
01
Hermes Agent 实战 13|旗舰篇:让 Agent 从零部署并灾难恢复一个 7 节点生产集群 原创
06-22
02
Hermes Agent 实战 12|工具链外延:用 AI 运维 AI,与这个系列的诞生 原创
06-22
03
Hermes Agent 实战 11|踩坑合集:当「手动 rm」从来不是真正的修复 原创
06-22
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式