架構總覽
Hermes Agent 是 Nous Research 開源(MIT)的桌面端 AI agent,核心定位是「與你一起成長的持久 agent」:一則訊息進來,從 Telegram/Discord/Slack 等任意界面接入,經核心迴圈呼叫模型+工具 (tool) 完成任務,並把經驗沉澱進記憶與技能,供下次複用。
分層視圖
各層一句話
- 界面層:TUI / CLI / Web / ACP(編輯器協議),只是入口,不持有業務。
- 網關層 (gateway layer):
gateway/run.py統一接入,各平台是轉接器 (adapter),會話與投遞分離,串流 (stream) 與 hooks 可插拔。 - 核心迴圈:
run_conversation編排「模型呼叫 → 工具呼叫 → 結果回填 → 預算 (budget) 遞減」,失敗走回退 (fallback)。 - 能力層:Tool 是原子函式(
tools/registry.py註冊),Skill 是更高階的可演化流程(被學習閉環改寫),Provider/MCP/Plugins 提供模型與外部能力。 - 排程擴展:Cron 定時驅動、Subagent 隔離派生(零上下文 (context) 成本)、6 種沙箱後端隔離執行。
- 學習閉環:把會話經驗沉澱成技能與使用者模型,形成「越用越好用」的正向循環。
由上而下:每一層的代表性簽名
要理解 Hermes 的分層,最快的辦法是看每一層在程式碼裡露出什麼「簽名」。
界面層 → 核心迴圈的轉發器。所有殼都呼叫 AIAgent.run_conversation,但它只是個轉發(run_agent.py:6350):
def run_conversation(self, user_message, system_message=None,
conversation_history=None, ..., moa_config=None) -> Dict[str, Any]:
"""Forwarder — see ``agent.conversation_loop.run_conversation``."""
from agent.conversation_loop import run_conversation
token = set_conversation_context(self._conversation_root_id())
with scoped_runtime_main({}):
return run_conversation(self, user_message, ...)會話 ID 透過 ContextVar 推下去——主迴圈內的所有 LLM 呼叫(壓縮 (compression)、視覺、MoA slot、背景審視 fork)都自動帶標籤。
網關層 → 平台轉接器的總管。GatewayRunner 用 mixin 拼出授權/看板/斜線命令能力(gateway/run.py:22392),入口 start_gateway(replace=True) 會先殺舊實例——為 systemd 重啟時舊程序沒退乾淨留的後門:
class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, GatewaySlashCommandsMixin):
"""Main gateway controller. Manages the lifecycle of all platform adapters
and routes messages to/from the agent."""核心迴圈 → 真正幹活的地方。run_conversation 在 agent/conversation_loop.py,編排「模型呼叫 → 工具呼叫 → 結果回填 → 預算遞減」,失敗走回退;簽名見上。
能力層 → 工具註冊。每個 tools/*.py 在模組匯入時呼叫 registry.register(tools/registry.py:365),override=True 才允許插件覆蓋內建工具:
def register(self, name, toolset, schema, handler, check_fn=None,
requires_env=None, is_async=False, description="", emoji="",
max_result_size_chars=None, dynamic_schema_overrides=None, override=False):
"""Register a tool. Called at module-import time by each tool file.
``override=True`` is an explicit opt-in for plugins ... Without it,
registrations that would shadow an existing tool are rejected."""插件想替換內建工具得顯式 override=True 並通過 allow_tool_override 配置——防止插件悄悄把 browser 工具換掉。MCP 工具同名覆蓋走單獨通道。
排程擴展 → cron 的 tick。tick(cron/scheduler.py:3888)是檔案鎖保護的單次掃描,gateway 每 60 秒從背景執行緒呼叫一次;adapters 參數讓 cron 任務複用 gateway 已建好的平台連線,不用自己重連。
學習閉環 → 學習圖匯總。build_learning_graph(agent/learning_graph.py:248-328)把學到(非內建)的技能和記憶拼成節點/邊。它先呼叫 build_skill_nodes(_skill_roots()) 掃 base 和 profile 兩個根,然後過濾 source != "base" 且 created_by == "agent" 或 use_count > 0 的技能當「學到的」——內建技能不進學習圖,避免雜訊蓋過演化訊號。
分層動機
為什麼這麼分層?一句話:讓能改的層不污染不能改的層。
- 界面層換殼頻繁(TUI/Web/IDE 協議各有各的迭代節奏),不能讓換殼逼著改業務;所以殼只掛回呼(
step_callback/event_callback),不進run_conversation內部。 - 網關層接入新平台是常態(QQbot、企業微信、新 Discord 風格),但核心迴圈不應感知平台——所以平台邏輯全是轉接器,網關只把訊息和 agent 呼叫解耦。
- 能力層是「原子」,學習閉環是「演化」——原子不能被演化路徑污染(否則背景審視一個 bug 把工具改壞了,主迴圈也跟著崩),所以
learning_mutations改的是skills/而不是tools/。 - 排程擴展是橫切:Cron 和 Subagent 都是「在主迴圈之外觸發主迴圈」,共享
run_conversation但不直接持有 agent 狀態。
常見誤讀
- 「Hermes 是個 agent 框架」——不是。它是一個具體的桌面 agent 實作,核心迴圈、工具集、技能目錄都是 Hermes 自帶的。框架化能力(MCP、Plugin、Provider 抽象)是它為了能擴展而暴露的邊界,不是它的本體。
- 「學習閉環會自動優化模型提示」——不會。它只改
skills/下的 SKILL.md 和~/.hermes/skills/下的記憶,不改系統提示 (prompt) 模板。提示模板是倉庫裡的固定檔案,改它要靠發版。 - 「Subagent 是分散式」——不是。Subagent 在同一程序內 fork agent(共享 runtime),沙箱是執行隔離(程式碼跑在 Docker/SSH/Modal/Daytona),不是 agent 狀態的分散式。
- 「網關層 = 訊息平台接入」——只對了一半。網關還管會話狀態(
session/)、投遞去重(delivery_ledger)、串流分發 (dispatch)(stream_*)、斜線命令、kanban watchers——這些都是「訊息平台 + agent 之間」的職責,不只是接入。