Hermes Relay(连接器)
Relay 处于实验阶段。在线协议、认证方案和配置在系统验证期间可能直接变更,不经过弃用周期。
Hermes Relay 本身不是一个聊天平台——它是一套连接器系统,让你的网关在不持有任何平台凭据的情况下,前置一个或多个真实消息平台(Discord、Telegram、Slack、WhatsApp 等)。一个独立服务——连接器——持有平台 bot token 和 socket。你的网关通过一条已认证的 WebSocket 向外拨出到连接器,握手时收到一份能力描述符,然后在该 socket 上交换规范化的消息事件(入站)和动作(出站)。
关键特性:
- 仅出站联网。 网关从不打开入站端口。入站消息沿网关拨出的同一条 WebSocket 回来,因此 relay 可在 NAT 后、以及无公网 IP 的主机上工作。
- 网关上没有平台机密。 Bot token 存放在连接器上。需要鉴权的平台媒体 URL 在连接器侧重新托管,因此平台凭据永不过线。
- 平台无关。 网关从握手描述符了解前置平台能做什么(消息长度限制、markdown 方言、编辑/线程/流式支持,以及确切的受支持操作集合),而非从硬编码的平台逻辑。
正式的网关 ⇄ 连接器接口见仓库中的 docs/relay-connector-contract.md。
何时用 Relay
Relay 适用于由托管或共享连接器服务管理平台侧的部署——例如多租户托管,一个共享 bot 前置众多用户的 agent;或你不希望 bot token 放在网关机上的设置。如果你直接跑自己的 bot,请改用原生平台适配器(Telegram、Discord 等)。
注册
自托管网关用一个按网关生成的密钥向连接器认证。hermes gateway enroll 用一个一次性注册 token(在你的租户路由被开通时由连接器铸造,并随你的网关配置下发)兑换出该密钥:
hermes gateway enroll \
--token <enrollment-token> \
--connector-url wss://connector.example.com/relay
它做的事:
- 从你已有登录(
~/.hermes/auth.json)解析一个新的 Nous Portal 访问 token——这证明你拥有哪个 Nous 组织(租户)。如果配置了gateway.idp.token_url,则改用你自己的 IdP(离线/自托管 IdP 路径,不涉及 Nous Portal):配置了client_id/client_secret时执行通用的 OAuth2 client-credentials 授权;两者都没配时,该 URL 被视为环境 token 端点(普通 GET,响应体即 token——metadata-server 模式,例如 Domino 的$DOMINO_API_PROXY/access-token)。两个凭据只配其一算错误。 - 通过 TLS 向连接器的
/relay/enroll端点 POST 注册 token 和一个网关 id。 - 连接器校验 token(签名、一次性、租户匹配),铸造一个按网关的密钥加一个按租户的投递 key,并一次性返回它们。
- 把凭据持久化到
~/.hermes/.env:GATEWAY_RELAY_ID、GATEWAY_RELAY_SECRET、GATEWAY_RELAY_DELIVERY_KEY(若提供还包括GATEWAY_RELAY_URL/GATEWAY_RELAY_WAKE_URL)。
之后重启网关以加载新环境。
参数:
| 标志 | 说明 |
|---|---|
--token | 一次性注册 token。也可通过 GATEWAY_RELAY_ENROLL_TOKEN 设置。 |
--connector-url | 连接器 base 或 relay URL(wss://…/relay 或 https://…)。也可通过 GATEWAY_RELAY_URL 或 config.yaml 中的 gateway.relay_url 设置。 |
--gateway-id | 此网关实例的稳定 id(用于 kill-switch 粒度)。默认 gw-<hostname>。 |
--wake-url | 可选的可达 URL,连接器在这个网关空闲、缓冲工作到达时戳它(无载荷 GET)以唤醒。持久化为 GATEWAY_RELAY_WAKE_URL。没有它,网关在下次重连时仍会排空缓冲消息。 |
hermes gateway enroll 在托管/受管安装中拒绝运行——那里托管平台直接把 relay 密钥注入容器环境。
配置
配置了连接器 relay URL 时 Relay 即激活。即使部署注入了 URL,也想让某个 profile 不走 relay,可在 config.yaml 中禁用该平台:
platforms:
relay:
enabled: false
- 显式禁用优先。 设了
enabled: false,网关就不会解析身份 token、开通或重写GATEWAY_RELAY_*凭据、注册 relay 适配器或发送相关性策略——即使设了gateway.relay_url或GATEWAY_RELAY_URL。原生消息适配器照常连接,如同没有 relay URL;cron 投递也不把任何平台视为 relay 前置。 - 省略
enabled则保持基于 URL 的激活。enabled: true仍需要连接器 URL。gateway.json里的enabled: false只是建议性的,与其他所有平台一样;退出选项放在config.yaml(用户或托管)里。
判定来自网关加载器所用的同一批文件与合并方式(顶层或 gateway.platforms 块、托管覆盖层),在激活时应用。改完后重启网关;已打开的 relay socket 不会被拆除。运行时 relay 禁用期间,hermes gateway enroll 仍可用。
| 设置 | 位置 | 含义 |
|---|---|---|
GATEWAY_RELAY_URL | 环境(~/.hermes/.env) | 连接器 relay WebSocket URL。除非在平台配置中显式禁用,否则启用 relay。 |
gateway.relay_url | config.yaml | 同上,配置文件形式(环境优先)。 |
GATEWAY_RELAY_ID | 环境 | 此网关实例 id(由 enroll 写入)。 |
GATEWAY_RELAY_SECRET | 环境 | 认证 WebSocket 升级的按网关密钥(由 enroll 写入)。 |
GATEWAY_RELAY_DELIVERY_KEY | 环境 | 按租户投递 key(由 enroll 写入;为向前兼容保留)。 |
GATEWAY_RELAY_WAKE_URL / gateway.relay_wake_url | 环境 / config.yaml | 空闲/挂起网关的可选唤醒戳目标。 |
GATEWAY_RELAY_PLATFORMS | 环境 | 此网关在一条连接上前置的平台列表,逗号分隔(如 discord,telegram)。通常由部署/编排器盖戳。 |
GATEWAY_RELAY_BOT_IDS | 环境 | 按平台 bot 身份的 JSON 映射,如 {"discord": {"botId": "…"}}。与 GATEWAY_RELAY_PLATFORMS 配对。 |
gateway.idp.token_url | config.yaml | 设置后,注册/开通改向你自己的 IdP 认证而非 Nous Portal:同时设了 gateway.idp.client_id/client_secret 时走 OAuth2 client-credentials;否则视为环境 token 端点(普通 GET 返回 token,原始或 {"access_token": …})。 |
受支持的能力
relay 连接上实际能用什么在握手时协商:连接器声明一个 supported_ops 列表,网关只使用连接器显式声明的操作(旧连接器回退到遗留的 send/edit/typing/follow_up 集合)。按平台的能力标志(基于编辑的流式、线程、草稿流式、markdown 方言、消息长度限制)也来自握手描述符。在该协商前提下,relay 支持:
- 文本消息与流式——发送、回复,以及前置平台支持消息编辑时的渐进式基于编辑的流式;否则输出退化为每段一条消息。
- 双向媒体——出站图片、语音、音频、视频和文档上传到连接器(或由公开 URL 引用),通过各平台原生上传通道投递,带说明文字。入站附件落地为文件供 agent 使用;需鉴权的平台 URL 在连接器侧重新托管,因此平台凭据永不到达网关。媒体再托管上限 25 MB,约 1 小时过期。
- 原生交互提示——执行审批、确认和澄清问题以原生平台控件渲染(Discord 按钮、Telegram 内联键盘、Slack Block Kit 动作、WhatsApp 按钮/列表消息),而非编号文本回退。按钮点击以真实点击用户的已认证提示响应返回,因此网关的授权门限与对打字回复一视同仁。提示过期由网关侧强制。
- 回应表情 ack 生命周期——机器人的处理状态表情(处理中 👀、完成 ✅/❌)在 relay 上工作。表情是尽力而为:失败的表情绝不会让一轮失败。
- 线程生命周期——通过平台抽象的
thread_create/thread_rename操作创建交接线程和重命名线程(包括 LLM 拟题的语义重命名),带不覆盖保护,因此人工手动重命名优先。可用性取决于平台(例如 Slack 线程不能重命名;WhatsApp 没有线程)。 - 正在输入指示——网关在处理期间通过连接器发出正在输入(及停止输入)。
- 聊天元数据——
get_chat_info查询在被声明时代理到连接器。 - 缓冲投递与唤醒——网关空闲或断开时,连接器持久缓冲入站消息,重连时按序回放(ack 门控,不丢不重)。若注册了唤醒 URL,连接器在缓冲工作到达休眠网关时戳它。
支持多平台前置:一个网关可在单条 relay 连接上前置多个平台(例如 Discord 和 Telegram),每条出站消息按其目标平台打标。
故障排查
注册返回 401——连接器无法校验你的身份 token。用 hermes auth add nous(或 hermes setup)重新登录后重试。
注册返回 403——注册 token 无效、过期、已用过,或属于另一个租户。注册 token 一次性使用;向为你开通租户路由的人索要新的。
"Could not reach the connector"——检查连接器 URL。你可以粘贴 wss://…/relay 拨号 URL 或 https://… base URL;CLI 自动在二者间映射。
enroll 拒绝运行——你在托管/受管安装中,relay 密钥由托管平台开通。自助注册仅用于自托管网关。
Relay 平台在之前能用后显示为禁用——握手成功之后出现 4401 关闭码,意味着网关密钥被吊销(例如实例被回收)。网关刻意停止重连并把 relay 报为禁用,而非重试。在任何成功握手之前的 4401 视为尚未开通的瞬时竞态,正常重试。
注册后毫无变化——网关在启动时读取 GATEWAY_RELAY_*。重启它(hermes gateway restart)。
某个功能(按钮、媒体、线程……)悄悄退化为纯文本——你那个平台的连接器在握手 supported_ops 中没有声明该操作。网关刻意回退到文本行为,而不是发送一个连接器处理不了的 op。