直接连接企业微信,收到消息就调用 Agent,几十行代码就能跑。但张三和李四同时说话、同一用户连续补充、再接一个 Telegram 后,这段代码会立刻失去边界。
Gateway 的价值不是多一次消息转发,而是把平台差异与会话状态从核心循环外部治理起来。
这篇只回答一个问题:为什么把微信消息转给 Agent 的几十行代码,无法自然扩展成多平台产品?
Hermes 给出的答案是:Gateway 用适配器统一平台事件,以 session key 隔离历史,同一会话串行处理并在新消息到来时优雅中断,再把结果路由回原平台。
先用 session key 把人和对话分开
没有 session key,每条消息都是新对话;维度不足,同一个群里不同用户又会共享历史。Hermes 把平台、聊天类型、群或聊天 ID、用户 ID 组合成唯一标识。
这不是纯技术主键,而是产品的上下文边界。群内按人隔离还是共享上下文,应该由明确场景决定,不能由一个过于简单的 key 偶然决定。
适配器翻译平台,Gateway 处理通用事件
企业微信和 Telegram 的连接方式、字段与发送格式不同。每个平台适配器负责把原始消息翻译成 MessageEvent,再把通用回复转换回平台格式。
GatewayRunner 只看统一事件,负责找会话、加载历史、调用核心循环和返回结果。新增平台时,下游 Agent 不需要增加对应 if-else。
同一会话必须串行,新消息需要中断
如果用户在 Agent 思考时补充一句“用 Python 写”,同时启动第二个 Agent,会出现两份实例并发读写同一历史。Hermes 让同一 session 只运行一个 Agent,新消息先暂存并请求当前 Agent 中断。
中断不是强杀进程,而是在流读取、工具序列和循环起点检查标志后退出。已经产生的内容与工具结果仍写入数据库,下一轮加载完整历史后继续。
长期运行需要明确过期和恢复策略
消息网关不会像 CLI 一样关掉终端就结束。Hermes 支持空闲超时与每日重置,避免几个月前的对话无限续接。
进程崩溃后,近期活跃的半完成会话不会被强行恢复,而是在下一条消息到来时从干净状态重置。恢复脏状态看似连续,实际可能带来重复工具调用和不一致历史。
用户补充一句话时发生了什么
假设用户先发“帮我写一个排序算法”,Agent 开始生成;十秒后用户补充“用 Python 写”。
适配器先把第二条平台消息翻译成 MessageEvent,Gateway 根据 platform、chat 和 user 等信息计算出同一个 session key。发现该 session 正在执行后,新消息不会启动第二个 Agent,而是进入 pending 状态,并向当前 Agent 设置中断标记。
当前 Agent 在流读取、工具序列或下一轮循环的检查点停止。中断前已经产生的回复、工具调用和结果仍写入 transcript。随后 Gateway 从数据库重新加载历史,把新的补充消息加入上下文,再启动下一轮处理。
Hermes 的 pending messages 只保留最后一条,这是一项明确取舍,不是消息队列的通用答案。真正可迁移的原则是:同一会话串行执行,新输入以可恢复方式打断旧任务,而不是让两个实例争抢同一份历史。
三个容易走偏的地方
- 把平台格式判断写进核心循环,新增渠道时修改整个执行链。
- 每条消息都创建并立即运行一个 Agent,造成同一会话并发冲突。
- session key 缺少用户维度,让群聊中的不同用户共享私有上下文。
产品经理可以怎样评审
评审这项能力,可以先检查三条:
- 消息型 Agent 首先要定义会话归属,再讨论模型能力。
- 补充消息、撤回、重试和中断都应以同一会话串行约束为基础。
- 故障恢复的目标是状态一致,不是尽可能续上所有半完成步骤。
原材料明确 pending_messages 只保留最后一条;本文不把这种取舍扩展为所有聊天产品的通用规则。
