ACPex is an Elixir implementation of the Agent Client Protocol (ACP) - a JSON-RPC protocol for editor-to-agent communication over stdio.
This is NOT the Agent Communication Protocol (agentcommunicationprotocol.dev). This implements the JSON-RPC based Agent Client Protocol from agentclientprotocol.com for editor-to-AI-agent communication.
ACPex.Application
└── ConnectionSupervisor
└── Connection (GenServer)
├── Transport (Ndjson)
└── SessionSupervisor
└── Session (GenServer) per conversation
- Agent: AI coding assistant (implements
ACPex.Agent) - Client: Code editor/IDE (implements
ACPex.Client)
All protocol messages use Ecto schemas from ACPex.Schema.*:
- Compile-time type checking via pattern matching
- Auto-completion support
Elixir (snake_case) ↔ JSON (camelCase) handled automatically via
:source field mappings:
# Elixir
%InitializeRequest{protocol_version: 1, client_info: %{}}
# JSON
{"protocolVersion": 1, "clientInfo": {}}Always use snake_case in Elixir code. The codec handles JSON conversion.
defmodule MyAgent do
@behaviour ACPex.Agent
def init(_args), do: {:ok, %{}}
def handle_initialize(request, state) do
response = %ACPex.Schema.Connection.InitializeResponse{
protocol_version: 1,
agent_capabilities: %{"sessions" => %{"new" => true}}
}
{:ok, response, state}
end
# Implement other callbacks...
end
{:ok, pid} = ACPex.start_agent(MyAgent, [])defmodule MyClient do
@behaviour ACPex.Client
def init(_args), do: {:ok, %{}}
def handle_session_update(notification, state) do
# Process streaming updates
{:noreply, state}
end
def handle_fs_read_text_file(request, state) do
case File.read(request.path) do
{:ok, content} ->
response = %ACPex.Schema.Client.FsReadTextFileResponse{content: content}
{:ok, response, state}
{:error, _} ->
{:error, %{code: -32001, message: "File not found"}, state}
end
end
# Implement other callbacks...
end
{:ok, pid} = ACPex.start_client(MyClient, [],
agent_path: "/path/to/agent",
agent_args: [] # Optional agent-specific args
)- Client sends
initializerequest → Agent responds with capabilities - (Optional) Client sends
authenticaterequest - Client creates sessions with
session/new - Client sends prompts with
session/prompt - Agent streams updates via
session/updatenotifications
Client → Agent (requests):
initialize,authenticate,session/new,session/prompt
Agent → Client (requests):
fs/read_text_file,fs/write_text_fileterminal/create,terminal/output,terminal/wait_for_exit,terminal/kill,terminal/release
Agent → Client (notifications):
session/update(streaming updates during processing)
# Must be called from within handler module (self() is Connection pid)
{:ok, response} = ACPex.Protocol.Connection.send_request(
self(),
"fs/read_text_file",
%ACPex.Schema.Client.FsReadTextFileRequest{path: "/path/to/file"},
30_000 # timeout in ms
)ACPex.Protocol.Connection.send_notification(
self(),
"session/update",
%ACPex.Schema.Session.UpdateNotification{
session_id: session_id,
update: %{"kind" => "agent_thought_chunk", "content" => %{"thought" => "..."}}
}
)Requests (return response or error):
{:ok, response_struct, new_state}
{:error, %{code: integer, message: string}, new_state}Notifications (no response):
{:noreply, new_state}Agents send these during session/prompt processing:
agent_thought_chunk- Reasoning/planningagent_message_chunk- Response contenttool_call- Tool usage announcementtool_call_update- Tool execution resultplan- Multi-step planavailable_commands_update- Available commands changedcurrent_mode_update- Mode changed
-32700: Parse error-32600: Invalid request-32601: Method not found-32602: Invalid params-32603: Internal error
-32001: Resource not found-32002: Permission denied-32003: Invalid state-32004: Capability not supported
- Stream updates frequently during long operations via
session/update - Check capabilities before requesting client features
- Validate file paths before requesting reads/writes
- Handle
session/cancelnotifications to stop processing - Return proper
stopReasoninPromptResponse:"done","cancelled","length","error"
- Validate all file/terminal requests before executing (sandbox to workspace)
- Implement permission checks for destructive operations
- Handle streaming updates efficiently (batch UI updates)
- Timeout long-running requests appropriately
- Implement all required callbacks (behaviour enforces this)
- Use typed schemas for all protocol messages (not raw maps)
- Sessions maintain conversation state - one session per conversation thread
ACP complements the Model Context Protocol (MCP):
- ACP: Editor ↔ Agent communication
- MCP: Agent ↔ Tools/Data communication
Agents may connect to MCP servers (specified in session/new via mcpServers
param).
Protocol supports multiple content types in prompts and responses:
text- Plain textimage- Base64 encoded imagesaudio- Base64 encoded audioresource- File content with URI and MIME typeresource_link- Reference to external resource
- Don't use raw maps - Always use typed schema structs
- self() is Connection pid - When calling
send_request/send_notificationfrom handlers - Requests need timeout - Default is 5 seconds, long operations need explicit timeout
- Protocol version matters - Always check in
handle_initialize - Sessions are isolated - Each has its own process and state
- agent_args is optional - Some agents (like Gemini CLI) need specific arguments
- Capabilities are negotiated - Don't assume features are available
- Spec: https://agentclientprotocol.com
- HexDocs: https://hexdocs.pm/acpex
- Livebook example:
livebooks/usage.livemd