Hermes 101|11|Gateway

Gateway 让 Hermes 从命令行程序变成跨 Telegram、Discord、Slack、Webhook 等入口持续运行的 Agent。

Hermes 101|11|Gateway

Gateway 是 Hermes 从“终端里的 Agent”变成“到处都能用的 Agent”的关键层。没有 Gateway,Agent 只能等待 CLI 输入;有了 Gateway,同一个运行时可以接入 Telegram、Discord、Slack、Webhook、Email、SMS、Matrix 等平台。

Gateway 不是简单的 bot wrapper。它要处理平台连接、消息归一化、session key、权限、slash command、审批、打断、投递和长任务生命周期。

从聊天消息到 Agent 回复

读完本文,你应该能回答

  • 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、处理审批和投递。

Gateway 的三层边界

这个分层很重要。新增平台时,不应该把平台 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 中。

参考资料