# ACP clients

> How an editor or harness drives Fermix over the Agent Client Protocol: connecting a client, what a session is, what the surface deliberately does not carry, remembered client identities, and turning it off.

Fermix answers the Agent Client Protocol (ACP, the protocol an external client speaks to drive an agent), so an editor such as Zed, or a coding harness such as Buzz (a client that hands repository work to an agent and posts the result back), can use your Fermix as its agent. The client launches `fermix acp`, which bridges its standard input and output to the already-running daemon (the background Fermix service) over a Unix socket (a local channel between processes on the same machine) at `~/.fermix/acp.sock`.

The surface is on by default, and a client that connects gets full [operator trust](/docs/ingress-and-trust) — the same level as the local CLI. That is safe only because of what the socket is: it is created with permissions `0600` (readable and writable only by you), it listens on no network port, and anything able to write to it is already running as your own account. It is the same boundary as the daemon control socket that is always present. If that is not the boundary you want, turn the surface off — see [turning it off](#turning-it-off).

## Connecting a client

`fermix acp` is a bridge, not a second Fermix. It requires the daemon to already be running and never starts one, because a second Fermix would fight the daemon's single database writer. With nothing listening it prints one line and exits non-zero:

```
Fermix daemon not running (or [fermix_channels.acp] disabled) — start it with `fermix run`; socket: /Users/you/.fermix/acp.sock
```

Most ACP clients ask for a command and its arguments. Set the command to `fermix` and the argument list to a single `acp`:

```bash
fermix acp
```

If your `FERMIX_HOME` is not the default `~/.fermix`, set it in the client's own environment as well. A client launched from a desktop icon inherits nothing from your shell, so it would otherwise look for the socket in the wrong place.

The bridge never retries. If the daemon goes away mid-session it prints one line and exits non-zero, and restarting is the client's job — respawn policy belongs to the client, not to Fermix. Any argument other than `forget` is a usage error on standard error, never a bridge, so a client configured with stray arguments fails loudly instead of pumping protocol down a connection that will not work.

## What a session is

Each ACP session is an ordinary Fermix conversation. It runs through the same agent loop as a chat message, with the same system prompt, the same tools, the same [sandbox](/docs/sandbox), the same [memory](/docs/memory), and the same [traces](/docs/traces-and-telemetry). Nothing about the surface is a second agent bolted on the side.

Opening a session requires an absolute working directory; a relative path is refused. Replies stream back as they are produced, and tool activity arrives as the client's own tool cards. Fermix runs one prompt at a time per session and refuses a second while the first is in flight.

Sessions do not resume. Fermix advertises no session loading, so reconnecting a client starts a fresh conversation rather than reattaching to the previous one.

Fermix answers ACP wire version 1 whatever version a client announces. That is the protocol's own negotiation rule: the client reads the answer and decides for itself whether it can proceed.

## What this surface does not carry

Several things a chat channel has are absent here by design, and each fails in a stated way rather than silently doing nothing.

- **Slash commands** — the whole command pipeline is off on this surface, so `/sandbox`, `/skills`, `/compact` and the rest do not exist. A message starting with `/` is ordinary text to the model. Use another [channel](/docs/channels) or the CLI for those.
- **In-chat approvals** — Fermix never asks the client for permission, and the tool that requests directory access is not offered. A [sandbox](/docs/sandbox) refusal on an ACP turn is final. Grant the path from an owner surface first, with `fermix grant path <path>` or from a chat channel, then ask again.
- **Callbacks into the client** — Fermix never asks the client to read or write files, and never asks it to run a terminal command. A client's permission prompts and filesystem bridges simply go unused.
- **Session-scoped MCP servers** — a session that tries to attach its own [MCP](/docs/mcp) servers is refused by name, and told to configure MCP in Fermix's own config instead.
- **Anything but text in** — only text and resource links are accepted. Images, audio, and embedded resources are refused with a message naming what the surface does accept.
- **Attachments out** — a reply that would carry a file renders as a single line naming the file and saying it is not transferable over this surface.
- **Origin delivery for scheduled jobs** — a [scheduled job](/docs/scheduled-jobs) created from an ACP session cannot deliver back to that session, because the session ends when the client closes. The refusal says so and points at naming an explicit delivery target on a configured channel, or `none`.

One more behavior is worth knowing because it looks like an error and is not: when a turn has already published something through a tool and then fails, the client is told the turn completed rather than that it errored. That is deliberate — an error would invite the client to retry and publish a second time. The real failure is in the daemon log.

## Client identities and delegated coding runs

An ACP client can present a posting identity — a signing key it uses to publish on the user's behalf. Fermix remembers it, keyed by its public key identifier (an `npub`), so a piece of work started in one session can still be delivered after that session ends.

This is what gates delegated coding work. The [coding agents](/docs/coding-agents) tools are advertised on an ACP turn only when the client presented a usable identity. A client that presents none — an editor such as Zed — sees no coding-agent tool at all, and so does a client whose key is present but malformed.

A delegated run does not come back as the answer to the prompt that started it. The turn returns a run id straight away, and the outcome arrives later as a new message published with the client's own credentials. If the identity was forgotten between starting the run and its finishing, delivery is refused with a named reason and the run record carries the error; reconnecting the client is the fix.

Remembered identities are files under `~/.fermix/acp_identities/`, one per client, readable only by you. They outlive the connection, they survive a daemon restart, and turning the ACP surface off does not delete them. `fermix acp forget` is the only thing that does:

```bash
fermix acp forget npub1...   # forget one client
fermix acp forget --all      # forget every remembered client
```

That command reads the files directly, so it works whether or not the daemon is running. A record whose permissions are not exactly owner-read-write is refused, and the connection runs without an identity for the rest of its life; `chmod 600` on the file named in the error and reconnect the client to fix it.

`fermix doctor` prints an ACP row. With the surface on it shows the socket path and whether the listener is actually up; with the surface off it reports only `disabled`, because neither is asked of the daemon. Either way it lists every remembered client with when it was last seen, and it never prints key material.

## Turning it off

The surface has one setting, and `enabled` is all of it. There is no credential, no allowlist, and no owner id to configure, because trust comes from the socket's permissions rather than from a sender id.

```toml
[fermix_channels.acp]
enabled = false
```

The setup wizard's **Channels** step carries the same choice as an **Accept ACP clients** toggle. Either way the listener reflects the change after a daemon restart, not immediately. Turning the surface off stops new connections; it does not delete remembered identities.

## Limits and failure modes

| Limit | Behavior |
|-------|----------|
| Concurrent clients | 64. A connection past the cap is refused with a message naming the cap, then closed — never a silent hang. |
| Daemons per home | One. A live socket at the path is refused rather than taken over; a stale socket left by a crashed daemon is cleaned up automatically. |
| Socket path length | The operating system caps a Unix socket path at 103 bytes on macOS and 107 on Linux. A very long `FERMIX_HOME` exceeds it, and ACP is then disabled for that boot with a log line naming the path and the fix. Everything else on the daemon keeps running. |
| Failure to bind | ACP is disabled for that boot only. The daemon logs one error naming the path and the reason, and boots normally without it. |
