Skip to content

ACP Protocol

源码版本v2026.7.20

Responsibility

ACP (Agent Client Protocol) is a standard protocol for editors/IDEs to talk to an agent. acp_adapter/ exposes Hermes as an ACP server so that ACP-capable clients (Zed, VS Code extensions, etc.) can directly drive Hermes: manage sessions, read/write resources (files/images), approve edits, trace provenance, and isolate tools by permission. This lets Hermes extend beyond messaging platforms and embed into development workflows.

Design Motivation

Hermes was originally designed for messaging platforms — the gateway layer connects long conversations like Telegram and Discord. But an IDE is different: the working directory is opened by the editor, so cwd has to follow the editor; file changes can't just happen because the agent says so — they have to surface to the user for review; and sessions need to trace "this session was compressed from which one." The ACP layer translates these IDE-specific constraints into primitives Hermes already has. The code doesn't rewrite an agent loop — it wires the ACP message stream into run_conversation, converts ACP resources into OpenAI content parts, and turns the write-file operation inside tool calls into an EditProposal that goes through human review. The core is "adapt, don't rewrite."

Key Files

Data Flow

  1. An ACP client (editor) discovers and launches the Hermes ACP server via acp_registry/agent.json (acp_adapter/__main__.py).
  2. The client opens a session → SessionManager:175 creates a SessionState, aligns cwd (acp_adapter/session.py:29), and expands toolsets (acp_adapter/session.py:129).
  3. The client sends a message/resource → resource conversion:201 turns the ACP resource (file/image, subject to the acp_adapter/server.py:96 size limit) into OpenAI content parts, fed to run_conversation:588.
  4. When the agent wants to modify a file, the tool call becomes an EditProposal via build_edit_proposal:178, popped to the user for confirmation via the approval requester (acp_adapter/edit_approval.py:51).
  5. Tool execution is gated by acp_adapter/permissions.py permissions; each operation records provenance via acp_adapter/provenance.py:22.
  6. Streaming output and events (acp_adapter/events.py) are returned to the client.

acp_registry/agent.json is the client's first-impression business card for Hermes: version, distribution (uvx), launch command. This contract has to stay aligned with the actual binary — otherwise the client installs the wrong version and can't start.

cwd translation is the easiest place to trip in ACP scenarios: a Windows client runs Hermes inside WSL, but the workspace is E:\Projects or a \\wsl.localhost\... UNC path. _translate_acp_cwd unifies these into POSIX — otherwise every Path operation in the agent is misaligned.

The edit proposal is a frozen dataclass. The key field is old_text: _proposal_for_write_file reads the existing file content first, so the diff display can show "before / after" instead of a blind change:

python
@dataclass(frozen=True)
class EditProposal:
    tool_name: str
    path: str
    old_text: str | None
    new_text: str
    arguments: dict[str, Any]

The approval requester binds via ContextVar, not a global variable. Multiple concurrent ACP sessions each have their own requester and don't cross-contaminate. .env / id_rsa default to ask rather than workspace_session, avoiding being mistakenly allowed through by an "everything in the workspace is trusted" policy.

Provenance is specific to the IDE scenario: on messaging platforms sessions are linear; in IDEs they get compressed, forked, and resumed. build_session_provenance walks up parent_session_id, counts layers where end_reason == 'compression', and tags sessionKind and compressionDepth, putting the internal Hermes session id alongside the external ACP session id inside _meta.hermes.sessionProvenance.

Boundaries and Failure

  • Handshake version mismatch: agent.json pins hermes-agent[acp]==0.19.0. Once Hermes upgrades to 0.20, a client with a cached old manifest starts a server whose schema doesn't match — the InitializeResponse is missing fields and the handshake fails.
  • WSL cwd translation gap: When translate_cwd_for_wsl_backend doesn't cover some new Windows path form, the agent ends up with a misaligned cwd. Every Path operation drifts, but doesn't error immediately — it only blows up as "directory not found" on the first file write.
  • Approval-requester deadlock: Approval goes through ContextVar + user callback. If the client event loop stalls and never ACKs, the wait after build_edit_proposal just hangs. Whether the ACP protocol layer's cancel actually propagates to that wait point depends on whether events.py's interrupt path covers it.
  • Provenance chain break: If parent_session_id is written wrong during a compression fork, build_session_provenance hits a dead end and returns root. The client then displays a continuation as a new session — history looks "lost."

Summary

ACP lets Hermes embed into editor workflows: session management, resource read/write, edit approval, permission isolation, provenance tracing. The core is server.py (protocol adaptation) + session.py (sessions) + edit_approval.py (human-approved edits). It complements the gateway layer — the gateway connects to messaging platforms, ACP connects to IDEs.

Unofficial community learning site. Content based on the MIT-licensed NousResearch/hermes-agent source.