small gleam coding and (not yet) persistent agent daemon with a detachable cli forked from okami.mom/albedo
albedo docs acp.md
6.2 kB · 120 lines
Markdown
at acp

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.