ACP editor integration #
albedo acp connects Zed and other Agent Client Protocol
editors to the normal albedo daemon. The stdio frontend reuses the authenticated,
OpenAPI-generated client; it does not run another agent loop or transcript store.
Connect from Zed #
Configure a provider with albedo login, then register the installed executable:
{
"agent_servers": {
"albedo": {
"type": "custom",
"command": "/absolute/path/to/albedo",
"args": ["acp"],
"env": {}
}
}
}
For a checkout, use its built cli/bin/albedo and set ALBEDO_ROOT in env to
the absolute checkout path. ALBEDO_DAEMON selects a packaged daemon as usual;
see daemon lifecycle. The session's cwd selects the
workspace, not the executable's launch directory. Zed's dev: open acp logs
command shows the wire exchange. Stdout is protocol-only; diagnostics use stderr.
Sessions, context and tools #
The adapter speaks stable ACP wire version 1. It supports independent sessions, text, resource links, images and embedded resources, streamed assistant and thought messages, tool activity, cancellation and durable history loading. PNG, JPEG and WebP images use the same admission and provider-fitting machinery as native CLI attachments. Embedded resource text and binary context retain their URI/MIME metadata; resource links remain references for native tools to retrieve. ACP conversations are normal durable albedo sessions and remain available after an editor disconnects. Resume does not replay history; load does.
Editor buffers #
When the editor advertises fs.readTextFile and fs.writeTextFile, normal
files.read, files.write and files.edit use its callbacks. Reads see unsaved
content, including named buffers with no disk file. Helper signatures and the
ready/awaitable text result are unchanged. A callback error or disconnected
required client is never interpreted as permission to fall back to stale disk.
Capability combinations are respected. Whole-file writes can use a write-only client; partial edits require both read and write capabilities if either is editor-backed, so disk text cannot overwrite a different unsaved buffer. Unbound native and remote file helpers keep their existing behavior. File search and arbitrary direct Python filesystem access are not editor-buffer APIs.
ACP v1 does not expose a versioned compare-and-swap file operation. files.edit
rechecks current text before writing, but a concurrent user edit between that
check and application is not an atomic transaction. Callback text is bounded to
512 KiB; oversized buffers fail explicitly rather than being truncated and saved.
Permissions #
Every model tool invocation requests approval before execution, including Python source and direct MCP tools. Denial, cancellation, malformed decisions and unknown option IDs do not start the tool. Nested Python MCP calls have their own approval. Waiting for a person does not block the protocol reader or another session, and has no short human-response timeout.
This is approval mediation, not a Python sandbox. Approving a Python call allows
that source to execute arbitrary native code; the prompt shows its complete
input. Shell commands, pathlib and other direct operations inside approved
Python remain native operations, not individually intercepted editor tools.
Editor-provided MCP #
Stdio and streamable HTTP servers supplied in mcpServers are forwarded to the
existing daemon MCP implementation. Literal environment variables and HTTP
headers belong only to that binding. All requested servers must prepare; a failed
server fails installation instead of silently dropping its tools. Client-only
activation does not start globally configured but deselected servers.
Transient descriptors, credentials and discovered catalogues are not saved into global extension settings, credential files or MCP catalogue rows. Names that collide with configured global servers are rejected rather than overriding them. Disconnect closes and joins the transient transports, including stdio processes. Native MCP servers already selected by the user retain their normal behavior.
Disconnects and cancellation #
An interrupted prompt waits for its captured input/turn to settle; an admission receipt or interruption acknowledgement is not completion. The real run stop latch cancels pending approvals and is checked again before invoking a tool. Late replies cannot authorize a different request or binding generation.
The frontend renews a 15-second binding lease every five seconds. EOF, process
loss and lease expiry cancel callbacks, release subscriptions and close transient
transports. They never delete the conversation or stop the shared daemon.
The nonsecret approval-required policy remains sealed after disconnect or daemon
restart: opening a native CLI does not silently bypass editor approval. Rebinding
an ACP client restores interactive access. Explicit idle native adoption is
available through the client-binding API's DELETE with adopt_native=true.
Read/write callbacks have a 20-second deadline, below the synchronous Python host-call limit. A timed-out already-dispatched write has an unknown outcome; its late reply is ignored and it is not automatically sent again. ACP clients are responsible for honoring request cancellation; acknowledgement alone cannot prove arbitrary external effects were undone.
Protocol boundaries #
Audio and draft v2/subagent extensions are not advertised. Buffers, frames, callback queues, descriptors and receipts have explicit byte/count bounds. Unsupported or malformed input produces an error, not a guessed success. When provisional output is replaced by a provider retry or stream reset, the v1 frontend refuses to append duplicate text; reconnect and load durable history.
The reusable session client-binding resource is declared in
OpenAPI. It owns portable approval/read/write requests, while
cli/internal/acp alone maps them to ACP method names. Named daemon operations
still own session/input/history/stream recovery, and the daemon still owns
execution, credentials and persistence.