--- sidebar_position: 2 title: "ACP Internals" description: "How the ACP adapter works: lifecycle, sessions, event bridge, approvals, and tool rendering" --- # ACP Internals The ACP adapter wraps Hermes' synchronous `AIAgent` in an async JSON-RPC stdio server. Key implementation files: - `acp_adapter/entry.py` - `acp_adapter/server.py` - `acp_adapter/session.py` - `acp_adapter/events.py` - `acp_adapter/permissions.py` - `acp_adapter/tools.py` - `acp_adapter/auth.py` - `acp_registry/agent.json` ## Boot flow ```text hermes acp / hermes-acp / python -m acp_adapter -> acp_adapter.entry.main() -> load ~/.hermes/.env -> configure stderr logging -> construct HermesACPAgent -> acp.run_agent(agent) ``` Stdout is reserved for ACP JSON-RPC transport. Human-readable logs go to stderr. ## Major components ### `HermesACPAgent` `acp_adapter/server.py` implements the ACP agent protocol. Responsibilities: - initialize / authenticate - new/load/resume/fork/list/cancel session methods - prompt execution - session model switching - wiring sync AIAgent callbacks into ACP async notifications ### `SessionManager` `acp_adapter/session.py` tracks live ACP sessions. Each session stores: - `session_id` - `agent` - `cwd` - `model` - `history` - `cancel_event` The manager is thread-safe and supports: - create - get - remove - fork - list - cleanup - cwd updates ### Event bridge `acp_adapter/events.py` converts AIAgent callbacks into ACP `session_update` events. Bridged callbacks: - `tool_progress_callback` - `thinking_callback` - `step_callback` - `message_callback` Because `AIAgent` runs in a worker thread while ACP I/O lives on the main event loop, the bridge uses: ```python asyncio.run_coroutine_threadsafe(...) ``` ### Permission bridge `acp_adapter/permissions.py` adapts dangerous terminal approval prompts into ACP permission requests. Mapping: - `allow_once` -> Hermes `once` - `allow_always` -> Hermes `always` - reject options -> Hermes `deny` Timeouts and bridge failures deny by default. ### Tool rendering helpers `acp_adapter/tools.py` maps Hermes tools to ACP tool kinds and builds editor-facing content. Examples: - `patch` / `write_file` -> file diffs - `terminal` -> shell command text - `read_file` / `search_files` -> text previews - large results -> truncated text blocks for UI safety ## Session lifecycle ```text new_session(cwd) -> create SessionState -> create AIAgent(platform="acp", enabled_toolsets=["hermes-acp"]) -> bind task_id/session_id to cwd override prompt(..., session_id) -> extract text from ACP content blocks -> reset cancel event -> install callbacks + approval bridge -> run AIAgent in ThreadPoolExecutor -> update session history -> emit final agent message chunk ``` ### Cancelation `cancel(session_id)`: - sets the session cancel event - calls `agent.interrupt()` when available - causes the prompt response to return `stop_reason="cancelled"` ### Forking `fork_session()` deep-copies message history into a new live session, preserving conversation state while giving the fork its own session ID and cwd. ## Provider/auth behavior ACP does not implement its own auth store. Instead it reuses Hermes' runtime resolver: - `acp_adapter/auth.py` - `hermes_cli/runtime_provider.py` So ACP advertises and uses the currently configured Hermes provider/credentials. ## Working directory binding ACP sessions carry an editor cwd. The session manager binds that cwd to the ACP session ID via task-scoped terminal/file overrides, so file and terminal tools operate relative to the editor workspace. ## Duplicate same-name tool calls The event bridge tracks tool IDs FIFO per tool name, not just one ID per name. This is important for: - parallel same-name calls - repeated same-name calls in one step Without FIFO queues, completion events would attach to the wrong tool invocation. ## Approval callback restoration ACP temporarily installs an approval callback on the terminal tool during prompt execution, then restores the previous callback afterward. This avoids leaving ACP session-specific approval handlers installed globally forever. ## Current limitations - ACP sessions are process-local from the ACP server's point of view - non-text prompt blocks are currently ignored for request text extraction - editor-specific UX varies by ACP client implementation ## Related files - `tests/acp/` — ACP test suite - `toolsets.py` — `hermes-acp` toolset definition - `hermes_cli/main.py` — `hermes acp` CLI subcommand - `pyproject.toml` — `[acp]` optional dependency + `hermes-acp` script