網關內部機制
消息網關是一個長期運行的進程,通過統一的架構連接 Hermes 與 14+ 個外部消息平臺。
核心文件
| 文件 | 用途 |
|---|---|
gateway/run.py | GatewayRunner — 主循環、斜槓命令處理、消息分發(約 7,500 行) |
gateway/session.py | SessionStore — 會話持久化與會話密鑰構造 |
gateway/delivery.py | 向目標平臺/渠道發送出站消息 |
gateway/pairing.py | 用戶授權的私信配對流程 |
gateway/channel_directory.py | 將聊天 ID 映射為可讀名稱,用於定時發送 |
gateway/hooks.py | 鉤子發現、加載與生命週期事件分發 |
gateway/mirror.py | send_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) │
└─────────────────────────────────────────────────┘
消息流轉流程
當來自任意平臺的消息到達時:
- 平臺適配器 接收原始事件,將其標準化為
MessageEvent - 基礎適配器 檢查活躍會話保護:
- 如果該會話的 Agent 正在運行 → 將消息入隊,並設置中斷事件
- 如果是
/approve、/deny、/stop→ 跳過保護(直接分發)
- GatewayRunner._handle_message() 接收事件:
- 通過
_session_key_for_source()解析會話密鑰(格式:agent:main:{platform}:{chat_type}:{chat_id}) - 檢查授權(參見授權部分)
- 檢查是否為斜槓命令 → 轉發至命令處理器
- 檢查 Agent 是否已運行 → 攔截如
/stop、/status等命令 - 否則 → 創建
AIAgent實例並啟動對話
- 通過
- 響應 通過平臺適配器返回
會話密鑰格式
會話密鑰編碼了完整的路由上下文:
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 正在運行時,傳入的消息會經過兩級順序保護:
-
一級 —— 基礎適配器(
gateway/platforms/base.py):檢查_active_sessions。如果會話處於活躍狀態,則將消息入隊至_pending_messages並設置中斷事件。這會在消息到達網關運行器之前就捕獲它們。 -
二級 —— 網關運行器(
gateway/run.py):檢查_running_agents。攔截特定命令(/stop、/new、/queue、/status、/approve、/deny),並按需路由。其餘所有消息將觸發running_agent.interrupt()。
必須在 Agent 被阻塞時仍能到達運行器的命令(如 /approve)通過 await self._message_handler(event) 內聯分發 —— 它們繞過後臺任務系統,以避免競態條件。
授權機制
網關使用多層授權檢查,按順序評估:
- 平臺級全允許標誌(如
TELEGRAM_ALLOW_ALL_USERS)—— 若啟用,則該平臺所有用戶均被授權 - 平臺允許列表(如
TELEGRAM_ALLOWED_USERS)—— 逗號分隔的用戶 ID 列表 - 私信配對 —— 經認證的用戶可通過配對碼為新用戶配對
- 全局全允許(
GATEWAY_ALLOW_ALL_USERS)—— 若啟用,則所有平臺的所有用戶均被授權 - 默認:拒絕 —— 未授權用戶將被拒絕
私信配對流程
Admin: /pair
Gateway: "Pairing code: ABC123. Share with the user."
New user: ABC123
Gateway: "Paired! You're now authorized."
配對狀態由 gateway/pairing.py 持久化,重啟後仍有效。
斜槓命令分發
網關流程中的所有斜槓命令均通過相同的解析管道:
hermes_cli/commands.py中的resolve_command()將輸入映射為規範名稱(處理別名、前綴匹配)- 將規範名稱與
GATEWAY_KNOWN_COMMANDS進行比對 _handle_message()中的處理器根據規範名稱進行分發- 部分命令受配置限制(
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/.env | API 密鑰、機器人令牌、平臺憑證 |
~/.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:start | Agent 開始處理消息 |
agent:step | Agent 完成一次工具調用迭代 |
agent:end | Agent 完成並返回響應 |
command:* | 任意斜槓命令被執行 |
鉤子從 gateway/builtin_hooks/(始終啟用)和 ~/.hermes/hooks/(用戶安裝)中發現。每個鉤子是一個包含 HOOK.yaml 清單和 handler.py 的目錄。
記憶提供者集成
當啟用記憶提供者插件(例如 Honcho)時:
- 網關為每條消息創建一個帶會話 ID 的
AIAgent MemoryManager使用會話上下文初始化提供者- 提供者工具(例如
honcho_profile、viking_search)通過以下方式路由:
AIAgent._invoke_tool()
→ self._memory_manager.handle_tool_call(name, args)
→ provider.handle_tool_call(name, args)
- 會話結束/重置時,觸發
on_session_end()以進行清理和最終數據刷新
記憶刷新生命週期
當會話被重置、恢復或過期時:
- 內置記憶被刷新到磁盤
- 記憶提供者的
on_session_end()鉤子被觸發 - 臨時
AIAgent執行一次僅記憶的對話輪次 - 然後上下文被丟棄或歸檔
後臺維護
網關在處理消息的同時運行週期性維護任務:
- 定時任務觸發 — 檢查任務調度並觸發到期任務
- 會話超時清理 — 在超時後清理廢棄會話
- 記憶主動刷新 — 在會話過期前主動刷新記憶
- 緩存刷新 — 刷新模型列表和提供者狀態
進程管理
網關以長期運行的進程形式運行,通過以下方式管理:
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 掃描來終止所有網關進程(用於更新時)。