AI Engineer Europe 2026
Building an ACP-Compatible Agent Live — Bennet Fenner, Zed
Read the talk
Building an ACP-Compatible Agent Live
A minimal TypeScript agent gains sessions, streaming text, visible tool calls, editor-aware file access and a terminal tool through the Agent Client Protocol.
From a talk by Bennet Fenner
Before you start: Familiarity with TypeScript, asynchronous events and basic model tool calling will help you follow the implementation.
Start with the model loop
The starting code is TypeScript, although Fenner introduces himself as a Rust developer with little TypeScript experience. It already implements a minimal coding agent, with two tools and no ACP support. A read tool accepts a file path and returns its contents. An edit tool accepts a path, the old text to find and the new text to substitute into an existing file. These tools provide the ability to inspect and change code before any editor integration exists.
The agent uses Anthropic's API. Because model requests are stateless, the agent maintains the conversation and sends the accumulated history with each request. Its loop follows a small procedure:
- Append the user's prompt to the conversation and call the model.
- Receive the model's response. An
end_turnstop reason means the turn is finished. - If the model requests a tool, execute the read or edit locally and collect its result.
- Add that result to the conversation and call the model again.
The tool handler initially reads directly from the filesystem. ACP does not need to replace this reasoning-and-tool loop; it needs to connect the loop to a client.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Give the agent an ACP session
Fenner begins the conversion with a goal of doing it in ten minutes, using boilerplate from the ACP TypeScript SDK. The recording uses an interface-based implementation of an agent. Current SDK documentation instead recommends fluent agent() and client() APIs and deprecates the older connection classes; the walkthrough here retains the recording's interface-based structure.
The first method is initialize. The demo returns its latest supported protocol version and keeps capabilities minimal. More precisely, initialization negotiates a version: an agent returns the client's requested version if it supports that version, otherwise its own latest supported version. Both sides can advertise capabilities. The demo needs no interactive authentication flow because it obtains its Anthropic API key from an environment variable.
A session corresponds to a thread in the editor. Creating one generates a random identifier, instantiates the coding agent with the working directory supplied by the client, stores that instance in an internal map and returns the identifier. Subsequent prompts use that ID to reach the same agent and its conversation history.
A prompt request carries the session ID and an array of content blocks. ACP can carry text, images and other content, but this agent supports only text. The adapter looks up the session's agent, filters out non-text blocks and passes the resulting text to the existing prompt function. That starts the model-and-tool loop described above.
Fenner also wires cancellation through to the session's agent as a convenience. At this point the adapter has the basic path from initialization to session creation, prompting and cancellation. The completed code on screen shows prompt handling followed by the cancellation method that retrieves the agent and calls its cancel method.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
A completed prompt with no visible answer
Zed does not need special code for this agent. Its configuration specifies an ACP-compatible agent launched with Node and the path to the compiled JavaScript file. Fenner builds the project, launches a fresh instance and enters a prompt. Zed shows a wait indicator, then finishes without displaying an answer.
The ACP debug view explains the missing piece. Zed sends session/new, receives a session ID, sends a prompt and gets a stop reason. Red adapter errors also appear in the view. Anthropic is producing output behind the scenes, but the agent has not forwarded that output to Zed. Handling the prompt request is not the same as publishing the response content. The adapter needs to send notifications while the turn is running.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Forward model text as session updates
To make output visible, Fenner passes the ACP connection and the session ID into the coding agent. The Anthropic SDK exposes text events through stream.on. Each event becomes a session update: a notification associated with the conversation, rather than the final response to the original prompt request.
The update type for model text is agent_message_chunk. Its content contains the new text, and its enclosing message identifies the session. The TypeScript wiring has this shape, with stream, connection and sessionId supplied by the agent's prompt handler:
typescript
stream.on("text", (text: string) => {
void connection.sessionUpdate({
sessionId,
update: {
sessionUpdate: "agent_message_chunk",
content: {
type: "text",
text,
},
},
});
});
The client can react to these notifications as they arrive; it does not have to wait for the prompt's stop reason to display the answer.
After rebuilding and restarting the agent, Fenner sends a greeting. This time Opus responds visibly in Zed, with individual chunks streaming into the conversation. The model loop was already producing text; the added notification path is what gives the editor something to render.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Introduce a tool call before updating it
Text output alone does not expose what a coding agent is doing. Fenner asks the agent to read helpers.ts. Its existing tool runs, and the model describes the file, but Zed does not show the read operation itself. The next layer is to publish tool activity through session updates too.
The agent first emits a tool_call update. It includes an identifier, a title, metadata describing the operation, an in_progress status and a file location. Those fields let Zed give the operation a useful presentation, including an appropriate icon and a connection to the affected file. The selected frame shows this initial read notification being added to the handler.
Once the client knows about that call, the agent sends tool_call_update messages for the same identifier. A later update can change the status and provide result content.
| Update | Purpose | Typical contents |
|---|---|---|
tool_call | Introduce the operation | ID, title, kind, status, locations |
tool_call_update | Update that operation | Same ID, new status, result content |
The ordering matters: the initial notification establishes the object the client will display; subsequent notifications change that object instead of introducing unrelated operations.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Read the editor's version of a file
The read handler still calls fs.readFile, which reads the file on disk. ACP offers a different path: when the client advertises the relevant filesystem capability, the agent can ask the client to read the file for it. This is explicit client-mediated file access, not automatic interception of filesystem calls.
The reason is an ordinary editor state: unsaved changes. A buffer can contain code that does not yet exist on disk. Calling readTextFile over ACP lets the editor return that current content, so the agent works with the version the user is looking at. The read capability must be available; read and write support are separate capabilities.
typescript
const { content } = await connection.readTextFile({
sessionId,
path: absolutePath,
});
The agent's read tool still returns text to the model. What changes is which component supplies that text.
Fenner runs npm run build, restarts the demo agent and asks it to read the file again. The read tool calls now appear in Zed. The test also exposes duplicated output, which remains visible as the demonstration moves on.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Show an edit as a diff
The read result now exposes the file's actual content in the client. Fenner applies analogous tool notifications to the edit handler, compiles again and restarts the agent. He asks it to add a Hello World comment at the top of src/agent.ts.
The edit produces a diff in Zed because the agent sends diff content over ACP. The integration has progressed from displaying model text to displaying a concrete code change. Duplicated initial tokens are still present; Fenner suspects something is wrong with the connection but does not debug the cause during the session.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Use the agent to add its next tool
With reads and edits working, the agent can modify its own implementation. Fenner gives it a prepared prompt asking it to add a terminal tool to itself. The prompt supplies information about the relevant ACP APIs, so the agent can use its existing file tools to implement the new capability.
Terminal support follows the same capability boundary as file access. A client can advertise that it creates and manages terminals for an agent. An agent could instead manage terminals itself, but using the client gives the editor a way to expose the running operation interactively. In the demo, the agent begins by adding a new tool description and proceeds to implement the terminal tool.
The generated implementation builds successfully. Fenner restarts the agent and asks it to wait five seconds and then list the directory. Zed displays a running terminal, followed by its output. This completes a useful progression: the original read and edit tools are enough to build another tool, while ACP gives the client a way to present the new operation.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
The demo's handoff
Fenner describes the ACP conversion as taking about fifteen minutes. He points viewers to the protocol site, but explicitly warns against using the agent-generated demo code in production. At recording time, the companion repository is empty; uploading the code is his stated next action. The closing repository slide is a handoff address, not evidence that the code is already available.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
What the client computes, and how it connects
The first audience question clarifies who computes the diff. ACP supports a structured diff content type: the agent sends the file path and its old and new text, and Zed computes the displayed difference. The agent therefore does not need to generate the editor's visual diff or send a precomputed patch. It supplies the two versions; the client owns their presentation.
The final question asks whether the connection is local. Fenner specifies standard I/O as the transport used by the demonstrated implementation. He says remote-transport work is underway, tentatively attributing it to JetBrains contributors, and expects it soon. That is a forecast, not a capability demonstrated here. The working connection in this session is the editor launching the agent process and communicating with it over standard input and output.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Resources
From the talk
Overview of the protocol connecting coding agents to editors and other clients.
Connect an ACP editor to OpenClaw or exercise its bridge with the built-in debugging client.
Run Cursor as an ACP agent and connect a custom client over standard I/O.
Further reading
Protocol-version negotiation and capability declarations for both sides of an ACP connection.
Message examples for tool progress, results, file locations and old/new-text diffs.
Read and write methods that let agents work with files and unsaved editor state.
Updates since the talk
Installation, runnable examples and current fluent APIs for implementing ACP agents and clients.
Read the transcript and explore slides
Loading transcript and slides…