ACP 协议
职责
ACP(Agent Client Protocol)是编辑器/IDE 与 agent 通信的标准协议。acp_adapter/ 把 Hermes 暴露为 ACP server,让支持 ACP 的客户端(Zed、VS Code 扩展等)能直接驱动 Hermes:管理会话、读写资源(文件/图片)、审批编辑、追溯来源(provenance)、按权限隔离工具。这让 Hermes 不止于消息平台,也能嵌入开发工作流。
设计动机
Hermes 原本为消息平台设计——网关层接 Telegram/Discord 这类长会话。但 IDE 是另一回事:工作目录是编辑器开的,cwd 要跟着编辑器走;改文件不能 agent 说改就改,得弹到用户面前过一遍;会话之间还要能追溯「这条压缩自哪条」。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_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,流式事件(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(人审编辑)。和网关层互补——网关接消息平台,ACP 接 IDE。