入口與啟動流程
職責
把「一條 hermes 命令 / 一個網關 (gateway) 訊息」變成「記憶體裡跑起來的 agent」。入口層刻意做得很薄——run_agent.py 只負責轉發和 CLI 裝配,真正的依賴注入在 agent_init.py。這樣網關、TUI、CLI、ACP、cron 能共用同一套初始化路徑。
設計動機
入口層薄、裝配集中,是為了讓所有呼叫路徑只學一套。如果 AIAgent.__init__ 自己解析 provider、合併 extra_body、算壓縮 (compression) 閾值,那網關 (gateway)、TUI、cron 想跑 agent 就得各自複刻這套邏輯,改一處要改多處。把 __init__ 退化成轉發器,真正決策收斂在 init_agent 一個函式裡,新接入一個前端(比如未來的 MCP 守護程式)只要建構 AIAgent 就拿到完整能力,不必知道裝配細節。
hermes_bootstrap.py 放在最前面也是同樣的思路:Windows UTF-8 是「越早越好」的環境修正,等到子程序派生以後再補救就來不及,所以它獨立成模組,所有入口點第一行就 import 它。
關鍵檔案
網關 main:22965— 網關程式入口main()AIAgent 類別:400-497— 瘦入口類別,__init__僅存參AIAgent.__init__ 轉發:497-560— 註解明確寫 "Forwarder — see agent.agent_init.init_agent"run_conversation 方法:6350—AIAgent.run_conversation實例方法,呼叫模組級 run_conversation:588CLI main:6434— CLI 入口init_agent:276-400— 依賴注入主函式_resolve_compression_threshold:93-127— 啟動期設定解析範例hermes_bootstrap.py— 最早期的環境/路徑引導(倉庫根)
資料流
- 使用者執行
hermes(CLI)或網關main()啟動(run_agent.py:22965)。 - 行程經
hermes_bootstrap.py做路徑/環境修正,進入CLI main:6434或網關迴圈。 - 建構
AIAgent:400實例,其__init__轉發到init_agent:276。 init_agent解析 provider、toolsets、壓縮閾值、自訂 provider extra_body 等,裝配出可執行的 agent 物件。- 網關在
gateway/run.py的GatewayRunner裡持有該 agent,把每條入站訊息餵給run_conversation:588。
AIAgent.__init__ 的函式體就是「轉發器」三個字的字面意思——建構參數一個不漏地透傳給 init_agent,自己不做任何解析:
"""Forwarder — see ``agent.agent_init.init_agent``."""
from agent.agent_init import init_agent
init_agent(
self,
base_url=base_url,
api_key=api_key,
provider=provider,
api_mode=api_mode,
...
)而最早接觸程序的 hermes_bootstrap.py 只在 Windows 上做編碼兜底,POSIX 直接放行,邏輯很短:
if not _IS_WINDOWS:
return False
if _bootstrap_applied:
return False
os.environ.setdefault("PYTHONUTF8", "1")
os.environ.setdefault("PYTHONIOENCODING", "utf-8")
for stream_name in ("stdout", "stderr"):
stream = getattr(sys, stream_name, None)
...
reconfigure(encoding="utf-8", errors="replace")setdefault 而不是 = 是故意的:使用者可以提前把 PYTHONUTF8=0 放進環境變數來主動 opt-out,不會被覆蓋。
邊界與失敗
- 入口被直接建構:有人繞過
hermes_bootstrap,直接python -m gateway.run在 Windows 上起程序,stdio 仍是 cp1252,非 ASCII 輸出會UnicodeEncodeError。bootstrap 的位置是約定,不是強制,文件要求所有入口點第一行 import。 AIAgent.__init__在init_agent之前拋例外:由於__init__函式體只是 import + 呼叫,任何失敗都來自init_agent內部,堆疊幀會顯得「錯位」。除錯時看 traceback 的最後一幀是init_agent,不要被__init__的註解誤導成「轉發器本身有 bug」。init_agent的匯入是惰性的:from agent.agent_init import init_agent寫在__init__函式體裡而不是模組頂部,是為了避免循環匯入——agent_init反過來要 importrun_agent。代價是首次建構AIAgent多一次 import 開銷(~毫秒級),之後走模組快取。hermes_bootstrap的 reconfigure 可能失敗:如果 stdout 被替換成BytesIO(測試場景)或已經關閉,reconfigure會拋OSError/ValueError,程式碼 catch 後靜默跳過。環境變數那條路徑仍然生效,所以子程序不受影響。
小結
入口層是「轉發 + 裝配」,不持業務邏輯。AIAgent 是瘦殼,init_agent 才是裝配中心。理解這條鏈路後,後面的Agent 主迴圈就是「被餵訊息後做什麼」。