Ctrl K
ComunidadGuías…

docs/src/acp-server.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Moltis as an ACP Agent

Moltis has long been an ACP *client*: it spawns and drives external agents such
as `codex-acp`, `claude-agent-acp`, and Cursor's `agent acp`. See
[External Agents](external-agents.md) for that direction.

`moltis acp` is the inverse. It lets any ACP *client* — [Zed](https://zed.dev),
Block's `buzz-acp`, or a bespoke harness — drive Moltis the same way it would
drive any other agent, while Moltis's own stack (sessions, memory, tool policy,
sandboxing, providers) stays behind the protocol.

## Usage

```bash
moltis acp
```

The command speaks JSON-RPC over stdin/stdout and serves exactly one client per
process, matching how every ACP harness spawns agents.

A prompt runs a real Moltis turn — the same path the Web UI takes, with your
configured providers, tools, memory, and session history — and streams the reply
back as `session/update` notifications while it runs.

### No server required

`moltis acp` boots the Moltis stack inside the spawned process and binds no
socket, so you do not start `moltis serve` first and it will not take a port.

Startup uses a headless profile. Providers, tools, memory, sessions, and
configured MCP servers are ready before the protocol starts, while channel
accounts and server-only background workers are not started.

Headless startup does not create or inject a gateway API key. There is no
gateway listener for sandboxed tools to call in this mode.

If sandbox networking is set to `trusted`, HTTP tools fail closed in ACP mode.
The trusted-domain approval proxy is not started because ACP has no domain
approval UI and the headless process does not bind listener ports.

It does open the databases under the data directory, which is what lets an ACP
session show up in the Web UI's session list. A gateway running at the same time
shares that state rather than being talked to over a socket.

### Checking a client without a model

```bash
moltis acp --echo
```

`--echo` serves a built-in agent that echoes the prompt back, without loading
providers, databases, or tools. Use it to verify a client's handshake in
isolation — if `--echo` works and a real run does not, the problem is your
Moltis configuration rather than the client.

## Why this is a surface, not a channel

ACP sits beside the Web UI and GraphQL rather than alongside Telegram or Nostr.
A channel exists to manage *external correspondents* — allowlists, sender
identities, OTP flows, per-account settings. An ACP client is the trusted local
parent process that spawned Moltis: there is no sender to gate and no account to
configure, so none of the channel contract applies. Do not expose the stdio
stream to an untrusted process. The controller can ask Moltis to use configured
tools and can provide stdio MCP servers, which is equivalent to granting that
controller the current user's privileges.

### stdout is the wire

Anything written to stdout that is not protocol framing corrupts the stream and
the client disconnects with a parse error. `moltis acp` therefore redirects all
logging to **stderr** for the lifetime of the process, including at
`--log-level trace`. If you add output to any code path this command reaches,
send it to stderr.

### Plugging into a client

Any harness that spawns an ACP agent binary can point at Moltis. For example,
`buzz-acp` spawns `$BUZZ_ACP_AGENT_COMMAND`:

```bash
BUZZ_ACP_AGENT_COMMAND=moltis BUZZ_ACP_AGENT_ARGS="acp" buzz-acp
```

Buzz is one client among several here, not a dependency — Moltis's Nostr channel
integration is a separate, unrelated feature.

## Sessions

ACP sessions map onto Moltis session keys in a dedicated `acp:` namespace, so a
`moltis acp` run cannot collide with a Web UI or channel session. The ACP
`SessionId` *is* the Moltis session key, which means a client can hand an id
straight back to `session/load` to resume a conversation.

The namespace is enforced, not merely conventional: `session/load` rejects any
id outside `acp:` with `invalid_params` before the backend is consulted, so a
client cannot name a Web UI or channel session and drive it. `session/new`
likewise refuses to return a key a backend minted outside the namespace.

The absolute `cwd` supplied by the ACP client becomes the session's Moltis
project and the working directory used by filesystem and execution tools. The
directory must exist. Loading an existing session rebinds it to the `cwd` in
that load request.

### Client-provided MCP servers

`session/new` and `session/load` accept stdio MCP servers. They are scoped to
that ACP session: their tools are added only to that session and they are shut
down when the client disconnects or replaces the session setup. HTTP and SSE
MCP transports are not advertised or accepted.

Each MCP command must be an absolute path. This makes executable selection
deterministic; it is not a sandbox. The command runs in the session `cwd` with
only the environment variables explicitly supplied in the ACP request and does
not inherit the Moltis process environment. Server names and environment
variable names must be unique within the request. Internally, client MCP tools
receive a session-specific namespace so they cannot override configured tools
or impersonate an MCP server allowed by an agent preset.

## Resource limits

The stdio surface rejects JSON-RPC frames over 4 MiB, prompts over 1 MiB or 256
blocks, and MCP setup data over 1 MiB. One connection may retain up to 64
sessions and run up to four session operations concurrently. History replay and
turn updates each have an 8 MiB output budget; persisted history reads stop at
that limit before parsing or allocating the remainder of the session file.
These limits bound accidental or malicious memory use and prevent one
controller from starting an unlimited number of simultaneous provider calls.

Protocol payload bodies are never written to Moltis logs, including at trace
level. MCP response bodies and child-process stderr are likewise omitted because
they may contain prompts, tool results, or credentials. In ACP mode, payload-
processing tracing targets are hard-filtered from both stderr and the in-memory
gateway log buffer, so a more-specific `RUST_LOG` directive cannot re-enable
them.

## Protocol support

| Method | Status |
|---|---|
| `initialize` | Supported; negotiates the version and advertises capabilities |
| `authenticate` | No-op — the client is a trusted local parent process |
| `session/new` | Supported; returns a namespaced Moltis session key |
| `session/prompt` | Supported; streams `session/update` notifications, then returns a stop reason |
| `session/cancel` | Supported; aborts the in-flight turn, resolving `prompt` with `cancelled` |
| `session/load` | Supported when the backend can resume; rejects ids outside the `acp:` namespace, then replays history before responding |
| `fs/*`, `terminal/*` | Not requested from the client |
| `session/request_permission` | Not yet routed through Moltis's tool gate |

Unknown session ids are rejected with `invalid_params`.

Only text and resource-link prompt blocks are accepted. Image, audio, and
embedded-resource blocks are rejected because those capabilities are not
advertised.

### What a turn streams

Moltis broadcasts a run's progress as the same events the Web UI renders. They
map onto `session/update` like this:

| Moltis | ACP |
|---|---|
| reply tokens | `agent_message_chunk` |
| reasoning | `agent_thought_chunk` (sent incrementally) |
| tool call started | `tool_call` with status `in_progress` |
| tool call finished | `tool_call_update` with status `completed` or `failed` |

Tool updates include capped raw arguments and output or error content. When a
turn finishes, Moltis reconciles streamed reply chunks with the authoritative
final message and emits any missing suffix before returning `end_turn`.

Web-UI affordances without an ACP equivalent — queueing, iteration counters,
voice-pending markers — are dropped rather than shown, so the client's
transcript is what the agent actually said.

> **Tool calls are reported, not gated.** A client sees tools as they run but
> cannot veto them: `session/request_permission` is not yet wired to Moltis's
> tool policy, so tool approval remains governed by your Moltis configuration,
> not by the ACP client. If you need a client to approve individual tool use,
> that is not available yet.

## Architecture notes

The protocol crate declares its traits with `#[async_trait(?Send)]`, so the
handler is pinned to the thread running a `tokio::task::LocalSet`, while
Moltis's services are `Send + Sync`. The `AcpBackend` trait in `crates/acp` is
where the two meet: implementations are `Send + Sync` and never learn a
`LocalSet` exists, and streaming flows back through a plain channel that the
protocol layer drains while the turn runs.