Hermes 101|11|Gateway
Gateway 让 Hermes 从命令行程序变成跨 Telegram、Discord、Slack、Webhook 等入口持续运行的 Agent。
Gateway 是 Hermes 从“终端里的 Agent”变成“到处都能用的 Agent”的关键层。没有 Gateway,Agent 只能等待 CLI 输入;有了 Gateway,同一个运行时可以接入 Telegram、Discord、Slack、Webhook、Email、SMS、Matrix 等平台。
Gateway 不是简单的 bot wrapper。它要处理平台连接、消息归一化、session key、权限、slash command、审批、打断、投递和长任务生命周期。

读完本文,你应该能回答
- Gateway 如何把 CLI Agent 变成跨平台 Agent?
- Adapter、Runner、Session Key 分别解决什么问题?
- 消息平台里的打断、审批和投递为什么比终端更复杂?
- mini-agent-harness 如何支持 Telegram/Discord/Webhook 这类入口?
本篇在系列中的位置
前面讲的是 Agent Runtime 内部机制,本篇把运行时接到外部入口。它解释为什么同一个核心 Agent 可以同时服务 CLI、消息平台和 Webhook;下一篇 Cron 会把入口从“人触发”扩展到“时间触发”。
贯穿案例
贯穿这个系列,可以一直带着同一个任务来读:用户说“帮我修复这个 repo 里的 failing tests”。不同章节会回答同一个任务在运行时的不同问题:入口怎样进入、上下文怎样准备、模型怎样决定下一步、工具怎样执行、状态怎样保存、失败后怎样恢复。
定义
Hermes Gateway 是一个长驻入口层。它把外部平台事件转成统一的 MessageEvent,交给 Agent Runtime 处理,再把文本、图片、文件或语音结果送回对应平台。
这里要区分两个容易混淆的词。Messaging Gateway 是本文讨论的入口层;Tool Gateway 是工具执行层,用来托管 web、image、TTS、browser 等工具后端。前者回答“用户从哪里进入 Hermes”,后者回答“工具在哪里执行”。
Adapter 和 Runner
Hermes 把 Gateway 拆成两部分。
Platform Adapter 处理平台差异:如何连接、如何发 typing、如何发送图片、如何解析 thread、如何处理按钮。BasePlatformAdapter 提供共享逻辑:active session guard、pending message、interrupt、typing heartbeat、media extraction、send retry。
GatewayRunner 处理 Hermes 语义:鉴权、slash command、session 创建、上下文恢复、调用 AIAgent、记录 transcript、处理审批和投递。

这个分层很重要。新增平台时,不应该把平台 SDK 细节散落到 Agent Loop 里;Agent 也不应该知道自己来自 Telegram 还是 Slack。
Gateway 事件流
Gateway 可以用一个具体事件流来理解。
| 阶段 | 输入 | Runtime 需要做什么 | 输出 |
|---|---|---|---|
| Receive | Telegram/Discord/Webhook 原始事件 | 解析平台、用户、频道、thread、附件 | 标准化 inbound message |
| Bind Session | platform + chat + thread + user | 找到或创建 session key | 可恢复的对话状态 |
| Run Agent | 标准化消息 + session | 调用同一个 AIAgent Runtime | assistant response / tool approval request |
| Deliver | response + platform target | 转成平台可发送格式 | 消息、媒体、审批按钮或错误提示 |
| Persist | session + delivery metadata | 保存历史和投递结果 | 下次可接续状态 |
这就是为什么 Gateway 不是“把消息转发给 CLI”。它必须把平台事件变成 Runtime 能理解的 session turn,再把 Runtime 输出变回平台语义。
Session Key
Gateway 的核心路由主键是 session key。它把 platform、chat_id、thread_id、user_id、chat_type 等信息压成一个稳定键,用来找到对应 session。
群聊、DM、thread、topic 的语义不同。一个群里是否按用户拆 session?一个 Discord thread 是否独立于 channel?Telegram topic 是否应该保留独立上下文?这些都不是 prompt 问题,而是 session key 和 runtime policy 问题。
打断与审批
聊天平台是异步入口。用户可能在 Agent 还在运行时又发一条消息,也可能发送 /stop,或者对危险命令回复 /approve。
Hermes 因此把某些命令设为 bypass path:/stop、/approve、/deny 不能被普通队列挡住。尤其是审批流,Agent thread 正在等待用户选择;如果 /approve 也被排队,就会死锁。
Delivery 不等于 Reply
普通回复是原路返回;delivery 是更通用的投递系统。Cron job、send_message、后台通知可以投递到 origin、home channel、指定 platform、指定 thread,甚至多个目标。
这个分离让 Hermes 可以同时支持“在当前聊天里继续对话”和“把计划任务结果发到另一个地方”。
可迁移伪实现:Gateway
下面的伪代码是机制抽象,不对应 Hermes 的真实 API 或文件结构。最小版本只需要四个对象:
type MessageEvent = { text: string; source: SessionSource }
type SessionSource = { platform: string; chatId: string; userId?: string; threadId?: string }
class MiniAdapter {
async onPlatformMessage(raw: unknown) {
const event = normalize(raw)
const key = buildSessionKey(event.source)
if (this.active.has(key) && !isBypassCommand(event.text)) {
this.pending.set(key, event)
this.interrupt(key)
return
}
const response = await this.runner.handleMessage(event)
await this.send(event.source.chatId, response)
}
}
class MiniGatewayRunner {
async handleMessage(event: MessageEvent) {
const key = buildSessionKey(event.source)
const history = await sessions.load(key)
if (event.text === "/new") return sessions.reset(key)
return agent.runConversation(event.text, history)
}
}
学习重点不是支持多少平台,而是把“平台事件”稳定地转成“Agent session”。
小结
Gateway 让 Hermes 具备常驻入口、多平台状态和外部投递能力。它的本质不是发消息,而是把异步世界里的聊天、按钮、文件、线程和时间事件,收束到同一个 Agent Runtime 中。
参考资料
- Hermes Agent Documentation: https://hermes-agent.nousresearch.com/docs
- Hermes Agent GitHub: https://github.com/NousResearch/hermes-agent