cd /news/agent-protocols/acp-from-scratch Β· home β€Ί topics β€Ί agent-protocols β€Ί article
[ARTICLE Β· art-161694] src=k33g.org β†— pub= topic=agent-protocols verified=true sentiment=↑ positive

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.

read12 min views1 publishedOct 31, 2026
ACP from scratch
Image: K33G (auto-discovered)

You have a coding agent you like (mine is Mini Me, my little coding agent side project), and a code editor you don't want to leave (in my case it's rather VSCodium, but when I need to stay "focused" I use Zed). 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 acoding agent ,
  • and where Mini Me fits in all this (there's no reason I shouldn't advertise my side project πŸ˜‰).

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

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 #

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

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

Each message is a JSON object, and there are three kinds:

  • a request carries anid and amethod : it expects a response;
  • a response carries thesameid , and aresult (or anerror );
  • a notification carries amethod butno 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

  • 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 #

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

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

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

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

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 orfailed ), with its result, some text or adiff .
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

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

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

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 #

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? #

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 declaredloadSession: 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 insession/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? #

Mini Me 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 andtool_call_update ;
  • asks for permission with session/request_permission , and when you choose "always", it remembers itper 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? #

ACP moves fast. A v2 is in the works (a first 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 #

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

── more in #agent-protocols 4 stories Β· sorted by recency
── more on @agent client protocol 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/acp-from-scratch] indexed:0 read:12min 2026-10-31 Β· β€”