Agent Client Protocol (ACP)
The Agent Client Protocol is an open protocol for AI agent hosting. It defines how a host application spawns, manages, and communicates with external AI agents over a stdio JSON-RPC connection.
Key facts:
- Full name: Agent Client Protocol (not “Agent Context Protocol”)
- Specification: agentclientprotocol.com
- Source: github.com/agentclientprotocol/agent-client-protocol
- Crucible uses the
agent-client-protocolcrate, version 2.0.0 (wire schema 1.5.0) - Transport: stdio JSON-RPC over newline-delimited messages (same pattern as LSP)
- Crucible is the host. It spawns external agents (Claude Code, OpenCode, Gemini CLI) as subprocesses.
- The agent binary receives a stdio connection; Crucible drives the session lifecycle.
Three-Layer Architecture
Crucible’s agent integration stacks three protocols, each with a distinct role:
Crucible (ACP Host)├── ACP Layer: Manages agent subprocess lifecycle, sessions, streaming├── Skills Layer: Context injection from knowledge graph└── MCP Layer: Exposes kiln tools to the agent
External Agent (e.g. Claude Code)├── Receives ACP connection from Crucible├── Loads skills context injected by Crucible└── Calls MCP tools served by CrucibleACP controls the agent. MCP provides tools to the agent. Skills provides knowledge. These layers compose cleanly: ACP manages the session, skills inject relevant context before each turn, and MCP handles tool calls the agent makes during its response.
Sessions
ACP is session-oriented. Every agent interaction happens within a session that tracks state across multiple turns. State persists between prompts within a session, and a host can load a prior session to continue its conversation.
Don’t confuse ACP sessions with Crucible’s internal daemon sessions. The daemon exposes its own JSON-RPC surface (session.create, session.send_message, session.subscribe, …) over the Unix socket — that is Crucible’s private client protocol, not ACP. When Crucible hosts or serves ACP, it bridges between the two.
Wire Methods
The ACP wire protocol is JSON-RPC 2.0 over stdio. The methods that matter in practice:
| Method | Direction | Description |
|---|---|---|
initialize | client → agent | Version handshake and capability exchange |
session/new | client → agent | Create a new session (with working directory) |
session/prompt | client → agent | Send a user prompt; the response ends the turn with a stop reason |
session/load | client → agent | Resume a previously created session, with a replay of its history |
session/resume | client → agent | Continue an earlier session without a replay of its history |
session/set_config_option | client → agent | Set one session config option; a model switch uses the model_config category |
session/list | client → agent | List the sessions that the agent stores (Crucible does not send it yet) |
session/delete | client → agent | Delete one stored session (Crucible does not send it yet) |
session/cancel | client → agent | Cancel the in-progress turn |
session/close | client → agent | Close a session and release its resources |
session/update | agent → client | Streaming notification: message chunks, thought chunks, tool_call / tool_call_update entries |
session/request_permission | agent → client | Ask the client to approve a tool call (allow/reject, once/always) |
elicitation/create | agent → client | Ask the user a free-form question; Crucible refuses it with -32601 today |
Streaming
A prompt turn streams through session/update notifications:
- Client sends
session/promptwith the user’s input - The agent emits
session/updatenotifications as it works: incremental message text, thought chunks (if the model exposes reasoning), andtool_call/tool_call_updateentries as tools start and finish - If a tool needs approval, the agent sends
session/request_permissionand waits for the client’s answer - The
session/promptresponse returns with a stop reason (end_turn,cancelled, …) when the turn completes
The client renders updates in real time (TUI streaming, web SSE, etc.) and can cancel mid-turn with session/cancel.
Tool calls are an upsert
The agent reports tool calls as an upsert keyed by toolCallId. The spec sets no order
between tool_call and tool_call_update: an update can arrive before its call, and some
agents send only updates. To model this, Crucible keeps one tool-call table per turn.
Crucible announces each call once, at the first update that carries a name. At the end of
the turn, Crucible announces each nameless call under the label “Unnamed tool”, and closes
each call that has no completion with an error that names the stop reason.
Permissions
Crucible does not enforce a per-capability ACP permission model. Tool calls from a hosted agent go through the same permission gate as every other session — permission patterns, agent-card tool policy, Lua hooks, and the [permissions] config (see Permission Precedence) — and interactive approvals surface as ACP session/request_permission requests.
The capabilities field on an [acp.agents.*] profile is parsed and stored but never read for enforcement. Setting capabilities = ["read_kiln"] on a profile has no effect today; do not rely on it to restrict an agent. Use the permission system instead.
Protocol Details
Handshake
When Crucible spawns an agent subprocess, it performs a version handshake via initialize. The current protocol wire version is 1. Versions are compatible if they share the same major version number.
Transport Configuration
Timeouts and limits under [acp] in config.toml:
streaming_timeout_minutes(default 15) — how long a streaming turn may go without completing before it is cut off.- The removed fields
session_timeout_minutesandmax_message_size_mbstill load without an error; the values are ignored.
Error Handling
Errors propagate as JSON-RPC error responses with standard error codes. The error event type notifies the host of asynchronous failures during streaming. Crucible surfaces these in the TUI as inline error messages.
Built-in Agent Profiles
Crucible ships with profiles for common ACP-compatible agents:
| Profile | Command | Install |
|---|---|---|
opencode | opencode acp | npm install -g opencode-ai@latest (or curl -fsSL https://opencode.ai/install | bash) |
claude | npx @zed-industries/claude-agent-acp | npm install -g @zed-industries/claude-agent-acp (bridges to the Claude Code CLI) |
gemini | gemini | npm install -g @google/gemini-cli |
codex | npx @zed-industries/codex-acp | npm install -g @zed-industries/codex-acp (bridges to the OpenAI Codex CLI) |
cursor | cursor-acp | npm install -g cursor-acp (bridges to the Cursor CLI) |
hermes | hermes acp | curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash |
opencode, gemini and hermes speak ACP directly; the other three are bridges that also
need the underlying vendor CLI installed. Hermes notes: a polished tool’s result arrives in
content text blocks with no rawOutput; permission requests carry a fresh perm-check-N
id that matches no announced tool call; session/close and ping answer -32601, which is
not an error. cru prints the same install lines when no agent is
found, so if this table ever disagrees with the binary, trust the binary.
Agent discovery uses parallel probing: Crucible checks all known agents concurrently via which + --version, caches the result, and falls back through the priority list if the preferred agent isn’t available.
Custom Agent Profiles
Define custom profiles in config.toml using extends to inherit from a built-in:
[acp.agents.my-claude]extends = "claude"env = { ANTHROPIC_BASE_URL = "http://localhost:4000" }
[acp.agents.my-agent]command = "/usr/local/bin/my-agent"args = ["--mode", "acp"]Then use with: cru chat -a my-claude
Crucible as ACP Host
When you run cru chat -a claude, Crucible:
- Discovers the agent binary (parallel probe of known agents)
- Spawns the agent as a subprocess with stdio pipes
- Handshakes over JSON-RPC to negotiate protocol version
- Creates an ACP session and configures the agent
- Injects skill context and Precognition results (semantic search hits from your kiln)
- Streams the conversation through the TUI or web UI
- Routes all tool calls through Crucible’s MCP server, enforcing permissions
After a daemon restart, Crucible sends session/resume to continue the agent session;
when the agent refuses, Crucible falls back to session/new. To switch the model, Crucible
sends session/set_config_option with the agent’s model_config option. At shutdown,
Crucible sends session/close when the agent advertises the capability. A -32601 reply
to session/resume or session/close is not an error.
The agent never touches your kiln directly. Every file read, search, and write goes through Crucible’s tool layer, giving you full control over what the agent can access.
Precognition Integration
Before each turn, Crucible runs semantic search against your kiln using the user’s message as a query. Relevant note fragments are injected into the agent’s context alongside any loaded skills. This means the agent has access to your knowledge without you manually searching for context.
Crucible as ACP Agent
Crucible also implements the other side of the protocol: the agent role. Run
cru acpand Crucible speaks ACP on stdin/stdout, so any ACP host (Zed, JetBrains, Neovim, marimo — or another Crucible instance) can drive it as a knowledge-grounded agent. Point your editor’s ACP agent configuration at the cru acp command.
What the host gets is the ordinary internal Crucible agent, exposed through a different front door:
initialize— Crucible advertises protocol v1, text prompts, andloadSessionsupport.session/new— creates a normal daemon session (type = chat,agent = internal) with the host-suppliedcwdas the workspace. It shows up incru session listand persists like any other session.session/prompt— the user’s message is forwarded to the daemon; the daemon’s event stream is translated into ACPsession/updatenotifications: text deltas become agent message chunks, thinking becomes thought chunks, and tool calls/results becometool_call/tool_call_updateentries (with a coarse tool-kind for host icons).session/request_permission— when the daemon needs approval to run a tool, Crucible surfaces it to the host as a permission request with Allow/Reject (once/always) options and maps the choice back to Crucible’s permission model.session/cancel— forwarded to the daemon to stop the turn; the prompt returnsstop_reason = cancelled.session/load— resumes an existing daemon session so the host can continue a prior conversation.
Because sessions are real daemon sessions, Precognition and kiln tools apply automatically — the host does not need to know anything about Crucible’s knowledge graph.
Not yet wired (v1): session modes, model listing/switching and forking over ACP, host-side filesystem/terminal capabilities (tools run daemon-side exactly as for internal sessions), and authentication (none advertised). A free-form question maps to ACP elicitation/create, which Crucible does not serve yet; panels have no ACP analogue. Crucible auto-declines both.
Dogfood: Crucible hosting Crucible
Because Crucible is both host and agent, you can point one instance at another. Add a profile that runs cru acp:
[acp.agents.crucible]command = "cru"args = ["acp"]Then cru chat -a crucible runs a full round trip: the host Crucible spawns cru acp, which serves the internal agent back over the protocol. This is the end-to-end test of both roles at once. (See the “Manual verification” note in the ACP agent-mode module for a scripted stdio recipe.)
Comparison with MCP
| Aspect | ACP | MCP |
|---|---|---|
| Purpose | Agent lifecycle and sessions | Tool discovery and execution |
| Direction | Host controls agent | Agent calls tools |
| Transport | stdio JSON-RPC (subprocess) | stdio or SSE |
| State | Session-oriented (multi-turn) | Stateless (per-call) |
| Streaming | Built-in event subscription | Not specified |
ACP and MCP are complementary. ACP manages the agent process and conversation. MCP provides the tools the agent uses during that conversation. In Crucible, both protocols work together: ACP on the outside (host ↔ agent), MCP on the inside (agent ↔ tools).
See Also
- Agents & Protocols: overview of agent architecture
- Agent Skills: skills specification reference
- MCP Gateway: connecting external MCP servers
- chat: chat command reference