ACP 協議
職責
ACP(Agent Client Protocol)是編輯器/IDE 與 agent 通訊的標準協議。acp_adapter/ 把 Hermes 暴露為 ACP server,讓支援 ACP 的客戶端(Zed、VS Code 擴充功能等)能直接驅動 Hermes:管理會話、讀寫資源(檔案/圖片)、審批編輯、追溯來源(provenance)、按權限隔離工具 (tool)。這讓 Hermes 不止於訊息平台,也能嵌入開發工作流。
設計動機
Hermes 原本為訊息平台設計——網關層 (gateway layer) 接 Telegram/Discord 這類長會話。但 IDE 是另一回事:工作目錄是編輯器開的,cwd 要跟著編輯器走;改檔案不能 agent 說改就改,得彈到使用者面前過一遍;會話之間還要能追溯「這條壓縮 (compression) 自哪條」。ACP 這層就是把這些 IDE 特有約束翻譯成 Hermes 已有的原語。程式碼裡沒有重寫一套 agent 迴圈,而是把 ACP 訊息流接到 run_conversation,把 ACP 資源轉成 OpenAI content parts,把工具呼叫裡的寫檔案操作改造成 EditProposal 走人審。核心是「適配,不是重寫」。
關鍵檔案
server.py 頭部:1-92— "ACP agent server — exposes Hermes Agent via the Agent Client Protocol"資源處理:96-150—_MAX_ACP_RESOURCE_BYTES、_resource_display_name、_is_text_resource/_is_image_resource資源文字/圖片轉換:201-296—_format_resource_text/_resource_link_to_parts/_image_data_url(資源→OpenAI content parts)SessionState / SessionManager:159-175— ACP 會話狀態與管理cwd 翻譯:29-62—_translate_acp_cwd/_normalize_cwd_for_compare(編輯器工作目錄對齊)toolsets 展開:129-159—_expand_acp_enabled_toolsetsclass EditProposal:26-51— 編輯提案(寫檔案 / patch)build_edit_proposal:178-220— 從工具呼叫建構提案(write_file / patch_replace / patch_v4a)審批請求器:51-69—set_edit_approval_requester/ token 機制permissions.py— 工具權限隔離provenance:22-111—build_session_provenance/session_provenance_meta(操作來源追溯)auth.py/entry.py/events.py__main__.py— ACP server 啟動入口(5 行)acp_registry/agent.json— agent 元資料清單(供客戶端發現)acp_registry/icon.svg— 圖示
資料流
- ACP 客戶端(編輯器)按
acp_registry/agent.json發現並啟動 Hermes ACP server(acp_adapter/__main__.py)。 - 客戶端發起會話 →
SessionManager:175建立SessionState,對齊 cwd(acp_adapter/session.py:29)、展開 toolsets(acp_adapter/session.py:129)。 - 客戶端發訊息/資源 →
資源轉換:201把 ACP resource(檔案/圖片,受acp_adapter/server.py:96大小限制)轉成 OpenAI content parts,餵給run_conversation:588。 - agent 想改檔案時,工具呼叫經
build_edit_proposal:178變成EditProposal,經審批請求器(acp_adapter/edit_approval.py:51)彈給使用者確認。 - 工具執行受
acp_adapter/permissions.py權限管控;每次操作經acp_adapter/provenance.py:22記 provenance。 - 串流 (stream) 輸出與事件(
acp_adapter/events.py)回傳客戶端。
acp_registry/agent.json 是客戶端第一次見 Hermes 時的名片:版本、分布方式(uvx)、啟動命令。這層契約必須和實際二進制對齊,否則客戶端裝錯版本就跑不起來。
cwd 翻譯是 ACP 場景最容易踩坑的地方:Windows 客戶端把 Hermes 跑在 WSL 裡,工作區卻是 E:\Projects 或 \\wsl.localhost\... UNC 路徑。_translate_acp_cwd 把它統一翻成 POSIX,否則 agent 裡所有 Path 操作對不齊。
編輯提案是 frozen dataclass,關鍵在 old_text——_proposal_for_write_file 先讀已存在的檔案內容,這樣 diff 展示能看到「之前/之後」,而不是盲改:
@dataclass(frozen=True)
class EditProposal:
tool_name: str
path: str
old_text: str | None
new_text: str
arguments: dict[str, Any]審批請求器走 ContextVar 綁定,不是全域變數,多個並發 ACP 會話各自有自己的 requester,不會串擾。.env / id_rsa 預設走 ask 而非 workspace_session,避免被「整個工作區都信任」的策略誤放行。
provenance 是 IDE 場景特有:訊息平台裡會話是線性線,IDE 裡會話會被壓縮、分叉、resume。build_session_provenance 沿 parent_session_id 往上走,數 end_reason == 'compression' 的層數,標出 sessionKind、compressionDepth,把內部 Hermes session id 和對外 ACP session id 一起放在 _meta.hermes.sessionProvenance。
邊界與失敗
- 握手版本不匹配:
agent.json釘死hermes-agent[acp]==0.19.0。Hermes 升到 0.20 後客戶端快取舊清單,啟動的 server 和客戶端 schema 對不上,InitializeResponse 缺欄位就會握手失敗。 - WSL cwd 翻譯漏網:
translate_cwd_for_wsl_backend沒覆蓋到某個新的 Windows 路徑形式時,agent 拿到對不齊的 cwd,所有Path操作跑偏,但不會立刻報錯,只在第一次寫檔案時以「找不到目錄」炸出來。 - 審批請求器死結:審批走
ContextVar+ 使用者回調,客戶端事件迴圈卡住沒回 ACK 時,build_edit_proposal之後的等待一直掛著。ACP 協議層的 cancel 是否能傳播到這個等待點,要看events.py的中斷路徑有沒有覆蓋。 - provenance 鏈斷:壓縮分叉時
parent_session_id寫錯,build_session_provenance走到斷點就回傳root,客戶端把 continuation 誤當新會話展示,歷史看起來就「丟了」。
小結
ACP 讓 Hermes 嵌入編輯器工作流:會話管理、資源讀寫、編輯審批、權限隔離、來源追溯。核心是 server.py(協議適配)+ session.py(會話)+ edit_approval.py(人審編輯)。和網關 (gateway) 層互補——網關接訊息平台,ACP 接 IDE。