hermes源码学习8--Gateway 内部机制

消息 gateway 是一个长期运行的进程,通过统一架构将 Hermes 连接到 20 余个外部消息平台。

关键文件

文件 用途
gateway/run.py GatewayRunner --- 主循环、斜杠命令、消息分发(大文件;请查看 git 获取当前行数)
gateway/session.py SessionStore --- 会话持久化与会话键构造
gateway/delivery.py 向目标平台/频道投递出站消息
gateway/pairing.py 用于用户授权的 DM 配对流程
gateway/channel_directory.py 将聊天 ID 映射为可读名称,用于 cron 投递
gateway/hooks.py Hook(钩子)发现、加载与生命周期事件分发
gateway/mirror.py send_message 提供跨会话消息镜像
gateway/status.py 面向 profile 范围的 gateway 实例的 token 锁管理
gateway/builtin_hooks/ 始终注册的 hook 扩展点(当前未内置任何 hook)
gateway/platforms/ 平台适配器(每个消息平台一个)

架构概览

复制代码
┌─────────────────────────────────────────────────┐
│                  GatewayRunner                  │
│                                                 │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐       │
│  │ Telegram │  │ Discord  │  │  Slack   │       │
│  │ Adapter  │  │ Adapter  │  │ Adapter  │       │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘       │
│       │             │             │             │
│       └─────────────┼─────────────┘             │
│                     ▼                           │
│              _handle_message()                  │
│                     │                           │
│         ┌───────────┼───────────┐               │
│         ▼           ▼           ▼               │
│  Slash command   AIAgent    Queue/BG            │
│    dispatch      creation   sessions            │
│                     │                           │
│                     ▼                           │
│                 SessionStore                    │
│              (SQLite persistence)               │
└───────┴─────────────┴─────────────┴─────────────┘

消息流程

当消息从任意平台到达时:

  1. 平台适配器 接收原始事件,将其规范化为 MessageEvent
  2. 基础适配器 检查活跃会话守卫:
    • 若该会话的 agent 正在运行 → 将消息加入队列,设置中断事件
    • 若为 /approve/deny/stop → 绕过守卫(内联分发)
  3. GatewayRunner._handle_message() 接收事件:
    • 通过 _session_key_for_source() 解析会话键(格式:agent:main:{platform}:{chat_type}:{chat_id}
    • 检查授权(见下方授权章节)
    • 检查是否为斜杠命令 → 分发至命令处理器
    • 检查 agent 是否已在运行 → 拦截 /stop/status 等命令
    • 否则 → 创建 AIAgent 实例并运行对话
  4. 响应通过平台适配器回传

会话键格式

会话键编码了完整的路由上下文:

复制代码
agent:main:{platform}:{chat_type}:{chat_id}

示例:agent:main:telegram:private:123456789

支持线程的平台(Telegram 论坛话题、Discord 线程、Slack 线程)可能在 chat_id 部分包含线程 ID。切勿手动构造会话键 --- 请始终使用 gateway/session.py 中的 build_session_key()

两级消息守卫

当 agent 正在运行时,传入消息会依次经过两级守卫:

  1. 第一级 --- 基础适配器gateway/platforms/base.py):检查 _active_sessions。若会话处于活跃状态,将消息加入 _pending_messages 队列并设置中断事件。此级在消息到达 gateway runner 之前进行拦截。

  2. 第二级 --- Gateway runnergateway/run.py):检查 _running_agents。拦截特定命令(/stop/new/queue/status/approve/deny)并进行相应路由。其余所有消息触发 running_agent.interrupt()

必须在 agent 被阻塞时到达 runner 的命令(如 /approve)通过 await self._message_handler(event) 内联分发 --- 绕过后台任务系统以避免竞态条件。

授权

Gateway 使用多层授权检查,按顺序评估:

  1. 平台级全量放行标志 (如 TELEGRAM_ALLOW_ALL_USERS)--- 若设置,该平台所有用户均被授权
  2. 平台白名单 (如 TELEGRAM_ALLOWED_USERS)--- 逗号分隔的用户 ID
  3. DM 配对 --- 已认证用户可通过配对码为新用户授权
  4. 全局放行标志GATEWAY_ALLOW_ALL_USERS)--- 若设置,所有平台的所有用户均被授权
  5. 默认:拒绝 --- 未授权用户被拒绝

DM 配对流程

复制代码
Admin: /pair
Gateway: "Pairing code: ABC123. Share with the user."
New user: ABC123
Gateway: "Paired! You're now authorized."

配对状态持久化于 gateway/pairing.py,重启后仍然有效。

斜杠命令分发

Gateway 中所有斜杠命令均经过相同的解析流程:

  1. hermes_cli/commands.py 中的 resolve_command() 将输入映射为规范名称(处理别名、前缀匹配)
  2. 规范名称与 GATEWAY_KNOWN_COMMANDS 进行比对
  3. _handle_message() 中的处理器根据规范名称进行分发
  4. 部分命令受配置门控(CommandDef 上的 gateway_config_gate

运行中 Agent 守卫

在 agent 处理消息期间不得执行的命令会被提前拒绝:

复制代码
if _quick_key in self._running_agents:
    if canonical == "model":
        return "⏳ Agent is running --- wait for it to finish or /stop first."

绕过命令(/stop/new/approve/deny/queue/status)具有特殊处理逻辑。

配置来源

Gateway 从多个来源读取配置:

来源 提供内容
~/.hermes/.env API 密钥、bot token、平台凭据
~/.hermes/config.yaml 模型设置、工具配置、显示选项
环境变量 覆盖上述任意配置

与 CLI(使用带硬编码默认值的 load_cli_config())不同,gateway 通过 YAML 加载器直接读取 config.yaml。这意味着存在于 CLI 默认值字典但不在用户配置文件中的配置键,在 CLI 和 gateway 之间可能表现不同。

平台适配器

每个消息平台在 gateway/platforms/ 下均有对应适配器:

复制代码
gateway/platforms/
├── base.py              # BaseAdapter --- 所有平台的共享逻辑
├── telegram.py          # Telegram Bot API(长轮询或 webhook)
├── discord.py           # Discord bot(通过 discord.py)
├── slack.py             # Slack Socket Mode
├── whatsapp.py          # WhatsApp Business Cloud API
├── signal.py            # Signal(通过 signal-cli REST API)
├── matrix.py            # Matrix(通过 mautrix,可选 E2EE)
├── mattermost.py        # Mattermost WebSocket API
├── email.py             # 电子邮件(通过 IMAP/SMTP)
├── sms.py               # 短信(通过 Twilio)
├── dingtalk.py          # 钉钉 WebSocket
├── feishu.py            # 飞书/Lark WebSocket 或 webhook
├── wecom.py             # 企业微信(WeCom)回调
├── weixin.py            # 微信(个人版,通过 iLink Bot API)
├── bluebubbles.py       # Apple iMessage(通过 BlueBubbles macOS 服务端)
├── qqbot/               # QQ Bot(腾讯 QQ,通过官方 API v2,子包:adapter.py、crypto.py、keyboards.py 等)
├── yuanbao.py           # 元宝(腾讯)私信/群组适配器
├── feishu_comment.py    # 飞书文档/云盘评论回复处理器
├── msgraph_webhook.py   # Microsoft Graph 变更通知 webhook(Teams、Outlook 等)
├── webhook.py           # 入站/出站 webhook 适配器
├── api_server.py        # REST API 服务器适配器
└── homeassistant.py     # Home Assistant 对话集成

适配器实现统一接口:

  • connect() / disconnect() --- 生命周期管理
  • send_message() --- 出站消息投递
  • on_message() --- 入站消息规范化 → MessageEvent

Token 锁

使用唯一凭据连接的适配器在 connect() 中调用 acquire_scoped_lock(),在 disconnect() 中调用 release_scoped_lock()。这可防止两个 profile 同时使用同一 bot token。

投递路径

出站投递(gateway/delivery.py)处理以下场景:

  • 直接回复 --- 将响应发回原始聊天
  • 主频道投递 --- 将 cron 任务输出和后台结果路由至已配置的主频道
  • 显式目标投递 --- send_message 工具指定 telegram:-1001234567890,或通过 hermes send CLI 封装同一工具供 shell 脚本使用
  • 跨平台投递 --- 投递至与原始消息不同的平台

Cron 任务投递不会镜像到 gateway 会话历史中 --- 它们仅存在于各自的 cron 会话中。这是有意为之的设计选择,以避免消息交替违规。

Hooks

Gateway hook 是响应生命周期事件的 Python 模块。

Gateway Hook 事件

事件 触发时机
gateway:startup Gateway 进程启动时
session:start 新对话会话开始时
session:end 会话完成或超时时
session:reset 用户通过 /new 重置会话时
agent:start Agent 开始处理消息时
agent:step Agent 完成一次工具调用迭代时
agent:end Agent 完成并返回响应时
command:* 任意斜杠命令被执行时

Hook 从 gateway/builtin_hooks/(扩展点 --- 当前发行版中为空;_register_builtin_hooks() 是一个空操作存根)和 ~/.hermes/hooks/(用户安装)中发现。每个 hook 是一个包含 HOOK.yaml 清单和 handler.py 的目录。

内存提供者集成

当内存提供者插件(如 Honcho)启用时:

  1. Gateway 为每条消息创建一个带会话 ID 的 AIAgent

  2. MemoryManager 使用会话上下文初始化提供者

  3. 提供者工具(如 honcho_profileviking_search)通过以下路径路由:

    AIAgent._invoke_tool()
    → self._memory_manager.handle_tool_call(name, args)
    → provider.handle_tool_call(name, args)

  4. 会话结束/重置时,on_session_end() 触发以进行清理和最终数据刷写

内存刷写生命周期

当会话被重置、恢复或过期时:

  1. 内置内存刷写至磁盘
  2. 内存提供者的 on_session_end() hook 触发
  3. 临时 AIAgent 运行仅含内存的对话轮次
  4. 上下文随后被丢弃或归档

后台维护

Gateway 在处理消息的同时运行周期性维护任务:

  • Cron 计时 --- 检查任务计划并触发到期任务
  • 会话过期 --- 超时后清理废弃会话
  • 内存刷写 --- 在会话过期前主动刷写内存
  • 缓存刷新 --- 刷新模型列表和提供者状态

进程管理

Gateway 作为长期运行进程运行,管理方式如下:

  • hermes gateway start / hermes gateway stop --- 手动控制
  • systemctl(Linux)或 launchctl(macOS)--- 服务管理
  • PID 文件位于 ~/.hermes/gateway.pid --- 面向 profile 的进程追踪

Profile 范围 vs 全局start_gateway() 使用 profile 范围的 PID 文件。hermes gateway stop 仅停止当前 profile 的 gateway。hermes gateway stop --all 使用全局 ps aux 扫描来终止所有 gateway 进程(用于更新时)。

相关推荐
console.log('npc')1 小时前
将 Figma 接入 Codex MCP:从 `/plugins` 到本地插件配置的完整教程
前端·人工智能·python·figma·code·codex·mcp
俊哥V1 小时前
每日 AI 研究简报 · 2026-06-11
人工智能·ai
Rain5091 小时前
1.1 理解AI Agent与自动化数据分析
人工智能·ai·数据分析·自动化·ai编程
吴佳浩 Alben1 小时前
pytorch 你不学?_EP01_环境准备与安装验证
人工智能·pytorch·python
葡萄城技术团队1 小时前
从NL2SQL到AI智能问数:企业数据分析的下一步在哪里
人工智能·数据挖掘·数据分析
硅谷秋水1 小时前
NVIDIA OmniDreams:用于闭环自动驾驶仿真、支持实时生成的世界模型
人工智能·深度学习·机器学习·计算机视觉·自动驾驶
MartinYeung51 小时前
[论文学习]针对 LLM 的间接提示注入攻击用于高效隐私洩露之深度分析
人工智能·学习
A15362551 小时前
六轴工业机械臂厂家怎么选?评估维度与选型参考
大数据·服务器·人工智能
未来和明天2 小时前
领嵌iLeadE-588边缘计算盒子断网状态下可以独立工作
人工智能