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 管一整个机房
    • Hermes Agent 实战 02|多 Profile 与超管模型:一个 Agent 安全地管十几台机器
      • 1. Profile 到底是什么
        • 1.1 创建一个 Profile
      • 2. 两种终端后端:本地 vs SSH
      • 3. 超管模型:能进所有机器,但不能「变身」
        • 3.1 为什么不能用委派(delegate)
        • 3.2 真正的做法:SSH-out(纯配置)
        • 3.3 安全红线:别为了 SSH 去读 .env
      • 4. 可复现:给你自己的 Agent 搭一套最小「超管 + 分身」
        • 4.1 Hermes 自带的 /profile 子命令清单
      • 5. 这套设计最容易出事的地方
    • 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-22
目录

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

# 多 Profile 与超管模型:一个 Agent 安全地管十几台机器

系列第 02 篇。上一篇讲了「一个 runtime 分裂成 12 个数字员工」,这篇讲清楚这套隔离是怎么搭起来的、那个能进所有机器的「超级管理员」到底怎么实现的,以及——为什么它不能用你以为的那种方式实现。文末给一套可以直接照抄、在你自己 agent 上复刻的最小方案。

# 1. Profile 到底是什么

在 Hermes 里,一个 Profile 就是一个完全独立的沙盒,物理上对应 ~/.hermes/profiles/<name>/ 下的一个目录:

~/.hermes/profiles/superdba/
├── config.yaml      # 这个 Profile 自己的模型/终端/网关配置
├── state.db         # 自己的会话与消息(和别人完全不共享)
├── .env             # 自己的密钥
├── skills/          # 自己的技能集
├── logs/            # 自己的日志
└── workspace/       # SSH 后端时的工作目录
1
2
3
4
5
6
7

关键点:会话库是 per-Profile 的。superdba 这个 DBA 看不到 freqtrade 的任何对话,也连不上它的机器。这是一条安全边界,不是组织上的方便而已。

# 1.1 创建一个 Profile

最省事的方式是从一个已有 Profile 克隆,再改后端。我当初建 fn-windows 就是从 <INTERNAL_BRAND> 克隆的:

# 交互式:hermes 命令行里
hermes
> /profile create fn-windows --clone <INTERNAL_BRAND>

# 克隆完之后,编辑它的 config.yaml 把后端指向新机器
# ~/.hermes/profiles/fn-windows/config.yaml
1
2
3
4
5
6

克隆会带上源 Profile 的配置骨架(模型、技能引用、网关设置),你只需要改三处:终端后端、.env 密钥、人格(SOUL/system_prompt)。

# 2. 两种终端后端:本地 vs SSH

每个 Profile 在 config.yaml 的 terminal.backend 决定它的命令跑在哪:

# 本地后端:agent 的命令直接在本机执行
terminal:
  backend: local

# SSH 后端:命令通过 SSH 在远程机器执行
terminal:
  backend: ssh
  # 具体连接参数由 ~/.ssh/config 的别名提供(见下文)
1
2
3
4
5
6
7
8

一个反直觉但很重要的点:backend: local 不代表「这台机器能从主控机本地访问」。它只代表「这个 Profile 的 agent 进程跑在它自己的宿主机上」。比如我有个 GCP 上的 Profile,它配的是 local,但对主控的 CasaOS 来说它仍然是一台远程机器——因为那个 agent 进程根本不在 CasaOS 上跑。判断「要不要 SSH」的依据是目标机器是不是主控机本身,而不是配置里写的 local。

# 3. 超管模型:能进所有机器,但不能「变身」

我想要一个 default Profile 当跨 Profile 超级管理员——一句话就能让它去任意一台机器上办事。这里有个大坑,先说结论:

超管是靠「SSH 出去」实现的,不是靠「委派/切换 Profile」实现的。

# 3.1 为什么不能用委派(delegate)

Hermes 有 delegate_task 工具可以派生子 agent,直觉上你会想:「让 default 委派一个跑在 <INTERNAL_BRAND> 身份下的子 agent 不就行了?」——不行。原因很实在:

  • delegate_task 没有 profile 参数,子 agent 加载不了另一个 Profile 的终端后端和 .env;
  • 子 agent 继承父 agent 的后端(local),只有模型凭证能覆盖;
  • 更底层的原因:run_agent.py 里 _hermes_home = get_hermes_home() 是模块级的——一个进程一辈子只认一个 Profile。想「中途变身成另一个 Profile」得改源码。
# hermes-agent/tools/delegate_task.py —— 真实签名(节选)
@registry.register(
    name="delegate_task",
    description="Spawn a child agent to handle a subtask in parallel.",
    parameters={
        "type": "object",
        "properties": {
            "task":      {"type": "string"},  # 子任务描述
            "context":   {"type": "string"},  # 显式注入的上下文(可选)
            "tools":     {"type": "array",  "items": {"type": "string"}},
            "model":     {"type": "string"},  # 子 agent 用哪个模型
        },
        "required": ["task"],
    },
)
def handle_delegate_task(args):
    ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

注意 properties 里没有 profile 字段——这就是「跨 Profile 委派不存在」的源码级证据。子 agent 会继承父进程的环境(HERMES_HOME / active_profile / 终端后端),所以「让 default 派一个 <INTERNAL_BRAND> 身份的子 agent」技术上做不到。

所以「子 agent 用 <INTERNAL_BRAND> 的身份运行」不是一个真实存在的功能。别在这上面浪费时间。

# 3.2 真正的做法:SSH-out(纯配置)

超管的实现其实朴素得多——default 的后端是 local,要去远程机器就 ssh <别名> '...',别名全部写在 ~/.ssh/config 里。完整可用的模板:

# ~/.ssh/config —— 超管的「机器花名册」
# ⚠️ Host 别名全小写、语义化;agent 会用这些名字直接 ssh ...

Host <INTERNAL_BRAND>
    HostName <IP>
    User <USERNAME>
    IdentityFile <SSH_KEY_PATH>
    ServerAliveInterval 30
    ServerAliveCountMax 3

Host fn-windows
    HostName <IP>
    User <USERNAME>
    # 远端是 PowerShell,agent 写命令时不能直接用 bash 习惯

Host gpu-ai
    HostName <IP>
    Port 2223
    User <USERNAME>
    IdentityFile <SSH_KEY_PATH>
    # 公网机:keepalive + 长超时
    ServerAliveInterval 15
    ServerAliveCountMax 6

Host *
    # 通用兜底(避免被 SSH 第一次连接卡在「Are you sure you want to continue connecting?」)
    StrictHostKeyChecking accept-new
    AddKeysToAgent yes
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

配好之后,超管 agent 的「去 <INTERNAL_BRAND> 检查容器」就翻译成一条 ssh <INTERNAL_BRAND> 'docker ps'。对那些目标就是主控机本身的 Profile(如本机的 CasaOS),超管直接用本地终端管理,连 SSH 都不用。

一次性把所有别名都验证一遍(agent 第一次跑 ssh 之前你必须自己跑过):

for h in <INTERNAL_BRAND> fn-windows gpu-ai; do
  echo "== $h =="
  ssh -o ConnectTimeout=5 -o BatchMode=yes "$h" 'hostname; uname -r; uptime' \
    || echo "  ! failed: $h"
done
1
2
3
4
5

-o BatchMode=yes 让它不会卡在密码/known_hosts 提示上——一次性验证哪些能用 key 直连。如果某台失败,先回到那台机器去修 SSH 服务端的 ~/.ssh/authorized_keys,别让 agent 替你 debug SSH 握手。

# 3.3 安全红线:别为了 SSH 去读 .env

这是我给自己立的硬规矩,也建议你照做:

日常 SSH 不要去读 profiles/*/.env。

端点信息全在 ~/.ssh/config 里,足够了。一旦 agent 去 cat 某个 Profile 的 .env,会把里面所有 API key / token 拉进上下文,而上下文是会落进 state.db 的——这正是 Hermes 的 read_file 密钥防护要拦的东西。只有当超管确实需要某个 App 的应用凭证时,才去读对应的 .env。

# 4. 可复现:给你自己的 Agent 搭一套最小「超管 + 分身」

不管你用的是 Hermes 还是别的 agent 框架,这套思路都能复刻。最小可用版本:

  1. 一个超管入口:一个能执行本地 shell、且持有一把 SSH 私钥的 agent 会话。
  2. 一张机器花名册:把每台目标机写进 ~/.ssh/config 的 Host 别名,统一用一把 key。先手动验证每个别名都能免密连上(参见 3.2 末尾的 for h in 循环)。
  3. 给 agent 的系统提示里写清楚边界:它对哪些机器是「本地直管」、哪些是「SSH 出去」、以及「不准为了连机器去读密钥文件」。
  4. 每台机器一个独立会话/记忆空间:哪怕你的框架没有 Profile 概念,也至少让每台机器的对话历史分库存放,避免上下文互相串味。

这套的好处是:机器清单的维护权在 ~/.ssh/config,不在 agent 的脑子里——加一台机器就是加一个 Host 块,agent 立刻就会用,不需要改 prompt、不需要重训。

# 4.1 Hermes 自带的 /profile 子命令清单

下面这套是当前可用的最全子命令(所有子命令都来自 hermes_cli/commands.py 的 COMMAND_REGISTRY——意味着 CLI、Telegram 菜单、Slack 路由、自动补全都用同一份):

hermes
# 在交互式 REPL 里:
> /profile list                    # 列出所有 Profile + 标记当前激活
> /profile create fn-windows --clone <INTERNAL_BRAND>
> /profile switch superdba         # 切换当前激活(写 active_profile 文件)
> /profile delete <name>           # 删一个 Profile(带二次确认)
> /profile rename <old> <new>
> /profile show <name>             # 显示 config.yaml 关键块

# 一次性 CLI:
hermes profile list
hermes --p<PASSWORD> superdba "select top 10 from dbo.orders"
1
2
3
4
5
6
7
8
9
10
11
12

注意 COMMAND_REGISTRY 的设计:所有 /xxx 斜杠命令都进同一份字典——加命令 = 加 CommandDef + 加 cli.py 的 process_command() 处理。少改一边,自动补全和 Telegram 菜单就跟新命令脱节。这条对插件开发者尤其重要。

# 5. 这套设计最容易出事的地方

「隔离 + 穿透」的组合天生是 Bug 高发区。最典型的一个:切换 Profile 时,上一个 Profile 的 SSH 后端连接会泄漏到下一个 Profile 的会话里——我亲眼见过 superdba 的命令跑到了 safeline 的机器上。根因还是那句话:get_hermes_home() 是模块级缓存,一个进程的状态没干净地随 Profile 切换重置。这个 Bug 我修了、提了 PR、并且每次升级都要盯着它别被上游合并冲掉——完整复盘放在第 10 篇。

下一篇讲 Gateway:怎么把这些 Profile 接到 Telegram,systemd 服务和裸进程的坑,以及一个 Telegram token 撞车是怎么让我排查了半天的。

#AI Agent#Hermes#多租户#SSH#运维
上次更新: 6/21/2026

← Hermes Agent 实战 01|架构总览:用一个 Agent 管一整个机房 Hermes Agent 实战 03|Gateway 运维:systemd、裸进程,和一个 Telegram token 撞车→

最近更新
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
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式