Connecting AI Agents to IDEs: The Agent Client Protocol
Karl Code started as a terminal application. You ran it in a shell, it read your files, it ran commands, it edited code. This worked. It was also mentally split-brained: your editor was in one window, your AI agent was in another, and the two communicated through you copy-pasting between them.
This is the fundamental UX problem with terminal-based AI agents. The agent can read and write files, but it cannot show you a diff inline. It can run a build, but it cannot jump to the error location in your editor. It can suggest a refactor, but it cannot highlight the range of code it wants to change. You are the bridge between two tools that should be talking directly to each other.
The Agent Client Protocol — ACP — is how I solved this. It is a standard that lets an AI agent communicate with a code editor as a structured message stream instead of a wall of terminal text. Karl Code speaks ACP, which means it can work inside any ACP-compatible editor. Right now that means Zed and VSCode, but the protocol is editor-agnostic.
What ACP Actually Is
ACP is not a library or a framework. It is a message format and a set of conventions for agent-to-editor communication. The editor launches the agent as a subprocess and communicates over stdin/stdout using JSON-RPC. Every message is a structured object — not free text.
The core message types are:
- Session initialization — the editor tells the agent what workspace it is in, what capabilities the editor supports, and what model to use.
- User message — the user types something in the editor's chat panel, and it gets forwarded to the agent.
- Assistant response — the agent replies with a mix of text, tool calls, and structured edit operations.
- Tool call — the agent requests an action: read a file, run a command, search code.
- Tool result — the editor executes the tool and returns the result.
- Diff — the agent proposes a code change as a structured diff that the editor can render natively.
The key difference from a terminal agent is that last one. In a terminal, a code edit is a sed command or a file rewrite that produces no visual feedback until you switch to the editor and reload. With ACP, the agent sends a structured diff, and the editor renders it as a native diff view — red lines, green lines, accept/reject buttons.
A terminal agent tells you what it changed. An ACP agent shows you what it changed, in your editor, using the same diff UI you already use for code review. That is not a cosmetic improvement. It changes how much you trust the agent.
The Tool Adapter Pattern
Karl Code has its own tool system — file operations, shell commands, code search, web fetch, all implemented internally. The editor also has its own capabilities — opening files, navigating to symbols, rendering diffs. The tool adapter pattern bridges these two worlds.
The adapter sits between the agent's internal tool protocol and the editor's ACP protocol. When the agent calls edit_file(path, old_text, new_text), the adapter translates that into an ACP diff message. When the agent calls read_file(path), the adapter checks whether the editor has the file open with unsaved changes and, if so, returns the unsaved buffer content instead of reading from disk.
This last point matters more than it sounds. In a terminal-only workflow, the agent reads from disk. If you have unsaved changes in your editor, the agent does not see them. It operates on stale data, produces an edit based on the old version, and you get a conflict. The adapter eliminates this entire class of problems.
The adapter is not a translation layer. It is a consistency layer. It ensures the agent and the editor agree on the current state of the code at all times.
The pattern looks like this:
Agent Tool Call ACP Message Editor Action
──────────────── ────────────── ─────────────
read_file("src/main.rs") → file.read request → Editor returns buffer
edit_file(...) → diff message → Editor renders diff
run_command("cargo test") → tool.run request → Editor runs in terminal
search("fn process") → workspace.search → Editor returns results
Each agent tool maps to one ACP message type. Some tools do not have an ACP equivalent — web_fetch, for example — and those run internally without involving the editor. The adapter only forwards tools that benefit from editor integration.
Streaming Changes Everything
The biggest difference between a terminal agent and an ACP agent is not the diff rendering. It is the streaming.
In a terminal, the agent produces output sequentially. You see the full response after the model finishes generating it. For a response that includes reading three files, running a test, and proposing an edit, you wait for the entire sequence to complete before seeing anything.
ACP supports streaming at the message level. The editor receives partial responses as the agent generates them. You see the agent's reasoning appear in the chat panel while it is still working. When it calls read_file, the editor opens the file in the background. When it proposes an edit, the diff appears before the agent has finished explaining the change.
This is not just nicer UX. It changes the interaction model. You can see the agent going down the wrong path and interrupt it before it wastes time. You can accept a diff while the agent is still writing the explanation. The feedback loop tightens from minutes to seconds.
Streaming does not make the agent faster. It makes the human's reaction faster. The agent generates at the same speed either way — but the human can steer, interrupt, and validate in real time instead of waiting for a complete response and reviewing it after the fact.
The Editor Launches the Agent
One detail that took me a while to get right: the editor should launch the agent, not the other way around. In the first version, I had Karl Code connect to a running editor instance via an extension. This was fragile. If the agent crashed, the editor was in a weird state. If the editor crashed, the agent had no one to talk to.
The ACP model is cleaner. The editor launches the agent as a subprocess. The editor owns the lifecycle. If the agent crashes, the editor detects the process exit and offers to restart it. If the editor closes, the agent's stdin closes and it exits cleanly.
This also means the agent does not need to know how to find the editor, connect to it, or manage the connection. It just reads from stdin and writes to stdout. The protocol is the interface.
When Not to Use ACP
ACP is not always the right answer. If your agent's primary output is a report — a summary of findings, a list of suggestions, a generated document — then a terminal is fine. The editor integration adds nothing because there is no code edit to render.
ACP also adds complexity. You need an editor extension, a message handler, and the adapter layer. For a personal tool that only you use, the terminal might be enough. The investment in ACP pays off when the agent is making frequent code edits that you want to review inline.
The right question is not "should my agent support ACP?" but "how often does my agent edit code that I want to review before it goes live?" If the answer is "most of the time," ACP is worth the integration cost. If the answer is "rarely," the terminal is fine.
The Principle
Tools that work in isolation are limited by their output format. A terminal agent can edit files, but it cannot show you what it edited in a way that leverages your existing review workflow. An ACP agent can, because it speaks a protocol that the editor understands.
The general principle is protocol over format. When two tools communicate via structured messages instead of formatted text, the receiving tool can do anything with the information — render diffs, jump to locations, populate search results, accept inline edits. When they communicate via formatted text, the receiving tool can only display it.
ACP is not the only protocol that does this. LSP did it for language servers. DAP did it for debuggers. ACP does it for AI agents. The pattern is the same each time: define a standard message format, let the tool speak it, and let the editor decide how to render the messages.
The result is an agent that feels like it lives inside your editor, not next to it.