CLI reference

The fountain binary manages Fountain resources from a terminal, or from a CI script. It is a convenience wrapper over the REST API. You can do everything here with curl.

This page follows what you want to do. It shows the useful invocations, and not each flag. For the complete list of flags, which comes from the binary itself, read All commands.

A command that is on neither of these two pages does not exist. A test walks the real command tree, and it fails either way round.

Install

brew install managoat/tap/fountain

Or take a release binary from the GitHub Releases page.

On Linux, Homebrew compiles a formula and needs a C compiler first, build-essential on Debian and Ubuntu. A release binary needs no compiler, and the quickstart has the three commands that install one.

Start from nothing

fountain auth register # create an account, wait for the emailed link, save the key
fountain quickstart # run the first request and stream the reply

auth register asks for an email address and a password, creates the account, then waits while you click the link in your email. It saves the API key and prints the same first request your start page shows. The wait ends after about ten minutes. The account and the emailed link both survive that, so fountain auth login finishes the job later. If the instance's operator set an access code, pass it with --access-code <code>.

quickstart sends that request. It runs the prompt against the agent your account already has, streams the reply, and prints where to go next.

Authentication

fountain auth login # browser approval on a terminal; email + password when piped
fountain auth login --device # force the browser approval flow
fountain auth login --password # force the email + password prompt
fountain auth login --api-key # prompts for a pasted API key instead
fountain auth whoami # print the current user
fountain auth logout # remove saved credentials

On a terminal, fountain auth login signs you in through your browser. The CLI shows a short one-time code and a console URL, and opens the URL for you. Enter the code in the console and approve the device. The CLI waits, collects a fresh API key, and saves it. This flow works for every account. An account made with the "Sign up with GitHub" button has no password, so the other flows cannot serve it.

When stdin is a pipe, the command reads an email and a password instead. A script that feeds credentials keeps its behavior. --password forces that prompt on a terminal. If the password login fails there, the CLI offers the browser flow.

--api-key takes a key you created yourself. Create an API key in the console, on the API keys page. Then run fountain auth login --api-key and paste the key at the prompt. The CLI checks the key against the server and saves it. The prompt also reads a piped line, so a script can supply the key on stdin.

auth login has no --endpoint flag. Point the CLI at a different instance with FOUNTAIN_BASE_URL. auth login then records that URL in the saved profile.

FOUNTAIN_BASE_URL=https://your-fountain.example.com fountain auth login

Profiles

Use --profile, or FOUNTAIN_PROFILE, to keep several instances side by side.

FOUNTAIN_BASE_URL=https://staging.example.com fountain auth login --profile staging
fountain conv list --profile staging

Agents

fountain agent list [--json]
fountain agent show <id>

An agent is read-only from the CLI. Create one and update one with fountain apply, or with the REST API.

Environments

fountain env list [--json]
fountain env show <id>

These are read-only too. Set an environment secret through apply, or through the API.

Vaults

A vault is the one resource with a full CLI surface. A vault holds the credentials for one conversation, and those are the ones you most often want to change with no edit to a manifest.

fountain vault list [--json]
fountain vault show <id-or-name>
fountain vault create <name> [--description "..."]
fountain vault delete <id-or-name>
fountain vault set-secret <id-or-name> <key> <value>
fountain vault delete-secret <id-or-name> <key>

Hosted Buzz agents

These commands change the inbound gate on a hosted Buzz agent. The gate decides whose @-mention the harness answers.

The Buzz desktop refuses to change access on a provider agent it already deployed. So this is where that gate changes. To set it restarts the harness, and the new gate is then live.

This does not make the agent mentionable. Buzz Desktop 0.5.17 and newer builds its agent directory from the owner-signed kind-30177 policy that the desktop published at deploy. It does not build it from what the harness advertises.

So open the gate here, publish that policy again, and other users can send the mention. Open the gate alone, and they cannot send it at all. Read "Who may talk to it", on the Buzz page a bundled distribution serves.

fountain buzz agents list [--json]
fountain buzz agents set-access <name-or-id> --respond-to anyone
fountain buzz agents set-access <name-or-id> --respond-to allowlist --allowlist <hex>,<hex>
fountain buzz agents set-access <name-or-id> --respond-to owner-only

--respond-to is one of owner-only, allowlist, anyone and nobody. Those are buzz-acp's own modes. Only the flags you pass change, so --respond-to on its own keeps the stored allowlist.

A later provider deploy from the desktop sends the desktop's own record again, and overwrites what you set here.

Conversations

fountain conv list [--json]
fountain conv show <id>
fountain conv stream <id>
fountain conv prompt <id> -p "next instruction" [-i screenshot.png]
fountain conv interrupt <id>
fountain conv terminate <id>
fountain conv delete <id>

You can repeat -i and --image. Each one takes a local file path.

Use --client-request-id on conv prompt or run to name a prompt submission:

fountain conv prompt <id> -p "Run the approved plan" --client-request-id plan-7-step-3

Fountain stores this value on the resulting turn. It is a correlation ID, not an idempotency key; repeating it can run the work again. Omit the flag to send no ID. See prompt correlation for the limits and how to find the turn.

Conversation output uses ACP. Setup and provisioning diagnostics and stderr remain visible. Historical vendor stdout has no formatted transcript. Its stored data remains available through GET /api/conversations/:id/events.

Sandboxes

A sandbox is the computer a conversation runs on. One persistent sandbox can hold the conversations of one agent (ADR 0023).

fountain sandbox list [--json] [--status ready,suspended]
fountain sandbox show <id>
fountain sandbox reset <id>

reset destroys a persistent sandbox. The conversations on it stay. The next prompt on one of them builds a clean machine for the same agent, environment and vault. Fountain refuses the command while a conversation on the sandbox runs a turn, and for an ephemeral sandbox.

Run an agent

fountain run <agent-name-or-id> -p "Audit the auth module"
fountain run <agent-name-or-id> -p "Run the test suite" --vault staging-creds
fountain run <agent-name-or-id> -p "Run the test suite" --environment staging
fountain run <agent-name-or-id> -p "Now fix the failures" --sandbox <sandbox-id>
fountain run <agent-name-or-id> -p "Run the approved plan" --client-request-id plan-7-step-1

For the full conversation creation API, pass a JSON object with wire names and IDs. The command prints the complete API response, including a queued sandbox request if the server returns 202; it does not start a stream:

fountain conv create --file launch.json
cat launch.json | fountain conv create --file -

For example, launch.json can contain:

{"agent_id":"<agent-id>","prompt":"Review the repository","labels":{"source":"nightly"},"queue":true}

Omitted fields stay omitted; null, false, zero and empty values pass through. The server validates the request. This command uses IDs directly and accepts no name-based launch flags, so file fields cannot collide with flag defaults. A promptless request creates an idle conversation. Use fountain run when you want the existing create-and-watch shortcut.

run creates a conversation, then streams until the turn reaches a terminal state.

--vault layers a vault's secrets over the agent's environment, and the vault wins on a collision. --environment provisions from that environment, and not from the agent's own.

The --sandbox flag attaches the conversation to a sandbox you already have, by id. Two conversations then share one disk. The sandbox_id field names that disk.

The --sandbox-mode flag is ephemeral or persistent. It replaces the agent's default for this conversation. A persistent conversation lands on the agent's own machine, and Fountain makes that machine on the first launch.

Long-running turns

The server closes an idle SSE connection after 60 seconds. So a turn that thinks for a while and prints nothing loses its connection.

The CLI reconnects with Last-Event-ID. It replays the output that arrived while it had no connection, and it never mistakes a dropped connection for a finished turn.

If nothing at all arrives for 30 minutes, the CLI exits with an error that names the conversation. It does not report success. Widen the wait with FOUNTAIN_STREAM_IDLE_TIMEOUT, in seconds.

FOUNTAIN_STREAM_IDLE_TIMEOUT=7200 fountain run researcher -p "large refactor"

A disconnect loses nothing. Reattach at any time.

fountain conv stream <conversation-id>

Editor integration (ACP)

fountain acp --agent <name-or-id> [--vault <name-or-id>] [--environment <name-or-id>] [--sandbox-mode persistent] [--sandbox <id>] [--log-level debug]

This speaks the Agent Client Protocol on stdio, so an ACP-capable editor can drive a Fountain conversation.

You do not run it yourself. The editor spawns it, and talks JSON-RPC over the pipe. stdout carries the protocol and nothing else. Diagnostics go to stderr, and that is where to look first when an editor reports a problem.

--agent names the Fountain agent that a session runs. The protocol has no field for it, so you configure it on the command line, with one editor entry for each agent.

The credentials are the ones that fountain auth login already saved. --profile chooses the instance.

fountain acp (reference) documents the protocol surface and the flags in full. Editors (ACP) has the setup, the editor config snippets, and the limits that matter before you start.

The first of those limits is that the agent works on a sandbox's files. It does not work on the files open in your editor.

Self-hosted runner

fountain runner # this machine becomes a sandbox provider
fountain runner --name mini --root ~/fountain-sandboxes --log-level debug

This dials out to Fountain, holds the connection, and serves sandboxes for an agent whose sandbox_provider is runner.

Each sandbox is a directory under --root, which defaults to ~/.fountain/runners/<name>/sandboxes. The agent's processes run on this machine as you, with HOME pointed at that directory. An idle sandbox parks: it stops its processes, and the directory stays.

Fountain trusts this machine, and gives it no VM and no egress policy. Run it where you would hand a capable colleague a shell. It needs a full-scope key. A name is unique for each account, and it defaults to the hostname. It reconnects with backoff. Read the runners guide.

A microVM for each sandbox

fountain runner --backend firecracker \
--bridge fcbr0 --subnet 10.61.0.0/24 \
--fc-kernel /var/lib/fountain/vmlinux \
--fc-rootfs /var/lib/fountain/rootfs.ext4

--backend firecracker gives each sandbox its own Firecracker microVM. The disk of a microVM is a private copy of --fc-rootfs, and it is the sandbox's memory between turns. Commands run in the guest. An idle sandbox parks when the daemon pauses its microVM, and the guest keeps its processes.

This backend needs Linux, /dev/kvm, and the CAP_NET_ADMIN capability. You must attach the bridge to your own network, and give it the first host address of --subnet. The base image must start the guest agent at boot.

fountain runner-guest # the in-VM agent. The guest init starts it

The runners guide has the full recipe for the base image and the bridge.

Apply manifests

fountain apply -f path/to/manifest.yml
fountain apply -f path/to/directory/ # walks all *.yml / *.yaml files
fountain apply -f dir/ --var REGION=eu-west-1 # ${VAR} substitution, repeatable

Apply is idempotent. It creates what is new, and updates what changed. It supports six kinds, which are Environment, Vault, Agent, Teammate, Schedule and Webhook.

--var and ${VAR} substitution apply to a spec.secrets value alone. A ${VAR} anywhere else in the document goes across as it stands. A setup_script and a name are two such places.

The CLI compiles each document into one manifest, and sends that to POST /api/apply in one request.

The server reconciles the six kinds in the order above. A document can name another document whatever its position in the file. An Agent names an environment. A Teammate names an agent, an environment and a vault. A Schedule names a teammate. Each name resolves against the manifest first, then against the records your account already holds.

The metadata.name is the key for five of the kinds. A Webhook is keyed by its spec.url, so the document name is a label alone. Change that URL and the next apply creates a second endpoint. The first one stays, and keeps delivering, until you delete it with fountain webhooks delete.

---
apiVersion: fountain/v1
kind: Teammate
metadata:
name: Ada
spec:
agent: ada # an Agent document, or an agent you already have
environment: my-project
vault: alice
---
apiVersion: fountain/v1
kind: Schedule
metadata:
name: standup
spec:
teammate: Ada
cron: "0 9 * * 1-5" # five fields, UTC
prompt: What is on today?
one_off: false
enabled: true
---
apiVersion: fountain/v1
kind: Webhook
metadata:
name: ci
spec:
url: https://ci.example.com/hooks/fountain
description: CI receiver
event_types: [conversation.turn.done]

A Teammate document adds the agent to the team, which opens the teammate's conversation and starts its computer. A later apply moves the name, the environment and the vault the teammate is bound to. It starts no second computer.

A Teammate document is the whole teammate. Drop environment or vault from it and the next apply clears that binding, which puts the teammate back on the agent's own environment and on no vault. The other five kinds behave the other way around, where an absent spec key leaves that field alone.

A teammate's computer is built for one environment and one vault. Move either of them and Fountain retires that computer, so the teammate's next message builds a new one with the files and tools of a fresh machine. It refuses the row while a turn is still running there, and prints what to do about it. A conversation that shared the retired computer, and that names a different environment or vault, does not follow the teammate. It builds a machine of its own from what it names.

Fountain keeps one computer for each agent, environment, vault and runtime. It refuses the row when the agent already has a computer on the environment and vault you are moving the teammate to. It does not join the teammate to that computer. Reset or remove the computer first, then apply again.

Two Teammate documents cannot name the same agent. An agent is on the team once, so the second document fails and the first one applies.

A Webhook that an apply creates prints its signing secret one time. Save it then. An apply that updates the same endpoint prints no secret.

A Schedule names its teammate. A teammate's name is not unique, so a name that two of your teammates answer to fails that row. Rename one of them, or name the teammate in the same manifest, which makes the name unambiguous.

Apply is additive. A document that you delete from the manifest leaves its record in place. There is no prune, so delete a record through its own command or the console.

The CLI prints + for a create, ~ for an update, = for a resource that already matched the manifest, and ! for a failure.

env = my-project
vault = alice
agent ~ ada
teammate + Ada
schedule + standup
webhook + ci
signing secret ci whsec_...
save it now, it is not shown again

A second apply of the same file prints = on every row, because Fountain wrote to none of them. Inline spec.secrets are the exception. Fountain encrypts them again on each apply, so they keep printing ~ under a row that prints =.

The server rejects unknown spec keys per resource. The CLI prints those errors and exits nonzero; valid resources in the same manifest still apply. Correct a misspelled field before you retry.

fountain apply requires Fountain server v0.3.0 or later, which introduced POST /api/apply. Newer document kinds and fields can require a newer server. If the endpoint returns 404, the CLI exits nonzero without individual resource requests. Upgrade the server or check FOUNTAIN_BASE_URL before you retry.

Secret references

A spec.secrets value can point at a secret manager, and hold no plaintext. That is what lets you commit a manifest to git.

The CLI resolves a value that starts with one of these schemes, on the client, at apply time. It runs the manager's own CLI, which you must install and authenticate first.

Scheme Manager Resolved with
op://vault/item/field 1Password op read
bws://<secret-uuid> Bitwarden Secrets Manager the bws CLI
infisical://<project?>/<env>/<path?>/<name> Infisical the infisical CLI
kind: Vault
metadata: { name: prod-tokens }
spec:
secrets:
GITHUB_TOKEN: op://Private/github/token
STRIPE_KEY: bws://8f0a3c1e-...

A failure to resolve fails the apply for that document. So does an empty value, which nearly always means the manager found no secret. The CLI writes no empty secret.

The CLI resolves a spec.secrets value alone. The schemes are inert anywhere else.

API keys

fountain keys list [--json]
fountain keys create <name> # prints the key once; it is not recoverable
fountain keys revoke <id>

OAuth apps

Register an app that offers "Sign in with Fountain." The registration also admits the app's redirect origins to the API.

fountain oauth-client list [--json]
fountain oauth-client create <name> --redirect-uri <url> [--redirect-uri <url>]... [--json]
fountain oauth-client update <id> [--name <name>] [--redirect-uri <url>]... [--json]
fountain oauth-client delete <id>

create prints the generated client_id that your app sends. A redirect URI must match exactly and must use https. Loopback hosts can use http and match on any port.

The app starts in development mode. It signs in only the account that registered it. delete stops new sign-ins, but keys already issued stay valid until you revoke them with fountain keys revoke.

Webhooks

Endpoints that Fountain sends conversation lifecycle events to. The webhooks reference holds the full event catalogue and a worked signature verifier.

fountain webhooks list [--json]
fountain webhooks create <url> [--description <text>] [--event <type>]...
fountain webhooks show <id>
fountain webhooks delete <id>
fountain webhooks test <id>
fountain webhooks rotate-secret <id>
fountain webhooks pause <id>
fountain webhooks resume <id>
fountain webhooks deliveries <id> [--limit <n>] [--json]
fountain webhooks redeliver <id> <delivery-id>

create and rotate-secret print the secret once. Fountain cannot show it again, and can only replace it. Repeat --event for each type. An endpoint with no --event gets conversation.turn.done, conversation.turn.failed and conversation.provision.failed.

deliveries turns a broken integration into a status code and a response body, rather than a support thread.

Output

A list command accepts --json. There is no -o flag, and there is no YAML output.

fountain agent list --json | jq '.[].name'

Configuration

fountain auth login writes ~/.fountain/credentials, an INI-style file with one section for each profile. It writes each value in double quotes.

[default]
api_key = "ftn_..."
base_url = "https://managoat.com"

The CLI writes the file 0600, and the directory 0700.

Environment variables

Variable Effect
FOUNTAIN_API_KEY The API key. It wins over the credentials file.
FOUNTAIN_BASE_URL The instance URL. It wins over the credentials file.
FOUNTAIN_PROFILE The profile to use. It is the same as --profile.
FOUNTAIN_STREAM_IDLE_TIMEOUT The seconds of silence before a stream gives up. The default is 1800.
FOUNTAIN_API_KEY=ftn_... FOUNTAIN_BASE_URL=https://other.example.com fountain agent list

The CLI resolves both the key and the URL in the same order. It reads the environment variable, then the active profile in the credentials file. For the URL alone it then falls back to the built-in default, https://managoat.com.

Do you self-host? That built-in default is the hosted instance, and not yours.

Run fountain auth login with FOUNTAIN_BASE_URL pointed at your instance. Do that first, before each other command. A CLI with no config, and FOUNTAIN_API_KEY exported, sends that key to managoat.com. Configure the URL before the key.