Managoat is the hosted Fountain. Fountain is the open-source engine, and its name is on the CLI, the API, the SDK and this manual. Everything here applies to Managoat unless a page says it is for a self-hosted server.

fountain acp, the ACP agent reference

fountain acp is the one process that each ACP client of Fountain spawns. Editors such as Zed, OpenClaw, and the Buzz harness all speak the Agent Client Protocol to it over stdio. It then drives a conversation in a Fountain sandbox.

Those three pages cover how to configure the client. This page is the reference for the adapter itself. It says what the adapter accepts, what it does with that, and what it deliberately ignores. The three pages then do not each state it again.

What it is, and is not. It is a control surface for a conversation on your Fountain instance. Open it, prompt it, watch it, interrupt it, reopen it tomorrow. It reaches nothing on the machine it runs on, beyond its own stdio and the CLI's credentials. Each path it reports is inside the sandbox.

Invocation

fountain acp --agent <name-or-id> [--vault <name-or-id>] [--environment <name-or-id>] [--sandbox-mode persistent] [--sandbox <id>] [--permission ask] [--log-level debug]
Flag Meaning
--agent Required, in practice. The Fountain agent that each session runs. ACP has no field for it, so you configure it for each process. Use one client entry for each agent you want to reach.
--vault A vault that attaches to each conversation this process opens. Vault values override the agent's environment, so a secret for one entry belongs here. An identity the agent posts under is the example. Two entries on the same agent with different vaults stay apart.
--environment Provisions each conversation from this environment, and not from the agent's own. One agent config then runs under several environments, with one entry for each. The vault still wins on a key collision. When the agent sets allowed_environment_ids, the environment must be on that list.
--sandbox-mode Where each conversation this process opens runs. ephemeral gives each conversation a sandbox of its own. persistent puts each one on the agent's own machine. Without the flag, the agent's default applies. See Sandboxes.
--sandbox A sandbox id to attach each conversation to, instead of a new one. It must be yours, and it must have the same agent, environment and vault.
--permission What happens before the agent runs a tool. ask sends the question to your client, as an approval prompt. auto_deny refuses. The default, auto_allow, runs the tool. To narrow it for one kind of tool, give key=verdict pairs, such as execute=ask. See Permission prompts.
--log-level The verbosity on stderr. One of debug, info, which is the default, warn and error.
--profile Which saved CLI credentials to use. It is a global flag.

The credentials are the CLI's. They come from FOUNTAIN_API_KEY and FOUNTAIN_BASE_URL in the environment, or from the profile that fountain auth login saved. There is no login inside the protocol. authenticate verifies what the CLI already holds, and its one advertised method says only "run fountain auth login". A hosted Buzz harness gets a freshly minted, sprite-scoped key in its environment for exactly this reason.

stdout carries the protocol and nothing else. Diagnostics go to stderr. Your client's agent-server log shows them, and that is the first place to look when something is wrong.

The protocol surface

The protocol version is 1, and Fountain negotiates down to the client's version when that is lower. agentInfo is fountain, with the CLI's version.

Method What Fountain does
initialize The capability handshake. Fountain logs the client's own capabilities, which are fs and terminals, and uses neither. This agent works on a sandbox filesystem, and not on yours.
authenticate Verifies the CLI's saved credentials against the instance. Fountain advertises it only when it holds none.
session/new Resolves --agent. It refuses an agent whose runtime has no ACP adapter, which today is no runtime at all. Then it opens a conversation. The ACP session id is the conversation id, as below. It responds with the agent's model, as the one model available.
session/prompt Sends the turn, as text and images. It drops another block with a warning, and refuses a prompt where it can use nothing. It then streams the conversation's ACP output back as session/update notifications, until the turn ends.
session/cancel Interrupts the turn that runs.
session/load Reopens a conversation that this process did not start. It replays the stored session/update history before the response, as the spec demands.
session/set_model Not implemented. The model belongs to the Fountain agent. A change here would change each conversation on that agent.
session/request_permission (agent → client) Goes to your client when the policy for that tool is ask (#708). It carries the agent's own options. Your answer goes back to the agent. See Permission prompts.

Fountain advertises loadSession: true. On prompts it advertises image: true, audio: false and embeddedContext: false. A client cannot inline a local file, which would be context about a machine the agent cannot see.

Fountain logs cwd and mcpServers on session/new, and ignores both. The sandbox clones its own checkout, and a Fountain agent carries its own MCP configuration to the sandbox. Fountain can add MCP servers of its own to a session, and it injects the Buzz publish tools this way. It never adds the client's.

The session id is the conversation id

session/new returns the Fountain conversation id as the ACP sessionId (ADR 0015, #699).

That is what makes session/load work across processes and across days. An editor hands back an id from last week, and it resolves to a real conversation. It does not resolve to a map that died with the process that minted it. It is also why the same id appears in the web UI, in fountain conv, and in the audit trail.

What streams back

The sandbox runtime already speaks ACP to Fountain (ADR 0014). So the adapter is a proxy, and not a translator. It forwards agent_message_chunk, agent_thought_chunk, tool_call, tool_call_update and their siblings as they arrive, and rewrites the sessionId to yours.

Two adjustments follow, and both are about the machine boundary.

  • Fountain moves tool_call.locations to _meta["fountain.sandboxLocations"]. They name files in the sandbox. Leave them in place, and an editor opens a path on your machine, or fails to open one.
  • A stop reason comes from the sandbox, such as end_turn, refusal or cancelled. Some things have no vocabulary in the sandbox. A sandbox that never provisioned, one that Fountain reclaimed mid-turn, and a conversation that somebody terminated are the three. Fountain reports each of those as a JSON-RPC error, and does not dress it up as "the agent finished".

A dropped SSE connection is not a lost turn. The server closes an idle stream after 60 s, and the adapter reconnects and continues from where it stopped.

_meta extensions on session/new

These are out-of-band fields that a chat harness sends. Fountain ignores each other field in _meta.

Field Meaning
channelId Names the external channel that this session serves. With it, session/new resumes the conversation already bound to that channel, for this user, agent and vault. That is the same conversation, the same sandbox and the same runtime session, with a fresh ACP id on the client's side. A harness that forgets its sessions on restart therefore lands back where it was (#774). A destroyed sandbox also stops the resume. The workspace does not survive it, so Fountain opens a new conversation on a new sandbox (#779). Without it, each session/new is a new conversation.
sandboxMode Where this one session's conversation runs, ephemeral or persistent. It replaces --sandbox-mode for the session. See Sandboxes.
sandboxId A sandbox id to attach this one session's conversation to. It replaces --sandbox for the session.
freshSession With channelId, it skips the resume this one time. It unbinds the current conversation, which continues and then retires like any other idle one. It opens a new conversation, and binds the channel to that. A Buzz owner's !rotate turns into this (#788). Fountain ignores it without channelId.

The same knobs exist on the API, as channel_id, fresh, sandbox_mode and sandbox_id on POST /api/conversations. Read Conversations, and Sandboxes for the list a sandboxId comes from.

Permission prompts

With --permission ask, the agent stops before it runs a tool and asks. The request starts in the sandbox. It goes to Fountain, then to your client, as an ACP session/request_permission. Your client shows its approval prompt. Your answer goes back the same way. The tool then runs, or it does not.

Your client gets the agent's own options, and only those. Fountain adds none. A client that answers with an option that the agent did not offer gets a refusal, and Fountain denies the call.

Each other outcome is also a denial. These are the outcomes.

  • Your client dismisses the prompt, or closes, or fails.
  • Nobody answers before the server's timeout, which is 5 minutes.
  • Another client answers first, and the first answer wins. Your client then sees a request that no longer waits.

A denial does not stop the turn. The agent reads that it has no permission for that tool, and continues.

A key matches the tool card's title first, and then ACP's kind. The kinds are execute, edit, read, delete, move, search, fetch, think and other. Prefer a kind. The claude runtime puts the command itself in the title, so a title matches one command and nothing else.

A launch can only narrow what the agent permits. A --permission that makes the agent's own policy less strict gets a 422, at session/new, that names the tool. To set the agent's own policy, use the API or the console.

The opencode runtime never asks. It decides permission in its own server, and sends no request. Fountain refuses a policy stricter than auto_allow on that runtime, and says so, and does not pretend to protect you (#959).

Lifecycle, sandboxes, and what survives

  • A turn ends when the conversation says so. That is the terminal turn stage event, with its stop reason. It is not the moment the output goes quiet.
  • An idle sandbox suspends, and is not lost. The next prompt resumes it. A sandbox that Fountain reclaimed at its maximum lifetime, or one that fails to reattach, comes back as an error on the turn that met it. Prompt again to provision a fresh one. Fountain keeps the transcript either way, and session/load replays it. The agent's own memory in the sandbox does not survive (#649).
  • To close the client stops nothing. The conversation is on the server, and the process is a window onto it. Reopen it with session/load, from the conversations app, or with fountain conv.

When something goes wrong

Start with stderr. Add --log-level debug when the default is not enough. Fountain words an error for a reader inside an editor, and not for one at a terminal.

Message Meaning
no Fountain agent configured The entry has no --agent.
agent "x" runs the … runtime, which does not speak ACP All four runtimes speak ACP. This names a conversation whose runtime column holds a name that no adapter covers. Use that agent from the conversations app, or with fountain run.
credentials for … were rejected Run fountain auth login. The message names the instance it tried, and that is usually the surprise.
could not resolve agent "x" on … The wrong name, or the right name on a different instance.
the sandbox never started: … The provision failed, and the reason belongs to the sandbox provider.
could not reattach to the sandbox, prompt again to provision a fresh one Fountain reclaimed the sandbox. The transcript survives.

To run the binary by hand is a fair diagnostic. It sits and waits for JSON-RPC on stdin, which proves that the process starts and finds its credentials.

Who spawns it

Client Who spawns it Page
Zed and other ACP editors The editor, from its agent-server config. Editors
OpenClaw, on Telegram, Discord or Slack The acpx plugin on the OpenClaw host. OpenClaw
Buzz, on Nostr buzz-acp, which Fountain itself supervises on the gateway, one for each hosted identity. Buzz