文章类型技术长文 所属专栏Agent 网络通信 预计阅读53 分钟 文档状态已发布
返回

Hermes 多端消息网关——Telegram、微信与可靠投递

拆解 Hermes 如何统一 Telegram、微信、企业微信、Discord 与 Slack 的事件、会话、并发和可靠投递,构建长期在线的多端消息网关。

开始阅读全文10628 字 · 53 分钟 查看系列目录Agent 网络通信
关键词 AgentHermes消息网关Telegram可靠投递
栏目 AgentNetworking;专栏 Agent 网络通信;标签 Agent、Hermes、消息网关、Telegram、可靠投递

版本说明:本文依据 2026 年 8 月 6 日读取的 Nous Research 官方 hermes-agent 仓库、Hermes Messaging Gateway 文档,以及 Telegram、Discord、Slack 的官方平台协议文档撰写。Hermes 的网关实现仍在快速演进,平台数量、默认超时、重试次数和连接方式均属于版本化细节,生产使用时应再次核对当前版本。

本文范围:讨论 Hermes 如何把一个可以调用模型和工具的 Agent,变成长期在线、可从 Telegram、微信、企业微信、Discord、Slack 等移动或协作平台访问的消息服务。本文不重复模型 API 的 SSE/WebSocket 原理,也不重新讲 Agent Loop 内部推理,而是聚焦 平台接入、事件归一化、会话路由、并发控制、消息呈现和可靠投递

Claude Code、Codex 一类 Coding Agent 的核心问题,是如何在本地工作区中组织模型请求、工具调用和文件修改。Hermes 面对的额外问题则是:

  • 用户不一定坐在运行 Agent 的机器前;
  • 用户可能从多个聊天平台、群聊和线程进入;
  • 平台事件模型、身份标识、消息长度和富文本格式并不统一;
  • 一个 Agent Turn 可能运行几分钟,而平台回复令牌、Typing Indicator 或消息编辑接口有自己的时间约束;
  • Gateway 可能在模型已经完成任务后、平台确认消息送达前崩溃;
  • 平台可能重复推送入站事件,也可能在出站时返回 429、断开长连接或丢失 ACK;
  • 同一个 Gateway 要同时服务多个 Chat,同时又不能让同一个会话中的两次任务相互污染。

因此,Hermes 的重点不是再设计一种模型传输,而是在模型 Agent 外增加一层长期运行的 Messaging Gateway。这层 Gateway 把不可靠、异构的外部消息系统,转换成 Agent 可以稳定消费的会话和事件;再把 Agent 的流式输出,转换为各平台能够接受的消息、编辑、附件和确认流程。Hermes 官方文档将其描述为一个连接所有已配置平台、管理会话、运行 Cron 任务并负责消息交付的单一后台进程。1


1. Hermes 与 Claude Code、Codex 的网络拓扑差异#

Hermes 并不是用另一种方式实现 Claude Code 或 Codex。它更接近一个“把底层 Agent 服务化”的接入层。理解三种拓扑的区别,是理解后续架构的前提。

1.1 CLI Agent#

CLI Agent 的典型路径是:

用户终端
│ stdin / keyboard
本地 Agent 进程
├── 模型 API
├── 本地文件系统
├── Shell / 工具
└── stdout / TUI

它的几个基本假设是:

  1. 用户与 Agent 进程处在同一个交互生命周期中。 终端关闭、进程退出,交互通常就结束。

  2. 用户输入与输出通道天然一一对应。 当前 stdin 的输入,通常就回到当前 stdout。

  3. 身份问题相对简单。 操作系统用户、当前工作目录和本地配置往往已经构成主要信任边界。

  4. 审批可以同步进行。 Agent 请求执行危险命令时,可以直接在终端弹出确认。

  5. 结果交付通常不需要独立持久化。 模型完成后打印到终端即可;若进程在打印前崩溃,常被视为任务失败,而不是“任务完成但最后一公里未送达”。

CLI Agent 的网络层重点是模型链路和工具链路,而不是消息平台的路由与投递。

1.2 IDE Agent#

IDE Agent 通常拆成多个进程或组件:

Webview / Chat Panel
Extension Host
Agent Runtime
├── Model Provider
├── MCP / Tools
└── Workspace

与 CLI 相比,它多出几类状态:

  • Webview 与 Extension Host 的 IPC;
  • 编辑器窗口、Workspace、选中文件和 Diff 状态;
  • IDE 重载后的会话恢复;
  • UI 面板中的渐进式输出和审批交互;
  • 插件进程与本地工具进程的生命周期。

但 IDE Agent 仍然大多依附于用户当前打开的 IDE。它面对的主要是“本地交互界面”和“编辑器状态”问题,而不是长期在线的跨平台消息交付。

1.3 长期运行的消息 Gateway#

Hermes 的拓扑不同:

Gateway 拓扑改变了系统的基本约束:

  • 进程长期存在:即使没有用户输入,也要保持平台连接、执行 Cron、清理会话和处理恢复。
  • 多来源输入:同一进程接收多个平台、多个 Chat、多个线程和多个用户的事件。
  • 异步交互:用户可以在 Agent 执行过程中离线,任务仍然继续。
  • 身份与路由是第一等状态:每条消息必须知道来自哪个平台、哪个 Chat、哪个 Thread、哪个用户,最终也必须回到正确位置。
  • 执行完成与投递完成分离:Agent 生成了最终结果,不代表用户已经收到。
  • 平台能力不对称:有的平台支持编辑、Typing、Thread 和语音;有的平台只支持一次性发送文本。
  • 崩溃恢复必须跨进程:会话、待投递消息和恢复边界不能只放在内存里。

可以把三种架构的关注点概括为:

架构主要生命周期核心状态最难的网络问题
CLI Agent终端进程当前 Turn、工作区、工具状态模型流、工具出网、终端中断
IDE AgentIDE/Extension HostWorkspace、面板、会话、DiffIPC、编辑器重载、工具桥接
Hermes Gateway长期后台服务平台身份、Session、队列、投递义务多端接入、路由、限流、断线、可靠投递

Hermes 官方开发文档给出的统一链路是:

User ↔ Messaging Platform ↔ Platform Adapter ↔ Gateway Runner ↔ AIAgent

Adapter 负责适配外部平台,Gateway Runner 负责授权、会话和调度,AIAgent 负责实际推理与工具执行。2


2. Hermes Gateway 架构#

Hermes 多端消息网关总体网络拓扑

Hermes Gateway 的核心并不复杂:把平台差异压缩在 Adapter 中,把会话连续性放进 Session Store,把推理交给 AIAgent,把定时任务交给 Cron,把最终交付义务交给 Delivery Ledger。真正的工程难点,是让这五部分在断线、并发、重启和平台限制下仍保持一致。

2.1 Platform Adapter#

所有平台 Adapter 都继承统一的 BasePlatformAdapter,至少实现:

  • connect():建立长连接、启动 Long Poll、启动 HTTP Server,或初始化 SDK;
  • disconnect():清理连接和后台任务;
  • send():向目标 Chat 发送文本;
  • send_typing():可选,发送 Typing Indicator;
  • get_chat_info():可选,补充 Chat 元数据;
  • handle_message(event):把归一化事件交给 Gateway Runner。2

Adapter 的职责不是简单封装一个 SDK。它至少承担六类转换:

  1. 连接转换 把 Telegram Long Poll、Discord Gateway、Slack Socket Mode、WeCom WebSocket、Webhook 等不同接入方式统一为“收到 MessageEvent”。

  2. 身份转换 从平台原始事件中抽取 User ID、Chat ID、Thread ID、Message ID、Workspace/Guild Scope。

  3. 内容转换 把文本、图片、语音、文件、Reply、Mention 转成统一字段。

  4. 授权前置检查 某些平台会在 Adapter 层就检查用户或群组策略,减少未授权事件进入核心 Gateway。

  5. 输出格式转换 将统一的文本、Markdown、附件和 Thread 元数据翻译成平台 API 参数。

  6. 平台故障分类 区分网络瞬时失败、认证失败、限流、不可恢复配置错误和平台拒绝。

平台 Adapter 的设计目标是:上层 Gateway 不应知道 Telegram 的 message_thread_id、Slack 的 thread_ts 或 Weixin 的 context_token 如何工作,只需要处理统一的消息与交付结果。

2.2 Per-chat Session Store#

Hermes 使用 SessionSource 描述一条消息从哪里来,主要字段包括:

platform
chat_id
chat_type
user_id
thread_id
message_id
scope_id
parent_chat_id
profile

这些字段共同参与路由、隔离和上下文注入。3

Session Store 不直接把“用户 ID”当作唯一会话键,而是生成一个确定性的 Session Key:

agent:main:{platform}:{chat_type}[:{chat_id}][:{thread_id}][:{participant_id}]

这样做有几个直接效果:

  • Telegram 私聊 A 与私聊 B 不共享上下文;
  • 同一群组中的不同 Topic 可以独立;
  • Discord Guild、Slack Workspace 可以通过 scope_id 形成更高层隔离;
  • 同一个用户在 Telegram 与微信上的对话默认不合并;
  • 同一 Chat 内是否按用户隔离,可以通过策略控制;
  • Gateway 重启后,可以根据 Session Key 找回原有 Session ID 和 Transcript。

Hermes 的 Session Store 同时维护:

  • Session Key 到 Session ID 的映射;
  • 会话起止时间和最近活跃时间;
  • 消息 Transcript;
  • Token 和成本统计;
  • suspendedresume_pending 等恢复标记;
  • 会话来源和结果投递路由。

官方 Session Lifecycle 文档指出,SQLite 是消息 Transcript 的规范存储,sessions.json 保留 Session Key 到 Session ID 的映射和元数据;当会话因重启中断时,resume_pending 可以保留原 Session ID,而 /stop 或强制挂起产生的 suspended 则会在下次访问时强制创建新会话。4

2.3 AIAgent Runtime#

Gateway 本身不负责推理。它在确认授权、找到 Session、准备平台上下文后,把消息交给 AIAgent。

AIAgent Runtime 仍然负责传统 Agent 的工作:

  • 构造模型上下文;
  • 调用模型;
  • 解析 Tool Call;
  • 执行 Shell、GitHub、MCP 等工具;
  • 回填 Tool Result;
  • 多轮迭代;
  • 生成最终答案。

Gateway 为 Runtime 增加的是外部上下文:

  • 当前平台;
  • Chat 类型;
  • 用户与群组信息;
  • Thread 信息;
  • 可用的发送渠道;
  • 当前 Session ID;
  • Approval Channel;
  • 平台能力,例如能否编辑消息、发送语音或显示按钮。

这使模型不仅知道“任务是什么”,也知道“结果最终要交付到什么地方”。

2.4 Cron Scheduler#

Gateway 不只响应用户输入,还要运行定时任务。官方架构文档显示,Cron Scheduler 与 Session Store 和 Adapter 共享 Gateway 进程,按固定周期检查到期任务,并将结果投递到配置的 Home Channel。Messaging Gateway 文档当前描述的默认 Tick 周期是 60 秒。1

Cron 带来一个重要语义差异:

  • 普通消息是“用户输入驱动”;
  • Cron 是“时间驱动”;
  • 后台任务是“任务完成驱动”。

三者最终都通过 Delivery 层发消息,但不应强行写入同一个用户聊天 Transcript。Hermes 明确规定,Cron 交付保留在自己的 Cron Session 中,不镜像回普通 Gateway 会话,以避免对话角色交替被破坏。5

2.5 Delivery Ledger#

Delivery Ledger 是 Hermes 区别于普通 Bot Demo 的关键部件。

当 Agent 已经生成最终回复时,这段回复可能只存在于 Python 局部变量中。若 Gateway 在以下时刻崩溃:

Agent 完成
开始调用平台 send()
平台已收到,但 ACK 尚未返回
Gateway 崩溃

系统无法知道用户是否已收到消息。若不持久化:

  • 不重发:可能永久丢失结果;
  • 直接重发:可能产生静默重复;
  • 重跑 Agent:可能重复修改仓库、重复创建 PR、再次消耗模型 Token。

Hermes 因此把最终回复先记录为一个持久化“交付义务”,再执行平台发送。具体状态为:

pending → attempting → delivered
↘ failed
↘ abandoned

这部分将在第 9 节深入分析。


3. 不同平台的入站连接方式#

不同消息平台的“消息到达”并不是一个统一协议。Gateway 的第一层复杂性,就是维护多个不同生命周期的入站连接。

3.1 Telegram Bot API#

Telegram Bot API 基于 HTTPS。Bot 接收更新有两种互斥方式:

  • getUpdates Long Poll;
  • setWebhook 注册的 HTTPS Webhook。

同一个 Bot 不能同时使用两者。Telegram 使用单调递增的 update_id 帮助客户端确认和去重;未获取的 Update 最多在服务端保留 24 小时。6

Hermes 的 Telegram Adapter 基于 python-telegram-bot,负责:

  • 接收私聊、群聊、Topic 和命令;
  • 发送文本、图片、文件和语音;
  • Typing Indicator;
  • 消息编辑和渐进式输出;
  • Reply 与 Thread 路由;
  • 网络超时、初始化失败和代理处理。7

Telegram 接入的工程重点不是 HTTP 请求本身,而是:

  1. Long Poll 连接需要持续续期;
  2. update_id 必须去重;
  3. Bot Token 缺失属于不可恢复配置错误,网络抖动属于可恢复错误;
  4. Topic 和私聊 Topic 的 Reply Anchor 语义不同;
  5. 文本长度按 UTF-16 Code Unit 计算,而不是 Python 字符数;
  6. 流式编辑过快会触发 Flood Control;
  7. Telegram 返回的文件 URL 会过期,需要及时下载到本地缓存。

3.2 Weixin 与 WeCom#

“微信”在 Hermes 中对应两条不同链路。

Hermes 的 Weixin Adapter 通过腾讯 iLink Bot API 接入个人微信,设计特点是:

  • 使用 getupdates Long Poll 接收入站消息;
  • 每次出站回复必须带上该 Peer 最新的 context_token
  • 图片、文件和语音通过 AES-128-ECB 加密的 CDN 协议传输;
  • 支持 QR 登录;
  • 有独立的 Typing API;
  • 对频率限制、Session 过期和重复消息分别处理。8

这里的 context_token 类似平台侧对话续接凭证。它不是 Hermes Session ID,而是微信平台要求的回复上下文。Gateway 必须把它按 Account 和 Peer 持久化,否则重启后无法正确回复。

WeCom:企业微信 AI Bot WebSocket#

WeCom Adapter 使用持续 WebSocket:

  • aibot_subscribe 完成认证;
  • aibot_msg_callback 接收入站消息;
  • aibot_send_msg 发送 Markdown;
  • 媒体通过分块上传命令发送;
  • 30 秒心跳;
  • [2, 5, 10, 30, 60] 秒级退避重连;
  • 通过 req_id 关联请求与响应;
  • 支持 DM、Group 的 Pairing/Allowlist 策略。9

Weixin 与 WeCom 虽然都叫“微信”,但网络形态完全不同:

维度WeixinWeCom
入站方式Long Poll持久 WebSocket
回复关联context_tokenWebSocket req_id/Callback
媒体加密 CDN分块上传 API
连接恢复Long Poll 重建Heartbeat + Backoff Reconnect
消息编辑不支持渐进编辑当前 Adapter 不支持编辑
典型身份个人账号 Peer企业用户/群组

3.3 Discord Gateway#

Discord Gateway 是持久 WebSocket 协议。客户端建立连接后:

  1. 服务端发送 Hello,包含 Heartbeat Interval;
  2. 客户端按周期发送 Heartbeat;
  3. 服务端回 Heartbeat ACK
  4. 事件带序列号 s
  5. 断线后可通过 session_idresume_gateway_url 和最后序列号发送 Resume
  6. 服务端可能重放断线期间遗漏的事件。10

Hermes 的 Discord Adapter 使用 discord.py,额外处理:

  • Guild、Channel、Thread 和 DM;
  • Slash Command;
  • 自动 Thread;
  • Mention 与 Reply;
  • Message Component 字段的 UTF-16 长度;
  • 附件下载和 SSRF Redirect Guard;
  • 启动 Ready 超时;
  • WebSocket 关闭超时后的底层 Transport Abort。11

Discord 的恢复重点是:重连不等于新会话。Gateway 可以恢复平台 WebSocket,而 Hermes Session 仍然保持不变。

3.4 Slack Socket Mode#

Slack Socket Mode 允许 App 主动建立 WebSocket,而无需公开 Webhook Endpoint。连接 URL 通过 apps.connections.open 获取,Slack 会定期要求刷新连接。每个事件包含 envelope_id,客户端必须 ACK;Slack 支持同时维护多条 Socket 连接以提高可用性。12

Hermes 的 Slack Adapter 使用 slack-boltAsyncSocketModeHandler,支持:

  • Channel 和 DM;
  • Slash Command;
  • Thread;
  • Workspace 隔离;
  • Web API 出站发送;
  • Thread Context 获取;
  • Markdown 到 Slack mrkdwn 的转换;
  • 多用户并发 Slash Command 的 ContextVar 隔离。13

Slack 的关键路由字段不只有 Channel ID,还包括:

  • Team/Workspace ID;
  • Thread TS;
  • User ID;
  • Envelope ID;
  • Slash Command 的 response_url

其中 Workspace ID 属于持久路由状态,必须跨流式、异步和恢复边界保留,不能在发送时退回“默认 Workspace”。

3.5 Webhook#

Webhook 模式中,平台通过 HTTP POST 把事件推给 Gateway。正确实现至少要处理:

  • Endpoint 身份验证;
  • 签名校验;
  • 时间戳和重放窗口;
  • Event ID 去重;
  • 快速 ACK;
  • 异步执行长任务;
  • 出站 API 与入站 Webhook 分离;
  • 重试造成的重复事件。

Webhook 的核心语义是:

平台收到 2xx,只代表 Gateway 接收了事件,不代表 Agent 已完成任务。

因此 Webhook Handler 不应同步等待几分钟的 Agent Turn。更稳妥的做法是:

  1. 验证并持久化入站事件;
  2. 立即返回 ACK;
  3. 在后台交给 Session Queue;
  4. 最终通过独立出站 API 交付结果。

3.6 轮询型平台#

Long Poll 的抽象流程是:

cursor = last_confirmed_cursor
loop:
events, next_cursor = poll(cursor, timeout)
for event in events:
if not deduplicated(event.id):
normalize_and_dispatch(event)
persist(next_cursor)

可靠的轮询 Adapter 需要:

  • 持久化 Cursor/Offset;
  • 允许重复获取同一批事件;
  • 使用 Event ID 去重;
  • 对空结果正常续轮;
  • 对超时快速重建请求;
  • 对 429 和服务端错误使用退避;
  • 避免并发启动两条 Poll Loop;
  • 在断开时关闭旧连接池。

Weixin 和 Telegram Long Poll 都属于这种模式,但它们的 Cursor、Session 和回复约束不同,不能共享平台实现,只能共享抽象原则。


4. 平台事件归一化#

平台事件归一化与会话路由

Gateway 不可能让 AIAgent 理解每个平台的原始 Payload。它需要一个稳定的 Normalized Message Contract。

一个简化后的统一事件可以表示为:

{
"platform": "telegram",
"scope_id": null,
"user_id": "123456",
"user_name": "alice",
"chat_id": "-100987654",
"chat_type": "group",
"parent_chat_id": null,
"thread_id": "42",
"message_id": "991",
"reply_to_message_id": "950",
"platform_update_id": "834921",
"text": "请修复仓库中的支付超时问题",
"media": [
{
"kind": "image",
"local_path": "/.../cache/images/img_xxx.png",
"mime": "image/png"
}
],
"raw_message": {}
}

真正的字段会因平台能力而增加,但核心思想不变:平台专有事件先被压缩为稳定的路由字段、内容字段和可审计元数据。

4.1 User ID#

User ID 的用途不只是显示用户名,还包括:

  • Allowlist;
  • DM Pairing;
  • Per-user Session Isolation;
  • 审批权限;
  • 审计;
  • 消息归属;
  • 防止 Bot/Webhook 冒充用户。

需要注意:

  1. 不同平台的 User ID 不可直接比较;
  2. 某些平台同时有短期 ID 和稳定 Alt ID;
  3. 显示名称不是身份标识;
  4. 日志中应按平台策略做哈希或脱敏;
  5. 跨平台“同一个人”不能凭昵称自动合并。

Hermes 的 SessionSource 同时保留 user_iduser_id_alt,就是为了处理 Signal UUID、Feishu Union ID 等稳定身份差异。3

4.2 Chat ID#

Chat ID 表示交付目的地,是最重要的路由字段。

它可能代表:

  • Telegram 私聊;
  • Telegram Group;
  • Discord Channel;
  • Slack Channel;
  • Weixin Peer;
  • WeCom Group;
  • Webhook 的逻辑 Channel。

Chat ID 必须带 Platform Namespace 使用。裸字符串 12345 不足以构成全局唯一键:

telegram:dm:12345
discord:channel:12345
weixin:dm:12345

4.3 Thread ID#

Thread ID 把一个 Chat 进一步拆成多条会话 Lane。

典型例子:

  • Telegram Forum Topic;
  • Telegram DM Topic;
  • Discord Thread;
  • Slack thread_ts
  • Feishu Thread。

Thread ID 的关键不是“显示在哪个 UI 里”,而是决定:

  • Session 是否独立;
  • 回复发送到哪里;
  • 是否继承父 Channel 的 Model/System Prompt Override;
  • /stop 应取消当前 Thread 还是整个 Chat;
  • 结果恢复时回到哪个 Lane。

Hermes 默认让 Group Session 按用户隔离,而 Thread 默认共享;这些规则可配置。4

4.4 Message ID#

Message ID 用于:

  • Reply Anchor;
  • 去重;
  • 编辑;
  • Reaction;
  • Pin;
  • 生成 Delivery Obligation ID;
  • 区分同一 Session 中的不同 Turn。

入站事件可能被平台重复推送,因此不能把“成功收到一次 HTTP 请求”当作唯一性保证。Adapter 应对 Message ID、Update ID 或 Envelope ID 建立去重窗口。

4.5 Reply Anchor#

“回复这条消息”在不同平台里并不是同一件事:

  • Telegram 普通群聊可使用 Reply ID;
  • Telegram Forum Topic 更依赖 message_thread_id
  • Telegram 私聊 Topic 可能同时需要 Topic ID 和触发消息 ID;
  • Slack Thread 使用 Thread TS;
  • Discord Thread 通常通过 Channel/Thread 本身路由;
  • 某些跨频道 Handoff 必须刻意发送为顶层消息,不能误用 Reply Anchor。

Hermes 的 Base Adapter 专门生成平台感知的 Thread Metadata,并对 Telegram 私聊 Topic、群组 Topic、Slack Thread Handoff 等路径做不同处理。14

这说明 Reply Anchor 不是 UI 装饰,而是交付地址的一部分

4.6 文件、图片和语音#

媒体归一化通常包含两步:

  1. Adapter 从平台下载内容;
  2. Gateway 缓存为本地文件,让 Agent Tool 使用本地路径。

这样做的原因包括:

  • 平台文件 URL 会过期;
  • 后续 Vision/STT Tool 需要本地文件;
  • 重试时不应再次依赖平台临时 URL;
  • 需要统一做大小限制和 MIME 校验;
  • 需要防止 SSRF 和恶意 Redirect;
  • 需要在不同平台格式之间转换。

Hermes Base Adapter 对入站媒体设置统一大小上限,默认 128 MiB,可配置;还会校验图片 Magic Bytes、限制下载流量,并对 Redirect 重新执行 SSRF 检查。15

语音处理还要处理容器格式差异。例如 Telegram 的普通 Audio 与 Voice Bubble 支持的格式不同;Hermes 会根据平台和文件扩展名决定使用原生 Voice、Audio 还是普通 Document。


5. 会话和身份路由#

事件归一化完成后,Gateway 需要回答两个问题:

  1. 这条消息是否有权进入 Agent?
  2. 它属于哪一个持续会话?

5.1 一个 Chat 对应一个 Session#

最简单的规则是:

Session Key = Platform + Chat Type + Chat ID

这保证一个 Chat 的多条消息共享上下文,同时不同 Chat 隔离。

但真实平台还需要 Thread、Participant、Scope 和 Profile:

agent:main:{profile}:{platform}:{scope}:{chat_type}:{chat_id}:{thread_id}:{participant_id}

其中有些字段按需省略。Hermes 官方实现使用确定性 Key,而不是每次创建随机会话;这样 Gateway 重启后仍能重新找到原 Session。4

5.2 群聊和私聊隔离#

私聊通常天然一人一会话:

telegram:dm:alice
telegram:dm:bob

群聊则有两种策略:

共享群会话#

所有人共享上下文:

telegram:group:team-chat

优点是多人协作自然;风险是:

  • 一个用户看到另一个用户的上下文;
  • 权限边界复杂;
  • Prompt Injection 和身份混淆风险高;
  • 费用和任务归属不清晰。

群内按用户隔离#

在 Key 中增加 Participant:

telegram:group:team-chat:user-alice
telegram:group:team-chat:user-bob

Hermes 默认对 Group/Channel 使用 Per-user Isolation,以降低上下文串扰;Thread 则默认共享,以便一个 Topic 中多人协作。4

5.3 Thread 继承#

Thread 既要独立,又需要继承父级策略。

可继承的内容包括:

  • 平台与 Workspace Scope;
  • 父 Channel Allowlist;
  • Channel Model Override;
  • System Prompt Override;
  • Home Channel;
  • 代理与凭证;
  • 格式和消息长度能力。

不应继承的内容包括:

  • 父 Channel 的完整对话 Transcript;
  • 其他 Thread 的 Pending Tool State;
  • 其他 Thread 的 /stop 信号;
  • 其他 Thread 的 Delivery ID。

合理的路由方式是:

父级提供策略
Thread 提供会话边界
Message 提供当前 Turn Anchor

5.4 用户权限和 Allowlist#

Hermes Gateway 默认拒绝未在 Allowlist 中、也未完成 DM Pairing 的用户。官方授权顺序为:

  1. 平台级 Allow-all;
  2. 平台 Allowlist;
  3. DM Pairing;
  4. 全局 Allow-all;
  5. 默认拒绝。5

这是必要的,因为 Gateway 背后的 Agent 可能拥有:

  • Shell;
  • GitHub;
  • 文件系统;
  • 浏览器;
  • 企业 API;
  • 通知能力。

一个普通聊天 Bot 的未授权消息最多造成垃圾回复;一个带 Shell 的 Agent 则可能执行真实副作用。

权限判断至少应绑定:

Platform
Scope / Workspace / Guild
Chat
User
Role
Command
Tool Risk

Hermes 还提供 /approve/deny,让危险工具的审批可以在移动端完成。审批命令需要绕过“当前 Agent 正在运行”的普通消息 Guard,直接到达等待中的权限请求,否则会形成死锁。5

5.5 跨平台会话边界#

默认情况下,Telegram 上的 Alice 与微信上的 Alice 是两个不同身份、两个不同 Session。

跨平台自动合并会产生严重问题:

  • 昵称可能相同但不是同一个人;
  • 一个平台的认证强度可能低于另一个;
  • 群聊上下文可能被带入私聊;
  • Delivery 路由可能错发;
  • 审批权限可能被跨平台提升。

因此,跨平台会话合并必须是显式能力,至少需要:

  • 账户绑定;
  • 双向验证;
  • 明确的 Canonical User ID;
  • 可撤销映射;
  • 审计日志;
  • 来源标记;
  • 权限取交集而非并集。

Hermes 支持跨平台发送,但这不等于跨平台会话自动合并。Cross-platform Delivery 应被视为一个显式工具动作,而不是隐式 Session 共享。5


6. 将模型流转换成移动端消息#

模型流到移动端消息的转换流程

模型输出通常是毫秒到秒级的增量 Token 流,而消息平台需要的是离散的 API 调用。Gateway 需要在二者之间增加一个 Stream Consumer。

Hermes 的流式桥接大致如下:

Hermes 的 GatewayStreamConsumer 将同步回调转为异步 Queue,再进行缓冲、限频和渐进式编辑。16

6.1 Typing Indicator#

Typing Indicator 的目的不是装饰,而是填补 Time to First Visible Feedback。

一个完整回复可能经历:

用户发送消息
→ Gateway 路由
→ Agent 构造上下文
→ 模型首 Token
→ 多轮工具执行
→ 最终回复

若没有 Typing Indicator,用户会把几秒到几分钟的等待误认为 Bot 离线。

但 Typing Indicator 也有生命周期:

  • 周期刷新;
  • Turn 完成后停止;
  • /stop 后立即停止;
  • Gateway 重启后不恢复旧 Indicator;
  • 平台不支持时静默降级;
  • 不应因 Typing 失败影响主任务。

Weixin 有专门的 sendtyping API;Telegram、Discord、Slack 也有各自的 Typing/Presence 机制,但支持范围和持续时间不同。

6.2 Progressive Message Edit#

支持消息编辑的平台,可以先发送一条 Preview,再逐步编辑:

send("正在分析… ▌") → message_id
edit(message_id, "已读取仓库… ▌")
edit(message_id, "发现问题位于… ▌")
edit(message_id, "最终答案")

相比连续发送几十条消息,这种方式:

  • 减少消息噪声;
  • 保持上下文集中;
  • 更接近 CLI 流式体验;
  • 降低通知数量。

但必须控制编辑频率。Hermes Stream Consumer 使用:

  • Edit Interval;
  • Buffer Threshold;
  • Cursor;
  • Flood-control Strikes;
  • Adaptive Backoff;
  • Final Fresh Message;
  • Draft/Edit/Off Transport 切换。16

当连续编辑多次触发 Flood Control 时,系统会停止渐进编辑,保留最终发送,避免为了“看起来实时”而丢失真正结果。

6.3 文本分段#

消息平台有长度限制。Gateway 不能简单用 text[:limit] 截断,因为会破坏:

  • Unicode;
  • Markdown;
  • Code Fence;
  • 表格;
  • 链接;
  • Tool Result;
  • 语义段落。

更合理的分段顺序是:

  1. 按完整消息块;
  2. 按段落;
  3. 按换行;
  4. 按句子;
  5. 按空格;
  6. 最后按平台字符计量单位硬切。

分段还需要记录 Continuation Message ID。若流式输出超过单条消息上限,后续 Edit 不能继续编辑第一条,而要创建新的消息段,并确保 Finalization 不重复发送已经展示的前缀。

6.4 平台长度限制#

不同平台的“字符数”定义也不同。

例如 Telegram 的单条文本上限是 4096 个 UTF-16 Code Unit。一个 Emoji 或某些扩展汉字可能占两个 Unit,因此 Python len(text) 不能直接作为上限。Hermes 专门实现 UTF-16 长度和安全前缀切割。14

WeCom Adapter 当前设置 4000 字符上限,并会对接近上限的连续输入进行批处理,以识别客户端拆分。9

因此,Gateway 的统一输出接口应提供:

adapter.measure_text(text)
adapter.max_message_length
adapter.split_message(text)

而不是在核心层写死一个通用数字。

6.5 Markdown 转换#

模型输出通常接近 CommonMark/GFM,但平台格式并不兼容:

  • Telegram MarkdownV2 有严格转义;
  • Discord 支持一部分 Markdown;
  • Slack 使用 mrkdwn,不原生渲染 GFM Table;
  • WeCom 使用自己的 Markdown 子集;
  • Weixin 可能需要转换为纯文本块;
  • Code Fence 未闭合会污染整个后续消息。

Hermes 的 Slack Adapter 会识别 GFM Pipe Table,把表格转成等宽 Code Block,并按 CJK 显示宽度补齐列;Stream Consumer 会在最终发送前补齐未闭合的三反引号和 Inline Backtick,防止整条消息渲染错误。1316

Markdown 转换的正确位置是 Platform Adapter 或 Render Layer,而不是修改 Agent 原始答案。这样可以同时保留:

  • 原始语义内容;
  • 平台渲染结果;
  • Delivery Ledger 中可重放的规范内容;
  • 审计和故障定位能力。

6.6 图片、文件和语音回复#

多模态交付至少包含三类路径:

图片#

  • 本地生成图片;
  • Tool 下载图片;
  • 模型返回图片 URL;
  • 平台原生图片消息;
  • 多图批量发送。

文件#

  • 代码补丁;
  • 报告;
  • 日志;
  • ZIP;
  • PDF;
  • Tool Result 落盘文件。

语音#

  • 用户语音转写;
  • Agent 文本 TTS;
  • 平台原生 Voice Bubble;
  • Discord Voice Channel;
  • 流式 TTS。

Adapter 必须知道平台格式能力。例如 Telegram 普通 Audio 仅支持特定附件格式,而 Voice Bubble 常要求 OGG/Opus;Hermes 会根据平台构造不同输出扩展名和发送路径。14

媒体交付不能只记录“文本已发送”。Delivery Result 至少应包含:

{
"success": true,
"message_id": "...",
"media_ids": ["..."],
"platform": "telegram",
"chat_id": "...",
"thread_id": "...",
"delivered_at": "..."
}

7. 每个 Chat 的并发控制#

每个 Chat 的并发控制与会话执行模型

消息 Gateway 同时面对两种相反需求:

  • 不同 Chat 要并行,否则一个长任务会阻塞所有用户;
  • 同一 Session 要有序,否则两条消息会竞争同一个 Transcript 和工具状态。

7.1 同一会话串行#

Hermes 使用两级 Running-agent Guard:

  1. Base Adapter 在事件进入 Gateway Runner 前检查 Session 是否活跃;
  2. Gateway Runner 再检查当前 Session 是否已有 Agent 在运行。5

当同一 Session 正在运行时,普通消息可以:

  • 入队;
  • 触发 Interrupt;
  • /queue 显式排队;
  • 根据 Busy Mode 合并;
  • 等待当前 Turn 结束。

不能直接启动第二个 AIAgent 共用同一 Transcript,否则会出现:

Turn A 读取历史 H
Turn B 也读取历史 H
Turn A 写入 Tool Result A
Turn B 写入 Tool Result B
最终历史顺序不确定

这会破坏模型上下文、工具副作用和消息角色交替。

7.2 不同会话并行#

不同 Session 可以并行运行,但要受全局上限约束:

max_concurrent_sessions

当活跃 Session 达到上限时,新 Session 会收到明确的容量提示,而 /status 等轻量控制命令仍可绕过限制。Hermes 的测试明确验证了这一行为。17

合理的并发策略应至少区分:

  • 活跃前台 Session;
  • 后台任务;
  • Cron;
  • 消息投递;
  • Typing Refresh;
  • Platform Reconnect;
  • Media Download。

不能用一个 Semaphore 把所有任务粗暴锁死,否则长 Agent Turn 可能阻止平台心跳,最终造成全局断线。

7.3 /stop 与任务取消#

/stop 不是普通消息。它必须:

  1. 绕过当前 Session 的排队;
  2. 找到正在运行的 AIAgent;
  3. 触发 Cancellation/Interrupt;
  4. 终止或标记当前工具;
  5. 停止流式编辑和 Typing;
  6. 清理 Pending Approval;
  7. 更新 Session 状态;
  8. 防止旧的异步 Delta 在新 Session 中继续发送。

Gateway Runner 对 /stop/approve/deny 等命令执行 Inline Dispatch,就是为了避免它们被正在等待的 Agent 自己阻塞。5

取消的正确语义是:

停止当前执行,不等于删除历史;停止旧 Turn,也不允许旧回调继续污染新 Turn。

7.4 后台任务#

/background <prompt> 把任务放入独立后台 Session,而不是继续占用当前 Chat 的前台 Turn。

后台任务需要独立维护:

  • Session ID;
  • Cancel Token;
  • Tool Process;
  • Completion Notification;
  • Result Delivery Target;
  • 资源预算;
  • 重启后的处理策略。

若后台任务与当前 Chat 共享同一 Session,后续用户输入会与后台 Tool Result 交错,造成上下文污染。

7.5 Gateway 重启后的 Session 恢复#

Gateway 重启后有两类状态:

Session Continuity#

Transcript 和 Session Metadata 仍存在。resume_pending 表示上次执行因进程重启或 Drain Timeout 中断,下次访问应保留原 Session ID。4

In-flight Execution#

旧进程中的 Python Task、模型连接和 Shell Process 不一定仍存在。不能把“Session 可恢复”误解成“任意 Tool Call 可原地恢复”。

Hermes 的恢复策略区分:

  • resume_pending:软恢复,同一个 Session 继续;
  • suspended:硬中止,下次访问创建新 Session;
  • Delivery Ledger:恢复已经生成、尚未确认交付的最终回复;
  • Gateway Health Watchdog:发现进程或 Event Loop 停滞后由系统管理器重启。

Session 恢复解决“上下文连续性”,Delivery Ledger 解决“最终结果不丢失”,两者不能互相替代。


8. 平台断线和限流#

8.1 长连接断开#

不同平台的长连接恢复机制不同:

Discord#

  • Heartbeat;
  • Heartbeat ACK;
  • Sequence;
  • Session ID;
  • Resume Gateway URL;
  • Resume 重放遗漏事件。10

Slack Socket Mode#

  • WebSocket URL 临时获取;
  • Envelope ACK;
  • Refresh Requested;
  • 多连接冗余;
  • 连接轮换。12

WeCom#

  • 30 秒 Heartbeat;
  • 连接与请求超时;
  • 指数式 Backoff;
  • 重新 Subscribe 和认证;
  • Pending Response 失败清理。9

Weixin#

  • Long Poll 超时后重建;
  • 连续失败计数;
  • 短重试与长 Backoff;
  • Session Expired 单独处理;
  • Context Token 恢复。8

Gateway 应把平台连接状态与 Agent Session 分离:

Platform Connection: disconnected / connecting / ready
Agent Session: active / idle / resume_pending / suspended
Delivery: pending / attempting / delivered / failed

平台断线时,不应删除所有 Session;只需要暂停或排队出站发送,并在连接恢复后继续。

8.2 Webhook 重复事件#

Webhook 重复是正常现象,不是异常。

平台可能因为:

  • ACK 超时;
  • 网络抖动;
  • 服务端重试;
  • Gateway 重启;
  • 负载均衡切换;

再次发送同一事件。

正确的入站去重键通常是:

Platform + Scope + Event ID

如果平台没有 Event ID,可退化到:

Platform + Chat ID + Message ID + Event Type

去重记录必须有 TTL,避免无限增长;但 TTL 不能短到平台正常重试窗口内失效。Weixin 当前使用 300 秒去重 TTL;WeCom 使用有界 Message Deduplicator。89

8.3 平台 API 429#

平台 429 与模型 429 类似,但恢复动作不同:

  • Telegram Flood Control 可能要求等待指定秒数;
  • Discord/Slack API 有 Method/Route/Workspace 级限流;
  • Weixin 有平台特定 Rate-limit Error Code;
  • 媒体上传与普通文本可能使用不同 Bucket;
  • 编辑消息与发送新消息可能分别限流。

Adapter 应把平台错误规范化为:

retryable
retry_after
scope
operation
platform_error_code
message

Outbound Queue 再按这些字段安排重试。不能在每个发送调用里各自 sleep(),否则同一 Chat 的多个消息会乱序。

8.4 消息编辑频率限制#

模型可能每几十毫秒产生一个 Delta,但平台不允许同样频率的 Edit。

Gateway 需要把 Token 流重整为较低频率的 UI 更新:

高频 Token Delta
字符阈值 + 时间阈值
每 0.x ~数秒一次 Edit
遇到 429 增大间隔
连续失败后关闭 Progressive Edit
最终消息必须保留

Hermes Stream Consumer 记录 Flood Strike,并动态提高 Edit Interval;达到阈值后禁用本轮渐进编辑,转而保证最终发送。16

这里的优先级应始终是:

最终结果送达 > 渐进展示完整 > 实时性

8.5 代理和网络切换#

Gateway 是长期进程,网络环境可能变化:

  • 企业代理切换;
  • VPN 启停;
  • DNS 污染;
  • 代理凭证过期;
  • NO_PROXY 改变;
  • SOCKS 连接重建;
  • TLS CA 更新;
  • 平台域名解析变化。

Hermes Base Adapter 的代理解析顺序为:

  1. 平台专用代理变量;
  2. HTTPS_PROXY / HTTP_PROXY / ALL_PROXY
  3. macOS 系统代理;
  4. NO_PROXY 匹配则绕过代理。18

SOCKS 路径使用 Remote DNS,以避免本地 DNS 污染。网络切换后的正确处理不是重启所有 Agent Session,而是:

  • 重建 Adapter 连接;
  • 清理旧连接池;
  • 保留 Session Store;
  • 保留 Delivery Ledger;
  • 对未确认出站消息进入恢复流程。

9. Durable Delivery Ledger#

Durable Delivery Ledger 与恢复投递状态机

Delivery Ledger 解决的问题非常具体:

Agent 已经生成了最终答案,但 Gateway 还不能证明平台已经收到。

9.1 回复发送前持久化#

在任何平台 Send 之前,Gateway 先写入:

obligation_id
session_key
platform
chat_id
thread_id
content
state = pending
attempts = 0
owner_pid
owner_started_at
created_at

obligation_id 由:

session_key + inbound message_ref + content

哈希生成。同一个 Turn、同一内容重复记录,会得到同一个 ID,从而具备记录层幂等性。19

关键顺序是:

先 record_obligation()
再调用平台 send()

如果反过来,进程可能在 Send 成功后、写 Ledger 前崩溃,恢复系统将完全不知道这条回复存在。

9.2 平台确认后完成#

完整状态迁移是:

delivered 只能在 Adapter 返回明确成功后设置。HTTP 请求发出、Socket Write 成功、平台返回 2xx,分别代表不同层次,Adapter 必须把“平台已接受”转成统一 SendResult.success

9.3 Gateway 崩溃后的重新投递#

Gateway 启动时执行 sweep_recoverable()

  1. 扫描 pendingattemptingfailed
  2. 检查原 Owner PID 和进程启动时间;
  3. 若原进程仍存活,不抢占;
  4. 若原进程已死亡,原子 Claim;
  5. Attempts 加一;
  6. 仅 Claim 当前启动中有可用 Adapter 的平台;
  7. 返回待重投记录;
  8. Adapter 重新发送。19

这种 Owner Stamp 不只检查 PID,还检查 Process Start Time,避免操作系统复用 PID 后误判旧进程仍存活。

9.4 At-least-once 语义#

Hermes 选择的是“诚实的 At-least-once”:

pending#

Send 尚未开始,可以直接重投,没有重复风险。

attempting#

崩溃发生在等待平台响应期间。消息可能已经送达,也可能没有送达。恢复时必须重投,但可能重复。

failed#

平台明确拒绝过一次。重启是新的重试边界,可以再次尝试,但仍带恢复标记。

delivered#

已确认,不再处理。

Exactly-once 需要平台提供端到端幂等键和可查询交付状态。多数消息平台不提供这种统一能力,因此 Gateway 不应假装具备 Exactly-once。

9.5 可能重复时显式提示#

对于 attemptingfailed,Hermes 在重投内容前加可见前缀:

♻️ Recovered reply — the gateway restarted during delivery,
so this may be a duplicate:

这比静默重复更诚实。

分布式系统中的不确定性无法总被消除,但可以被:

  • 记录;
  • 限定;
  • 暴露;
  • 审计。

Hermes 当前限制:

  • 最多 3 次重投;
  • 最长 24 小时有效;
  • Delivered/Abandoned 记录保留 7 天;
  • 总行数有上限。19

9.6 为什么不重新运行整个 Agent Turn#

重新运行整个 Agent Turn 是最危险的恢复方式,因为原 Turn 可能已经:

  • 修改文件;
  • Push Branch;
  • 创建 Pull Request;
  • 发送外部通知;
  • 调用付费 API;
  • 写数据库;
  • 消耗大量 Token。

Delivery Ledger 已经保存最终文本。恢复的目标只是“把这段结果交付给用户”,不是“重新计算一次结果”。

正确边界是:

Agent Execution State Delivery State
已完成 未确认

因此恢复动作应是:

重投最终消息

而不是:

重新执行用户任务

Delivery Ledger 本质上把“计算结果”和“结果交付”拆成两个独立事务。


10. Telegram 或微信端完整案例#

下面用一个统一任务串起所有组件:

用户从 Telegram 或微信发送:“修复仓库中的支付超时问题,运行测试并创建 Pull Request。”

具体到 Telegram:

  1. Adapter 通过 Bot API 收到 Update;
  2. 使用 update_id 和 Message ID 去重;
  3. 若是 Topic,生成 Thread Metadata;
  4. 发送 Typing;
  5. Agent 运行时,Stream Consumer 可以编辑 Preview;
  6. 最终回复进入 Delivery Ledger;
  7. Send 成功返回 Message ID;
  8. 标记 Delivered。

具体到 Weixin:

  1. Long Poll getupdates 收到消息;
  2. Adapter 记录最新 context_token
  3. Typing 使用 iLink API;
  4. Weixin 不支持同样的渐进式编辑路径,Gateway 更依赖 Typing 和最终消息;
  5. 出站消息必须带该 Peer 的 context_token
  6. 最终回复同样先进入 Delivery Ledger;
  7. 若崩溃后重投,使用持久化的 Session/Peer 路由重新发送。

在这个案例中,Gateway 重启后没有再次请求模型、没有再次运行测试、没有再次创建 PR。它只恢复最后一公里的 Delivery Obligation。


11. 可复用的多端 Agent 网关设计#

Hermes 的实现可以抽象为八个可复用模块。

11.1 Platform Adapter#

统一接口:

class PlatformAdapter:
async def connect(self) -> bool: ...
async def disconnect(self) -> None: ...
async def send(
self,
chat_id: str,
content: str,
reply_to: str | None,
metadata: dict | None
) -> SendResult: ...
async def send_typing(self, chat_id: str) -> None: ...
async def edit_message(
self,
chat_id: str,
message_id: str,
content: str
) -> SendResult: ...

Adapter 还应声明能力:

{
"supports_threads": true,
"supports_editing": true,
"supports_typing": true,
"supports_images": true,
"supports_files": true,
"supports_voice": false,
"max_message_length": 4096,
"length_unit": "utf16"
}

11.2 Normalized Message#

统一消息必须稳定表达:

  • 来源 Platform;
  • Scope;
  • User;
  • Chat;
  • Thread;
  • Message;
  • Reply;
  • 文本;
  • 媒体;
  • Update/Event ID;
  • 授权上下文;
  • 原始 Payload;
  • 时间戳。

核心不变量:

Raw Platform Event
↓ Adapter
Normalized Message
↓ Gateway Core
任何平台走同一授权、会话和调度流程

11.3 Per-chat Session#

Session Key 至少包含:

Profile
Platform
Scope
Chat Type
Chat ID
Thread ID
Participant ID(按策略)

Session Store 需要:

  • Transcript;
  • Session ID;
  • Origin;
  • Reset Policy;
  • Resume State;
  • Token/Cost;
  • Last Activity;
  • Pending Background State。

11.4 Outbound Delivery Queue#

出站 Queue 负责:

  • 同一 Chat 保序;
  • 平台独立速率限制;
  • Progressive Edit 节流;
  • 文本分段;
  • 媒体上传;
  • Retry-After;
  • 优先级;
  • Final Message 优先;
  • 连接不可用时等待。

合理的优先级可设为:

P0:Approval / Deny / Stop
P1:最终结果
P2:错误与状态
P3:Progressive Edit
P4:Typing Refresh

在资源紧张时,应先丢弃低优先级的 Typing 和 Edit,而不是最终结果。

11.5 Delivery Ledger#

Ledger 的最小字段:

delivery_id
session_key
platform
chat_id
thread_id
content_hash
content
state
attempts
owner
created_at
updated_at
last_error

状态机:

pending → attempting → delivered
↘ failed
↘ abandoned

核心不变量:

最终结果必须先持久化,再开始平台发送。

11.6 Rate Limit Adapter#

不同平台应提供统一限流结果:

@dataclass
class RateLimitDecision:
retryable: bool
retry_after: float | None
bucket: str | None
global_limit: bool
operation: str

这样 Outbound Queue 可以统一调度,而不把 Telegram FloodWait、Discord Route Bucket、Slack Retry-After 和 Weixin Error Code 散落在业务逻辑中。

11.7 Approval Channel#

移动端 Approval 需要:

  • Approval ID;
  • Session Key;
  • Tool Call ID;
  • 操作摘要;
  • 风险级别;
  • 过期时间;
  • Approve/Deny;
  • 防重放;
  • 授权用户校验。

Approval 必须绕过普通 Session Queue,直接唤醒等待中的 Agent。否则 Agent 等审批,审批消息又排在 Agent 后面,形成永久死锁。

11.8 Gateway Health Watchdog#

Health Watchdog 至少监控三层:

Process Liveness#

  • PID;
  • Service Manager;
  • Restart Count;
  • Crash Loop。

Event-loop Liveness#

  • Heartbeat;
  • Scheduling Delay;
  • Thread Block;
  • File Descriptor;
  • Queue Depth。

Platform Liveness#

  • Connected;
  • Last Event Time;
  • Last ACK Time;
  • Heartbeat RTT;
  • Reconnect Count;
  • Auth Expiry;
  • Rate-limit State。

Hermes 支持 systemd Watchdog:只有 Event Loop 能及时调度时才发送 Heartbeat;如果 Event Loop 停止推进,systemd 重启进程。普通平台网络断开不应被误判成整个 Event Loop 死亡。1


结语:多端 Agent 的正确边界是“会话、执行与交付分离”#

Hermes 的架构价值,不在于支持多少聊天平台,而在于它把三个经常被混在一起的状态明确拆开:

1. 会话状态#

用户是谁
来自哪个 Chat / Thread
历史上下文是什么
重启后是否继续同一个 Session

2. Agent 执行状态#

当前 Turn 是否运行
正在等待哪个 Tool
是否被 /stop
后台任务是否仍存活

3. 消息交付状态#

最终文本是否已生成
平台发送是否开始
平台是否确认
是否需要重投
是否可能重复

这三个状态的生命周期不同,也必须由不同组件负责:

状态负责组件
会话Session Store
执行AIAgent Runtime / Gateway Runner
交付Outbound Queue / Delivery Ledger

一套生产级多端 Agent Gateway 应遵循五条原则:

  1. 平台差异终止在 Adapter 边界。
  2. Session Key 必须显式编码平台、Chat、Thread 和身份隔离。
  3. 同一 Session 保序,不同 Session 有界并行。
  4. 实时展示可以降级,最终结果不能丢失。
  5. 执行完成不等于交付完成;未知交付结果必须诚实地采用 At-least-once。

这也是 Hermes 与普通 CLI Agent 最根本的区别:它不仅要让 Agent“完成任务”,还要在用户离线、平台限流、连接断开和 Gateway 重启的情况下,确保正确的用户最终能在正确的会话里收到结果。


参考资料#

Footnotes#

  1. Nous Research, Hermes Messaging Gateway 2 3

  2. Nous Research, Adding a Platform Adapter 2

  3. Nous Research, gateway/session.py 2

  4. Nous Research, Session Lifecycle 2 3 4 5

  5. Nous Research, Gateway Internals 2 3 4 5 6

  6. Telegram Bot API

  7. Nous Research, Telegram Adapter

  8. Nous Research, Weixin Adapter 2 3

  9. Nous Research, WeCom Adapter 2 3 4

  10. Discord Gateway 2

  11. Nous Research, Discord Adapter

  12. Slack Socket Mode 2

  13. Nous Research, Slack Adapter 2

  14. Nous Research, gateway/platforms/base.py 2 3

  15. Nous Research, Base Adapter media cache and SSRF controls

  16. Nous Research, gateway/stream_consumer.py 2 3 4

  17. Nous Research, Gateway max concurrent sessions tests

  18. Nous Research, Base Adapter proxy and NO_PROXY handling

  19. Nous Research, gateway/delivery_ledger.py 2 3

Hermes 多端消息网关——Telegram、微信与可靠投递
https://jupiter-ws.cn/posts/agent-networking/07-hermes-messaging-gateway/
作者
Jupiter
发布于
2026-08-06
许可协议
CC BY-NC-SA 4.0