ACP from scratch Zed Industries published the Agent Client Protocol (ACP) on August 27, 2025, in partnership with Google, with Gemini CLI as the reference implementation, to let any coding agent work inside any code editor without a bespoke integration per pair. ACP runs an agent as a child process speaking JSON-RPC 2.0 over stdio, one JSON message per line, and the agent stays alive across sessions rather than restarting per question. JetBrains said on October 6, 2025 that it would co-develop the protocol with Zed across IntelliJ IDEA, PyCharm and WebStorm, and in January 2026 the two launched the ACP Registry, which now lists Gemini CLI, Claude Code, Codex CLI, GitHub Copilot, OpenCode, Goose, Cline, Qwen Code, Mistral Vibe and Cursor. You have a coding agent you like mine is Mini Me https://mini-me.org/ , my little coding agent side project , and a code editor you don't want to leave in my case it's rather VSCodium https://vscodium.com/ , but when I need to stay "focused" I use Zed https://zed.dev/ . Often, the two can only talk to each other if someone wrote an integration for this "specific pair", for example the VSCode extensions for GitHub Copilot or Claude Code; otherwise you're stuck using a CLI in your IDE's terminal. Today, the Agent Client Protocol , or ACP , is here to solve this problem. In this article, I suggest we start from scratch: - what problem ACP solves, - how a conversation goes between a code editor and a coding agent , - and where Mini Me fits in all this there's no reason I shouldn't advertise my side project 😉 . The problem: editors × agents the-problem-editors-agents Take a code editor that wants to let you use several agents: it will have to write one integration per agent. Now take a coding agent that must be available in several editors: it will have to write one "plugin" per editor. The number of integrations to maintain is the number of editors multiplied by the number of agents . I'll let you imagine the number of integrations to build as the number of code editors / IDEs and agents grows. ACP simplifies this. The code editor implements its role once it is the ACP client , with a built-in feature, or a plugin / extension , on its side the agent implements its own role once the agent that speaks ACP , and every combination works. If this rings a bell, that's normal: it's exactly what the Language Server Protocol LSP did for completion and diagnostics. The ACP announcement post makes the comparison itself: "Just as the Language Server Protocol unbundled language intelligence from monolithic IDEs, our goal with the Agent Client Protocol is to enable you to switch between multiple agents without switching your editor." Source: Bring Your Own Agent to Zed https://zed.dev/blog/bring-your-own-agent-to-zed A bit of history very short a-bit-of-history-very-short ACP was created by Zed Industries the Zed editor and published on August 27, 2025, in partnership with Google : Gemini CLI was the reference implementation. On October 6, 2025, JetBrains announced it would co-develop the protocol with Zed and bring it to its whole range of IDEs IntelliJ IDEA, PyCharm, WebStorm... . In January 2026, Zed and JetBrains launched the ACP Registry , a shared registry to discover and install agents directly from the code editor / IDE or even from any ACP client . Today, this registry lists Gemini CLI, Claude Code an ACP wrapper for Claude Code , Codex CLI, GitHub Copilot, OpenCode, Goose, Cline, Qwen Code, Mistral Vibe, Cursor... On the code editor side, besides Zed and JetBrains, Neovim, Emacs, VS Code or Obsidian go through plugins or extensions. The three building blocks of ACP the-three-building-blocks-of-acp ACP relies on three fairly simple choices: - An agent launched as a subprocess, which talks over stdio - JSON-RPC 2.0: requests, responses and notifications - Two roles client / agent , and requests in both directions 1. An agent launched as a subprocess, which talks over stdio 1-an-agent-launched-as-a-subprocess-which-talks-over-stdio The code editor launches the agent as a child process . The agent reads the editor's messages on its standard input stdin and writes its own on its standard output stdout , and there is one JSON message per line on a single line . ✋ Careful , the agent is not restarted for every question: the editor starts it only once , and the agent stays alive for the whole conversation and even for several sessions , waiting for messages on its standard input. So it looks a bit like a server... but it is not an HTTP server : no port, no network. It has only one "interlocutor", the code editor that launched it, connected to it by two pipes communication channels between the two processes . And when the code editor closes the conversation or stops, the agent stops too. It's like, for example, with LSP and Go: the editor starts gopls once, then "talks" to it as long as it needs to. Practical consequence: anything that is not a protocol message must go to the error output stderr . 2. JSON-RPC 2.0: requests, responses and notifications 2-json-rpc-20-requests-responses-and-notifications Each message is a JSON object, and there are three kinds: - a request carries an id and a method : it expects a response; - a response carries the same id , and a result or an error ; - a notification carries a method but no id : it expects nothing back. A request means "answer me". A notification means "FYI". Remember this difference well, we'll find it everywhere. 3. Two roles, and requests in both directions 3-two-roles-and-requests-in-both-directions - The client is the code editor: it opens sessions, sends the user's questions, displays what the agent says, and provides services to the AI agent asking the user for a permission, reading or writing a file, opening a terminal . - The AI agent coding agent is the program that talks to the model and runs the tools. Important point: requests don't only go one way . The editor asks the agent questions, but the agent can also ask the editor questions we'll see it with permissions . A session, from end to end a-session-from-end-to-end Let's see what a complete session looks like. Solid arrows are requests they expect a response , grey dashed arrows are their responses , and orange dashed arrows are notifications they expect nothing . Let's go through the steps one by one. Step 1: initialize , introductions step-1-initialize-introductions initialize , introductions The client our code editor speaks first. It announces the protocol version it uses, what it can do for the agent its capabilities , and who it is: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{ "protocolVersion":1, "clientCapabilities":{"fs":{"readTextFile":true,"writeTextFile":true},"terminal":true}, "clientInfo":{"name":"Zed","version":"0.210.0"} }} The agent answers same id , 0 with its version and its own capabilities: {"jsonrpc":"2.0","id":0,"result":{ "protocolVersion":1, "agentCapabilities":{"loadSession":false,"promptCapabilities":{"image":false}}, "agentInfo":{"name":"mini-me","version":"0.11.0"}, "authMethods": }} ✋ The rule to remember: anything not declared is not supported . An agent that does not declare loadSession will never receive a request to reload a session. A client that does not declare fs will never receive a file read request. So a minimal agent can declare no optional capability at all and still work. Step 2: session/new , opening a conversation step-2-sessionnew-opening-a-conversation session/new , opening a conversation A session is a conversation with its own history. The client asks for one, giving the working directory always an absolute path and, optionally, a list of MCP servers: {"jsonrpc":"2.0","id":1,"method":"session/new","params":{ "cwd":"/home/me/projects/mini-me", "mcpServers": }} {"jsonrpc":"2.0","id":1,"result":{"sessionId":"bob-51234-1"}} The agent chooses the session identifier. From then on, this identifier comes with every message about this conversation by the way, one agent can hold several sessions in parallel . Step 3: session/prompt , the user asks a question step-3-sessionprompt-the-user-asks-a-question session/prompt , the user asks a question The user types their question in the editor. The client sends it as content blocks : the text becomes a text block, and a file mentioned with @ becomes a resource link block: {"jsonrpc":"2.0","id":2,"method":"session/prompt","params":{ "sessionId":"bob-51234-1", "prompt": {"type":"text","text":"explain this file "}, {"type":"resource link","name":"acp.go","uri":"file:///home/me/projects/mini-me/internal/acp/acp.go"} }} It's a request it has an id , so the client expects a response. But that response will only come at the end, when the agent has finished its work. In the meantime, the agent doesn't stay silent... Step 4: session/update , the agent tells what it's doing step-4-sessionupdate-the-agent-tells-what-its-doing session/update , the agent tells what it's doing While it works, the agent doesn't wait until the end to "talk". It sends session/update notifications no id , so no response expected , which the editor displays "as they come". For example, a piece of the model's answer, streamed: {"jsonrpc":"2.0","method":"session/update","params":{ "sessionId":"bob-51234-1", "update":{"sessionUpdate":"agent message chunk","content":{"type":"text","text":"I'm reading the file "}} }} The sessionUpdate field says what it is about. The most common ones: | sessionUpdate | What it is | |---|---| | agent message chunk | A piece of the model's answer. | | agent thought chunk | A piece of the model's "thinking", displayed separately. | | tool call | A tool starts for example bash: go test ./... . | | tool call update | The tool progresses or ends completed or failed , with its result, some text or a diff . | | plan | The list of steps the agent intends to follow. | | available commands update | The slash commands /new , /compact ... the agent handles itself. | 🥰 A nice detail: when the agent modifies a file, it sends a diff the path, the old text and the new one , and the editor takes care of displaying it nicely. Step 5: session/request permission , the agent asks for permission step-5-sessionrequest permission-the-agent-asks-for-permission session/request permission , the agent asks for permission Now let's look at the request that goes the other way , and in particular the one asking for a permission, from the agent to the code editor. Before running a somewhat sensitive command running tools , the agent asks for the user's approval. It describes the tool call concerned and offers options: {"jsonrpc":"2.0","id":7,"method":"session/request permission","params":{ "sessionId":"bob-51234-1", "toolCall":{"toolCallId":"call-2","title":"bash: rm -rf build/","kind":"execute","status":"pending"}, "options": {"optionId":"once","name":"Allow once","kind":"allow once"}, {"optionId":"always","name":"Allow bash for this session","kind":"allow always"}, {"optionId":"reject","name":"Reject","kind":"reject once"} }} The editor shows a dialog to the user, then answers with the chosen option: {"jsonrpc":"2.0","id":7,"result":{"outcome":{"outcome":"selected","optionId":"once"}}} There are four kinds of options: allow once , allow always , reject once and reject always . What "always" means is up to the agent. Step 6: the end of the turn, and why it stopped step-6-the-end-of-the-turn-and-why-it-stopped When the agent has finished its work, it finally answers the session/prompt request from step 3 same id , 2 . And this response does not contain the text of the answer it was already sent piece by piece : it only says why the turn stopped . {"jsonrpc":"2.0","id":2,"result":{"stopReason":"end turn"}} | stopReason | Meaning | |---|---| | end turn | The model has finished talking and calls no more tools. | | max tokens | The response's token limit was reached. | | max turn requests | The agent reached its maximum number of model calls for this turn. | | refusal | The model refused to continue. | | cancelled | The client cancelled the turn. | What if we change our mind? session/cancel what-if-we-change-our-mind-sessioncancel session/cancel If, for example, the user presses Esc, the client the code editor sends a notification no id , no response expected : {"jsonrpc":"2.0","method":"session/cancel","params":{"sessionId":"bob-51234-1"}} The contract on the agent side is precise: stop what it's doing, run nothing more, and still answer the pending session/prompt request, with "stopReason":"cancelled" . An agent that "forgets" to answer would leave the editor in a shaky state, waiting for a response that will never come. Requests or notifications: the summary requests-or-notifications-the-summary If you should remember only one thing from this article, it may be this table: | Message | Direction | Kind | |---|---|---| | initialize | editor - agent | request | | session/new | editor - agent | request | | session/prompt | editor - agent | request the response comes at the end of the turn | | session/update | agent - editor | notification | | session/request permission | agent - editor | request | | session/cancel | editor - agent | notification | What about the rest? what-about-the-rest I won't go into every detail here, but know that the coding agent can also: - use the editor's services , if the editor declared them at initialization: read or write a file fs/read text file , fs/write text file , which lets it take into account the editor's not-yet-saved content, or open a real terminal in the editor terminal/create ... ; - reload an existing session session/load , if it declared loadSession: true : it must then "replay" the whole conversation to the editor, without running the tools again; - connect to the MCP servers the editor passes to it in session/new , or ignore them if it has no use for them. So ACP and MCP are complementary: ACP links the editor to the agent, MCP provides tools to the agent. ACP actually reuses MCP's JSON representations where they apply text, image and resource blocks . And where does Mini Me fit in? and-where-does-mini-me-fit-in Mini Me https://mini-me.org/ is an AI agent dedicated to using local language models that "speaks" ACP: it implements the agent role, with an ACP front in internal/acp , written in Go with the community SDK github.com/coder/acp-go-sdk . In practice, this means the editor launches Mini Me , talks to it in JSON-RPC over stdio, and Mini Me : - streams the model's answers with agent message chunk ; - runs its tools itself for now it doesn't use the editor's file/terminal services and reports on them with tool call and tool call update ; - asks for permission with session/request permission , and when you choose "always", it remembers it per tool name, for the duration of the session ; - offers its own slash commands, like /new clear the history and /compact compress the history , which it handles without going through the model. And what's coming next for the protocol? and-whats-coming-next-for-the-protocol ACP moves fast. A v2 is in the works a first draft https://agentclientprotocol.com/announcements/acp-v2-draft was published on July 20, 2026 , where the session becomes the central unit rather than the "one prompt, one answer" round trip. But it's a draft: the maintainers ask to keep v1 and advise against making it the default behavior in production. So everything described in this article is about version 1 of the protocol, the one we use today. To conclude to-conclude ACP comes down to a handful of ideas: - an agent launched as a subprocess, which speaks JSON-RPC over stdio, one message per line; - a "handshake" 🤝 initialize where each side announces what it can do; - sessions, and "speaking" turns session/prompt that end with a stop reason; - a stream of notifications session/update to show everything as it happens; - requests in both directions, for permissions and the editor's services. And the promise: you choose your agent without changing your editor, and vice versa. See you very soon for a next article. Written by Keep reading Zed Editor + Docker Agent + ACP: coding with a local agent plugged into llmman Plug Zed's agent panel into Docker Agent over ACP, then point it at llmman to chat with a fully local, OpenAI-compatible coding agent. Oct 3, 2026 A mini code agent with Docker Agent - Part 8: a local agent to learn Rust Assemble a local Rust learning companion with Docker Agent: a cookbook RAG, a rust-analyzer LSP toolset, and the context caps that keep a small model alive. Aug 11, 2026 A mini code agent with Docker Agent - Part 5: the mcp toolset Plug external MCP tool servers into a local Docker Agent: a local stdio server, a remote streamable HTTP one, and a tools filter to keep a small model focused. Aug 6, 2026 From other blogs