Skip to content

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/nichochar/agent-client-protocol
  • 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.

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 Crucible

ACP 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.

ACP is session-oriented. Every agent interaction happens within a session that tracks state across multiple turns.

  1. Create: session.create initializes a new session, returning a session_id
  2. Configure: session.configure_agent sets model, system prompt, tools, and permissions
  3. Interact: session.send_message sends user messages and streams agent responses
  4. Subscribe: session.subscribe opens an event stream for real-time updates
  5. Pause/Resume: session.pause and session.resume suspend and restore sessions
  6. End: session.end terminates the session and cleans up resources

Sessions have a status field: active, paused, or ended. State persists between messages within a session. The host can resume a paused session without losing conversation history.

When configuring an agent, the host provides:

  • Model: which LLM to use (e.g. claude-sonnet-4-20250514, gpt-4o)
  • System prompt: base instructions for the agent
  • Tools: MCP tool definitions the agent can call
  • Permissions: capability scoping (see Permissions below)
  • Working directory: filesystem context for the agent process
MethodDirectionDescription
session.createhost → agentCreate new session
session.configure_agenthost → agentSet agent configuration
session.send_messagehost → agentSend user message
session.switch_modelhost → agentChange model mid-session
session.cancelhost → agentCancel in-progress response
session.subscribehost → agentSubscribe to event stream
session.unsubscribehost → agentUnsubscribe from events
session.pausehost → agentSuspend session
session.resumehost → agentResume suspended session
session.endhost → agentTerminate session
event: messageagent → hostStreaming response chunk
event: thinkingagent → hostAgent thinking/reasoning content
event: tool_callagent → hostAgent requesting tool use
event: tool_resultagent → hostTool execution result
event: doneagent → hostTurn complete
event: erroragent → hostError notification

All messages use JSON-RPC 2.0 format. Each request carries a unique numeric ID for response matching.

ACP supports streaming responses through its event subscription model. The flow works like this:

  1. Host calls session.subscribe to open an event channel
  2. Host sends session.send_message with the user’s input
  3. Agent emits events as it processes:
    • message chunks: incremental text of the response
    • thinking chunks: reasoning content (if the model supports it)
    • tool_call events: when the agent wants to use a tool, with name and arguments
    • tool_result events: results returned after tool execution
    • done: signals the turn is complete
  4. Host renders chunks in real-time (TUI streaming, web SSE, etc.)

The streaming callback can return false to cancel the current response, which maps to session.cancel on the wire.

ACP defines capability scoping so hosts can restrict what agents are allowed to do. Crucible enforces permissions before forwarding tool calls to MCP.

Available permissions:

PermissionGrants
read_kilnRead notes from the kiln
write_kilnCreate or modify notes
run_toolsExecute MCP tools
web_searchInternet access

Permissions are set during session.configure_agent. The host validates each tool call against the agent’s granted permissions. A write_kiln call from an agent that only has read_kiln will be rejected before it reaches the MCP layer.

Custom agent profiles can specify capabilities in crucible.toml:

[acp.agents.read-only-claude]
extends = "claude"
capabilities = ["read_kiln", "run_tools"]

When Crucible spawns an agent subprocess, it performs a version handshake. The current protocol wire version is 1. Crucible tracks ACP spec revision 0.10.6 internally for compatibility checks. Versions are compatible if they share the same major version number.

The ACP transport layer has configurable parameters:

  • Timeout: 30 seconds default per operation
  • Max message size: 10 MB default
  • Debug logging: toggleable per session

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.

Crucible ships with profiles for common ACP-compatible agents:

AgentCommandInstall
OpenCodeopencode acpgo install github.com/grafana/opencode@latest
Claude Codenpx @zed-industries/claude-agent-acpnpm install -g @zed-industries/claude-agent-acp
Gemini CLIgemininpm install -g gemini-cli
Codexnpx @zed-industries/codex-acpnpm install -g @zed-industries/codex-acp
Cursorcursor-acpnpm install -g cursor-acp

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.

Define custom profiles in crucible.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

When you run cru chat -a claude, Crucible:

  1. Discovers the agent binary (parallel probe of known agents)
  2. Spawns the agent as a subprocess with stdio pipes
  3. Handshakes over JSON-RPC to negotiate protocol version
  4. Creates an ACP session and configures the agent
  5. Injects skill context and Precognition results (semantic search hits from your kiln)
  6. Streams the conversation through the TUI or web UI
  7. Routes all tool calls through Crucible’s MCP server, enforcing permissions

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.

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.

AspectACPMCP
PurposeAgent lifecycle and sessionsTool discovery and execution
DirectionHost controls agentAgent calls tools
Transportstdio JSON-RPC (subprocess)stdio or SSE
StateSession-oriented (multi-turn)Stateless (per-call)
StreamingBuilt-in event subscriptionNot 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).