// LSP-for-coding-agents · standardize how editors talk to AI agents · use acpx as the headless client
The Agent Client Protocol is a JSON-RPC standard for communication between code editors and AI coding agents. An agent that speaks ACP works in any ACP-capable editor; an editor that speaks ACP works with any ACP-capable agent. Think LSP, but for "edit my code with an LLM" instead of "tell me what's at this cursor."
Maintained by Sergey Ignatov (lead). Spec lives at agentclientprotocol.com; OpenAPI at /api-reference/openapi.json; libraries in Rust, TypeScript, Python, Java, Kotlin.
Three friction points it eliminates:
(agent × editor) pair needs a custom integration. With ACP, both sides target the same protocol.The LSP analogy. Before LSP, every editor wrote its own type-checking integration for every language. After LSP, one server per language, one client per editor. ACP applies the same insight to coding agents.
| actor | role | examples |
|---|---|---|
Agent | Program that uses generative AI to autonomously modify code. Receives prompts, emits updates, requests permission for risky ops. | Codex, Claude, Gemini, Pi, OpenClaw, Cursor agent |
Client | Typically a code editor or orchestrator. Manages the workspace, files, terminals; renders updates to the user. | VSCode, Zed, Neovim, JetBrains, acpx |
ACP uses JSON-RPC 2.0 over one of:
┌────────────┐ ┌────────────┐
│ Client │ ── JSON-RPC 2.0 over stdio/ws ──→ │ Agent │
│ (editor or │ ←── notifications + responses ── │ (LLM tool) │
│ acpx CLI) │ │ │
└────────────┘ └────────────┘
│ │
└─── manages files/terminals ←─── requests ────────┘
initialize; agent responds with capabilities.authenticate if the agent requires credentials.session/new creates a fresh session; session/load resumes a prior one.session/prompt; agent streams session/update notifications, may request file ops, terminal exec, or permission. Eventually the prompt response arrives with a stop reason.session/cancel notification at any time during a turn.| method | purpose |
|---|---|
initialize | Handshake; exchange protocol version + capabilities |
authenticate | Optional auth flow before sessions |
session/new | Create a new conversation session |
session/load | Resume a previously-created session by id |
session/prompt | Send a user message; receive the turn's stop reason |
session/set_mode | Switch operating mode (plan / edit / autonomous, agent-defined) |
session/cancel | Notification — interrupt the current turn |
| method | purpose |
|---|---|
session/update | Notification — progress: text deltas, tool calls, plan updates |
session/request_permission | Ask user to approve a risky operation |
fs/read_text_file | Read a file from the workspace |
fs/write_text_file | Write a file (after permission) |
terminal/create | Spawn a shell session |
terminal/output | Read terminal output |
terminal/wait_for_exit | Block until the command exits |
terminal/kill | Force-terminate a running command |
terminal/release | Release the terminal handle |
// 1. Client opens session
→ {"jsonrpc":"2.0","id":1,"method":"session/new","params":{"workspace_root":"/Users/me/myproj"}}
← {"jsonrpc":"2.0","id":1,"result":{"session_id":"sess_abc"}}
// 2. Client sends a prompt
→ {"jsonrpc":"2.0","id":2,"method":"session/prompt","params":{
"session_id":"sess_abc",
"messages":[{"role":"user","content":[{"type":"text","text":"fix the flaky test"}]}]
}}
// 3. Agent streams updates back as notifications
← {"jsonrpc":"2.0","method":"session/update","params":{"type":"agent_message_chunk","text":"Looking at "}}
← {"jsonrpc":"2.0","method":"session/update","params":{"type":"tool_call","id":"tc_1","name":"read_file",...}}
// 4. Agent asks the client to read a file
← {"jsonrpc":"2.0","id":"r1","method":"fs/read_text_file","params":{"path":"tests/flaky.test.ts"}}
→ {"jsonrpc":"2.0","id":"r1","result":{"content":"..."}}
// 5. Agent asks permission to write
← {"jsonrpc":"2.0","id":"p1","method":"session/request_permission",
"params":{"tool":"write_file","path":"tests/flaky.test.ts","diff":"..."}}
→ {"jsonrpc":"2.0","id":"p1","result":{"granted":true}}
// 6. Agent writes
← {"jsonrpc":"2.0","id":"w1","method":"fs/write_text_file",...}
// 7. Agent finishes the turn
← {"jsonrpc":"2.0","id":2,"result":{"stop_reason":"end_turn"}}
Every action an agent takes during a turn — reading a file, running a command, editing code — surfaces to the client as a session/update notification carrying a content block. Content types include text deltas, tool-call markers, plan updates, diffs, and terminal output. Reusing MCP-compatible content shapes where possible; custom types layered on top for agentic UX (notably diffs).
Risky operations — writing files, executing shell commands, deleting things — go through session/request_permission. The client decides how to surface the request to the user (modal dialog, auto-approve list, batch confirmation). The agent waits for the response before proceeding.
Why this matters. Permissions in ACP are first-class messages, not policy hacks. An autonomous orchestrator can implement "auto-approve any read; ask once per session for writes; never permit shell" as plain logic in the client. No prompt-engineering required.
A session is a turn-by-turn conversation, persisted by the agent. The protocol defines:
session/new — fresh startsession/load — resume a stored session by idsession/list — enumerate available sessions (stabilized)session/resume — continue from a checkpoint (stabilized)session/close — release server-side state (stabilized)session/set_mode — switch agent operating mode (plan / edit / autonomous, agent-defined)session/fork — branch a session for "what if I tried Y instead of X"openclaw/acpx is a CLI ACP client. It exists so that LLM orchestrators (autoresearch loops, CI bots, Claude Code itself) can drive coding agents over a structured protocol instead of scraping pseudo-TTYs.
| fact | value |
|---|---|
| language | TypeScript / Node.js |
| install | npm install -g acpx@latest or npx acpx@latest |
| version | 0.6.0 (alpha — interfaces still moving) |
| license | MIT |
| built-in adapters | codex, claude, gemini, cursor, pi, openclaw — auto-downloaded via npx on first use |
# talk to Codex over ACP, one prompt, no session
acpx codex "fix the flaky test in tests/auth.spec.ts"
# point at a custom ACP-server binary
acpx --agent ./my-acp-server "summarize this repo"
# run a flow file (multi-step orchestration)
acpx flow run ./repair.flow.ts --input-file ./input.json
# list / new / show / history / close
acpx codex sessions new
acpx codex sessions list
acpx codex sessions show sess_abc
acpx codex -s backend "implement token pagination"
acpx codex sessions close sess_abc
acpx cancel sess_abc # interrupt a running turn
acpx set-mode sess_abc plan # switch operating mode
acpx set sess_abc max_tokens 4096 # session config option
acpx config show
acpx status
session/cancel properlyfs/* and terminal/* handlers with permission controls and cwd sandboxingOfficial libraries (mint a server or client in your language of choice):
ACP defines a _meta property on most messages that propagates user data through the protocol. Custom capabilities are negotiated during initialize: each side advertises what it supports, and both sides agree on the intersection. Active RFDs add proxy chains (one ACP server fronting several agents), MCP-over-ACP transport, telemetry export, and elicitation (structured user input).
Concrete plays for the Organized AI / clip-pipeline setup:
acpx codex --json, get structured tool calls, log session/update events to PostHog as step.tool_called / step.tool_returned automatically.portrait-foreignfilm-clips as an ACP agent that exposes render, retranscribe, upload as tools. Then any ACP client (acpx, Zed, the dashboard) can drive a render with one prompt.$ai_generation events still flowing through PostHog.npx acpx@latest in production.{ auto_approve: true } in a wrapper and forget. The first time the agent decides to run rm -rf, you'll wish you hadn't. Default-deny shell, allowlist reads.session/list and session/close stabilized for a reason. Clean up after CI runs.session/cancel is a notification, not a forceful kill. The agent must be implemented to check for it. Some don't (or do so only at await points).