跳到主要內容

網關內部機制

消息網關是一個長期運行的進程,通過統一的架構連接 Hermes 與 14+ 個外部消息平臺。

核心文件

文件用途
gateway/run.pyGatewayRunner — 主循環、斜槓命令處理、消息分發(約 7,500 行)
gateway/session.pySessionStore — 會話持久化與會話密鑰構造
gateway/delivery.py向目標平臺/渠道發送出站消息
gateway/pairing.py用戶授權的私信配對流程
gateway/channel_directory.py將聊天 ID 映射為可讀名稱,用於定時發送
gateway/hooks.py鉤子發現、加載與生命週期事件分發
gateway/mirror.pysend_message 的跨會話消息鏡像
gateway/status.py針對配置文件作用域的網關實例的令牌鎖管理
gateway/builtin_hooks/始終註冊的鉤子(例如 BOOT.md 系統提示鉤子)
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 並設置中斷事件。這會在消息到達網關運行器之前就捕獲它們。

  2. 二級 —— 網關運行器gateway/run.py):檢查 _running_agents。攔截特定命令(/stop/new/queue/status/approve/deny),並按需路由。其餘所有消息將觸發 running_agent.interrupt()

必須在 Agent 被阻塞時仍能到達運行器的命令(如 /approve)通過 await self._message_handler(event) 內聯分發 —— 它們繞過後臺任務系統,以避免競態條件。

授權機制

網關使用多層授權檢查,按順序評估:

  1. 平臺級全允許標誌(如 TELEGRAM_ALLOW_ALL_USERS)—— 若啟用,則該平臺所有用戶均被授權
  2. 平臺允許列表(如 TELEGRAM_ALLOWED_USERS)—— 逗號分隔的用戶 ID 列表
  3. 私信配對 —— 經認證的用戶可通過配對碼為新用戶配對
  4. 全局全允許GATEWAY_ALLOW_ALL_USERS)—— 若啟用,則所有平臺的所有用戶均被授權
  5. 默認:拒絕 —— 未授權用戶將被拒絕

私信配對流程

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

配對狀態由 gateway/pairing.py 持久化,重啟後仍有效。

斜槓命令分發

網關流程中的所有斜槓命令均通過相同的解析管道:

  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)具有特殊處理邏輯。

配置來源

網關從多個來源讀取配置:

來源提供內容
~/.hermes/.envAPI 密鑰、機器人令牌、平臺憑證
~/.hermes/config.yaml模型設置、工具配置、顯示選項
環境變量覆蓋上述任意配置

與 CLI(使用 load_cli_config() 並帶有硬編碼默認值)不同,網關通過 YAML 加載器直接讀取 config.yaml。這意味著在 CLI 默認字典中存在但用戶配置文件中不存在的配置鍵,在 CLI 與網關中的行為可能不同。

平臺適配器

每個消息平臺在 gateway/platforms/ 中都有一個適配器:

gateway/platforms/
├── base.py # BaseAdapter — 所有平臺的共享邏輯
├── telegram.py # Telegram 機器人 API(長輪詢或 webhook)
├── discord.py # 通過 discord.py 提供 Discord
├── slack.py # Slack Socket Mode
├── whatsapp.py # WhatsApp 商業雲 API
├── signal.py # Signal 通過 signal-cli REST API
├── matrix.py # Matrix 通過 matrix-nio(可選 E2EE)
├── mattermost.py # Mattermost WebSocket API
├── email.py # 通過 IMAP/SMTP 發送電子郵件
├── sms.py # SMS 通過 Twilio
├── dingtalk.py # 釘釘WebSocket
├── feishu.py # 飛書/Lark WebSocket 或 webhook
├── wecom.py # WeCom(工作微信)回調
├── weixin.py # Weixin(個人微信)通過iLink Bot API
├── bluebubbles.py # 通過 BlueBubbles macOS 服務器的 Apple iMessage
├── webhook.py # 入站/outbound webhook 適配器
├── api_server.py # REST API 服務器適配器
└── homeassistant.py # 家庭助理對話集成

適配器實現一個通用接口:

  • connect() / disconnect() — 生命週期管理
  • send_message() — 出站消息發送
  • on_message() — 入站消息標準化 → MessageEvent

Token 鎖機制

使用唯一憑據連接的適配器會在 connect() 中調用 acquire_scoped_lock(),並在 disconnect() 中調用 release_scoped_lock()。這可防止兩個配置文件同時使用同一個機器人令牌。

消息投遞路徑

出站投遞(gateway/delivery.py)處理以下情況:

  • 直接回復 — 將響應發送回原始聊天
  • 主頻道投遞 — 將定時任務輸出和後臺結果路由到配置的主頻道
  • 顯式目標投遞 — 使用 send_message 工具指定 telegram:-1001234567890
  • 跨平臺投遞 — 將消息投遞到與原始消息不同的平臺

定時任務的投遞不會被鏡像到網關會話歷史中 — 它們僅存在於獨立的定時任務會話中。這是有意的設計選擇,以避免消息交替違規。

鉤子(Hooks)

網關鉤子是響應生命週期事件的 Python 模塊。

網關鉤子事件

事件觸發時機
gateway:startup網關進程啟動時
session:start新的對話會話開始時
session:end會話完成或超時
session:reset用戶通過 /new 重置會話
agent:startAgent 開始處理消息
agent:stepAgent 完成一次工具調用迭代
agent:endAgent 完成並返回響應
command:*任意斜槓命令被執行

鉤子從 gateway/builtin_hooks/(始終啟用)和 ~/.hermes/hooks/(用戶安裝)中發現。每個鉤子是一個包含 HOOK.yaml 清單和 handler.py 的目錄。

記憶提供者集成

當啟用記憶提供者插件(例如 Honcho)時:

  1. 網關為每條消息創建一個帶會話 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)
  1. 會話結束/重置時,觸發 on_session_end() 以進行清理和最終數據刷新

記憶刷新生命週期

當會話被重置、恢復或過期時:

  1. 內置記憶被刷新到磁盤
  2. 記憶提供者的 on_session_end() 鉤子被觸發
  3. 臨時 AIAgent 執行一次僅記憶的對話輪次
  4. 然後上下文被丟棄或歸檔

後臺維護

網關在處理消息的同時運行週期性維護任務:

  • 定時任務觸發 — 檢查任務調度並觸發到期任務
  • 會話超時清理 — 在超時後清理廢棄會話
  • 記憶主動刷新 — 在會話過期前主動刷新記憶
  • 緩存刷新 — 刷新模型列表和提供者狀態

進程管理

網關以長期運行的進程形式運行,通過以下方式管理:

  • hermes gateway start / hermes gateway stop — 手動控制
  • systemctl(Linux)或 launchctl(macOS) — 服務管理
  • PID 文件位於 ~/.hermes/gateway.pid — 配置文件範圍的進程跟蹤

配置文件範圍 vs 全局start_gateway() 使用配置文件範圍的 PID 文件。hermes gateway stop 僅停止當前配置文件的網關。hermes gateway stop --all 使用全局 ps aux 掃描來終止所有網關進程(用於更新時)。