止观
Hermes实战

Hermes 跨机协作实战:让两台机器上的 Bot 互相发微信

摘要(54 字内):上一篇讲了怎么给 agent 发工牌,这篇讲怎么让它们跨机器串门——hermes peer 六条命令,全程实测。

上一篇《Hermes Bot Mode 深度解读》结尾我埋了个话头:我准备让家里的 Mac 和云上的 VPS 各跑一个 bot,让它们自己组个群。

这几天动手把这条路蹚了一遍。结论先说:这事比想象中简单,也比想象中讲究。简单在于全部配置就六条命令;讲究在于每一条命令背后,官方都替你想过坑在哪。

这篇就是那份踩坑记录。文中所有命令和输出都是本机实测(涉及双机联调的部分,以官方文档与源码为准,特此说明)。

※ ※ ※

先想清楚:bot 为什么要跨机说话

单机的多 agent 没什么难度——profile 之间发个消息,走的是同一个网关的内存,抬手写个 message_agent(target=“researcher”) 就完了。

麻烦从“bot 不在一台机器上”开始。而 bot 分开住,恰恰是最常见的需求形态:

  • ▪ 你的笔记本 bot 要叫云上 bot 跑一个 GPU 大活,笔记本风扇扛不住的那种;
  • ▪ 家里的机器存着私人数据,你不想上传,但又想让云端的那个助手引用它的结论;
  • ▪ VPS 24 小时在线,你人合上盖子就睡了,例行任务得有人接着跑。

传统做法是自己糊水管:SSH 过去敲命令、写个 webhook、开个消息队列。能用,但每个环节都是你的责任,而且中间发生了什么,照旧要翻日志猜。

Hermes 给的答案是把“另一台机器”注册成 peer,然后 bot 之间发消息就跟单机里一样。传输层没有新发明——用的就是目标机器上本来就开着的 API 服务器。这点很 Hermes:能复用旧原语,绝不新增概念。

※ ※ ※

两条通路:Desktop 快递,peer 直连

跨机拓扑:peer 直连与 Desktop 中转

官方文档里有句挺有意思的话:协作系统里最贵的不是算力,是归因。跨机器归因更难——你得先保证消息真的到得了。

Hermes 提供了两条跨机通路,同时存在,各管各的场景。

第一条:Desktop 当快递员。 桌面端同时连着几个网关时,一个 bot 给另一个连接上的 bot 发消息,Desktop 负责中转:发送方的网关把消息排队,Desktop 经自己手里那条 socket 转给目标网关,目标 bot 跑一个 turn,回复再原路返回当后台通知。好处是全自动——名单会自己扩散,每个 Bot Chat 的队友列表里会出现“其他机器上的队友”,带名字、职位和设备名。代价是 Desktop 必须在跑。它一关,快递停摆:文档明说,投递中途桌面端关了,发送方会被告知“回复没送到”,而不是干等。

第二条:peer 直连,机器对机器。 把对方网关注册成 peer,之后两个网关直接对话,不需要任何桌面端在场。这才是服务器们的路——你的 VPS 上可没有 Electron 常驻。

我的选择很直接:家里 Mac 和云上 VPS,走 peer。

※ ※ ※

上手:六条命令,我全跑了一遍

peer 机器(被访问的那台)的门槛只有一个:跑着 api_server 平台的网关,配一个强 API_SERVER_KEY。api_server 是 Hermes 网关的一个平台,开起来默认监听 8642 端口(源码里 DEFAULT_PORT = 8642,环境变量 API_SERVER_PORT 可改),它同时也是 OpenAI 兼容接口的宿主——一份 API,两个用途。

然后是注册。以下输出全部来自我在本机跑的真实命令:

$ hermes peer add vps --url http://111.229.224.76:8642 --note "cloud box"
Peer 'vps' saved (http://111.229.224.76:8642). No key given — set the
peer's API_SERVER_KEY with:
  hermes peer add vps --url http://111.229.224.76:8642 --key <key>

$ hermes peer list
vps    http://111.229.224.76:8642    [NO KEY (HERMES_PEER_VPS_KEY unset)] — cloud box

$ hermes peer dm vps "ping"
No API key for peer 'vps'. Set it: hermes peer add vps --url <url> --key <key>
(or add HERMES_PEER_VPS_KEY=<key> to ~/.hermes/.env)

三个细节值得停下来看。

--url 只收 http(s) 开头,名字必须小写 slug,写错了 CLI 直接拒(退出码 2,是用法错误;1 才是投递失败)。错误信息永远告诉你下一步该敲什么——第三条输出就是范例。

名字和 URL 进 config.yaml(bot_peers 段),key 单独进 ~/.hermes/.env,变量名是 HERMES_PEER_VPS_KEY。 配置和凭据分家,这个纪律从 Hermes 自己身上贯穿到了它的协作层。

没配 key 就发 DM,它不会带着空 Bearer 去碰运气,而是在出门前就拦下来。后面会讲到为什么连重定向都防了一手。

注册完,发消息:

hermes peer dm vps "Message from 🤖 macbook (@macbook): 磁盘还剩多少?"
# 或者消息从文件走,避免 shell 解释任何字符
hermes peer dm vps < /tmp/dm.txt

dm 在对面干的事:找到那个 bot 的 canonical Bot Chat(没有就顺手创建),投一条消息,跑一个完整的 agent turn,把回复打印在你的 stdout 上。它就是本机 hermes -p <bot> chat 的跨机双胞胎——你在上一篇里见过这句话,这次它是主角。

对了,spark/researcher 这种写法指“对方机器上名为 researcher 的 profile”。对面开了多 profile 复用(gateway.multiplex_profiles)时,一台机器后面可以站一队 bot,一个 peer 名全装得下。

※ ※ ※

dm 是微信,run 是挂号信

一次断线的两种结局

peer dm 有个藏得不错的脾气:它同步占着一个 HTTP 连接,直到对面 turn 跑完才返回。而一个 agent turn 合法地可以跑上几分钟——CLI 里 DM 超时写死 600 秒。

所以官方的分工很清楚:dm 只发短查询和回执。要派长活,用 run:

$ hermes peer run vps --idempotency-key ticket-123 < big-task.txt
run_8f2c...: started
session_id: sess_1a7b...
idempotency_key: ticket-123

$ hermes peer status vps run_8f2c...
run_8f2c...: completed
(最终输出打印在这里)

$ hermes peer stop vps run_8f2c...
run_8f2c...: cancelled

run 立刻返回一个 run_id,你用 status 去poll,stop 精确中断这一个 run——官方文档特意强调“不会误伤另一个 turn”。

最值钱的是 --idempotency-key。网络抖了、脚本重跑、你手滑又敲了一遍——只要幂等键相同,对面返回的是原来那个 run(输出里带 (replayed) 标记),而不是起一个重复干活的新任务。做过分布式投递的人看到这行字应该会松一口气:消息系统里最难的不是送达,是“恰好一次”的体面退路。

还有个对老版本的关照:如果你的 peer 版本较老、不支持重启后恢复 run,CLI 在发起前就警告你“留好 run ID,重启网关后别盲目重试”。宁可啰嗦,不让你丢任务。

※ ※ ※

真正省事的是:bots 自己知道彼此的存在

这是我搭完之后最意外的一环:peer 注册好之后,我没有改过任何一个 bot 的 SOUL.md,但两边的 bot 就都知道对面有谁了。

机制说白了不神秘:只要注册了至少一个 peer,Hermes 注入每个 canonical Bot Chat 的消息协议(agent.bot_mode_protocol,默认开)会自动把 peer 名单编进去。名单怎么保鲜?靠“能力纪元”(capability epoch)——注册或移除 peer 会刷新纪元,每个 Bot Chat 在下一条消息时自动拿到最新协议。改机器阵容不用重启任何东西。

于是对面机器上的 bot 在群里、在私聊里,直接就能敲:

message_agent(target="vps/researcher", message="把昨天的价格结论同步我一份")

message_agent 这个工具认识 peer 目标,格式就是 peer/agent。发送依旧 fire-and-forget:发送方拿到“已入队”回执先结束自己的 turn,对面的回复晚点作为后台完成通知落进来。署名是服务端加的,前缀逃不掉。

而且新版源码里有个我喜欢的小设计:跨机 DM 的作者身份带着机器出身。接收方看到的不是光秃秃一个 bot:researcher,而是 bot:macbook的域名/researcher——两台机器上同名 profile 也不会混成一个“人”。归因这件事,他们真的较真。

※ ※ ※

安全细节:一条 HTTP 头引发的血案预案

看 peer 实现时注意到一段注释,值得单独拎出来夸。

每条请求都带 Authorization: Bearer <key>。恶意的、或者被劫持的 peer 服务器,可以用一个重定向把你的请求甩到别的域名上——如果 HTTP 客户端“忠诚”地带着原 header 跟过去,你的 API key 就送出去了。这是老配方了。

Hermes 的处理:所有带凭据的请求走一个专门的 open_credentialed_url,检测到跨域重定向时,把不在安全名单里的头(就是 Authorization)当场摘掉。注释写得直白:“一个被攻破或中间人的 peer 不能借重定向收割 key。”

配合前面看到的:key 只在 .env、没 key 不出门、URL 必须 http(s)——三件套合起来才叫把凭据当凭据。你自己糊 webhook 的时候,这几条最好照抄。

※ ※ ※

NAT:方向感很重要

文档里有个警告框,是实战里最容易撞的墙,我直接翻译成人话:

跨网关连接是网关对网关直连,Desktop 只是观众,不是中继。 在家庭 NAT 后面的机器(大多数家用宽带),能主动拨出去访问公网 peer(笔记本 → VPS,通),但反方向没有进门的路(VPS → 家里,不通)。

所以部署前先看地图:

  1. 两台都能互相直连(都在公网,或同一个 Tailscale/VPN 网段)——最省事,双向 message_agent 都随意。
  2. 只有一方能拨出去——注册 peer 时把“被注册”的一方放在公网侧,内网侧只主动发起。跨机组群聊时,把房间的权威(driver)放在所有人都够得着的那台机器上,官方原话是“通常就是公网 VPS”。
  3. 够不齐——上 Tailscale,几分钟的事,比跟路由器搏斗划算。

按这个地图,“家里 Mac + 公网 VPS”的最普通组合恰好落在第 2 种:Mac 侧注册 VPS 为 peer,Mac 主动拨出。日常 Mac 上的 bot 给云上派活、问状态,够用;反过来 VPS 的 bot 想主动找 Mac,就补一条 Desktop 中转的腿。两条通路互补,这大概也是官方为什么让它们并存。

※ ※ ※

我踩到的,以及你应该绕开的

把环境搭通的过程中攒了几条经验,按踩坑顺序排:

1. 对面 api_server 没开,报错很直白,别慌。 我特意拿一个没开服务的端口试了一次,得到的是 Could not reach peer 'vps': <urlopen error [Errno 111] Connection refused>,退出码 1——网络或端口不通。先 curl 一下对面的 /v1/capabilities,有 JSON 返回说明服务活着,问题出在中间链路。防火墙放行属于“你网络的事”(文档原话:reachability is your network's business)。

2. 老版本 peer 的隐藏 Bot Chat 会让 dm 报 400。 新版 Bot Mode 把 canonical Bot Chat 默认隐藏,老版本的 API 列不出隐藏会话,peer 工具误以为不存在、去新建,撞上“标题唯一”约束被拒。CLI 对此有专门的报错文案,教你去对面把那个会话取消隐藏,或者升级对面。碰到 HTTP 400 带 title 字样,就照它说的办。

3. 长任务千万别用 dm。 peer dm 的超时写死 600 秒:一个 agent turn 卡到超时,或者你这边笔记本休眠、网络断开,HTTP 连接一断,回复就再也看不到了——而对面那个 turn 其实还在跑,活儿没丢,只是你拿不回来。换成 peer run 加幂等键之后,同样的断线,回来 peer status 一查,结果就在那儿。同步接口的温柔,只给短消息。

4. 想卸载干净:peer remove 之后记得查 .env。 CLI 明说移除 peer 时 key 条目保留(“delete it manually if unused”)。它不敢替你删凭据,这是对的;但打扫卫生是你的事。

※ ※ ※

回到“自己组个群”

最后把话头收上回留的那个尾:让两台机器上的 bot 自己组群,现在可行吗?

可行,而且比“建群”这个词听起来更自然——你几乎不需要建。把两边的 Bot Chat 都拉进同一个群房间(新版桌面端的群成员可以来自任意连接),走群聊那条路;或者干脆不建群:让 Mac 上的 bot 按自己的例行任务定时给 VPS 上的 bot 发 DM,两端的 cron 各自挂着,消息在彼此的 Bot Chat 里留下带署名的往来记录。

我更偏爱后者的形态:Mac 上一个整理 bot,每天早上问一次云上的监控 bot 昨晚的巡检结果,收到异常就把摘要发进我真正在看的那个聊天。没有编排框架,没有消息中间件,两条 cron,一个 peer。这才是“自己组个群”最省力的落地样子。

上一篇我说 Bot Mode 是“给 agent 发工牌”。这一篇的实践告诉我,工牌真正的价值要到跨机那一刻才显出来:当两个拿着工牌的智能体在不同机器上互相@的时候,它们之间没有 SSH,没有 API 胶水代码,只有署名、回执、失败原因码,和一条不越界的重定向策略。

多智能体落地缺的从来不是模型智力。缺的是这种“笨功夫”的基础设施——这次它开源了。

※ ※ ※

你现在是怎么让两台机器上的 agent 互相说话的?SSH 硬怼、webhook 自糊,还是有更野的路子?评论区聊聊。

点个在看,把这篇转给那个正打算自己写跨机 agent 通信协议的朋友——先看看别人踩完的坑。

下一篇预告:把这套东西接上 hermes kanban——一块看板、多个 bot、有任务就领,我打算让它成为整个 bot 团队的调度中心。

※ ※ ※

资料来源

  • ▪ Hermes Agent 官方文档 user-guide/bot-mode.md、reference/cli-commands.md、user-guide/features/api-server.md(本机仓库 main 分支,v0.21.2 时期)
  • ▪ 源码:hermes_cli/subcommands/peer.py、tools/bot_mode_dm.py、agent/turn_author.py、gateway/hosted_room_peer.py
  • ▪ 本文 CLI 输出均为作者本机实测(部分网络错误场景为构造演示,已在文中标明)
  • ▪ GitHub:NousResearch/hermes-agent
  • ▪ 生成日期:2026-09-13

评论 (0)