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.
The sandbox contract
Each conversation runs in a sandbox, and each sandbox backend plugs into the
same seam, the Fountain.Sandbox behaviour. There is one contract that a
provider must meet, and it is executable.
The behaviour's moduledoc specifies the callbacks, the capabilities and the
error taxonomy. Fountain.SandboxConformanceCase is the conformance suite
that each adapter must pass. It covers replay-from-start attach, total
write_stdin, exactly one terminal frame for each command, allow: [] as
deny-all, and more.
ADR 0018
holds the design rationale.
So far, four providers implement it.
| Provider | What it is | Suspend semantics |
|---|---|---|
| Sprites | sprites.dev, the first backend and the instance default. | Parks on its own. The sprite scales to zero and keeps its disk. |
| E2B | e2b.dev | An explicit pause. It snapshots filesystem and memory, and restores both on resume. |
| Daytona | daytona.io, which you can self-host through DAYTONA_API_URL. |
An explicit stop. It keeps the disk, and a sandbox that stays parked archives to object storage. |
| Self-hosted runner | fountain runner on a machine the user owns. It dials out to Fountain, and needs no vendor account. |
Stops the sandbox's processes. The directory stays. Trusted mode, with no isolation and no egress policy. |
The three hosted providers advertise suspend, network policy and attach. A runner advertises suspend and attach alone. Only Sprites supports a TTY.
A warm start from a checkpoint exists in the Sprites transport alone, and it is off by default. A checkpoint id belongs to the sprite that created it, so a warm start across sprites cannot work (#654).
How to choose a provider
- Exactly one thing turns a hosted provider on: its credential is there.
That is
SPRITES_TOKEN,E2B_API_KEYorDAYTONA_API_KEY. The runner provider has no credential, because each daemon authenticates with the user's own API key. So it is on unless you setSANDBOX_RUNNERS_ENABLED=false. SANDBOX_PROVIDER, which defaults tosprites, sets the instance default for a new sandbox. Boot refuses an explicit default whose credential is absent.- An agent can pin a provider with
sandbox_provider. The provider select appears in the agent form on its own, once a second provider is on. - A sandbox always stays on the provider that created it. A parked disk lives where Fountain wrote it, so a wake never crosses providers.
You need at least one. With no provider credential, the app boots and each conversation fails at provision time.
What the contract guarantees
-
Capabilities are honest. An adapter advertises what it can truly do, from
:suspend,:network_policy,:attach,:tty,:checkpointand:public_url. The lifecycle then degrades to match. A provider without:suspenddestroys on idle, and does not fake a park. The agent's memory lives on the sandbox disk, so a resume with a fresh disk would lose data without a sound. -
The errors are one taxonomy. Each adapter normalizes the provider's errors into the shapes in the behaviour moduledoc, such as
:not_found,{:unavailable, _},{:denied, _}and{:rate_limited, _}. Retry policy is therefore the same on each provider. Fountain also keeps{:error, :not_found}apart from a transient failure, and that is what protects a parked disk from a teardown on one flaky network call. -
A network policy means what it says. In a
limitedenvironment,allow: []is deny-all on each provider. It is never a no-op. -
A sandbox URL is real or absent, and never a guess. A provider that gives a sandbox its own HTTP endpoint advertises
:public_url. Fountain stores the URL on the sandbox row, returns it assandbox.urlon the conversation API, and sets it in the sandbox asSANDBOX_URL. An agent can then answer when you ask where it runs. A provider that exposes a hostname for each port instead reports:unsupported, and does not return an address that would not resolve.On Sprites, the endpoint asks for a platform credential by default. So Fountain opens it, with
url_settings.auth = "public", when it creates the sandbox. An agent that serves a page serves it to a person, and that person holds no such credential. So anyone with the URL can reach what an agent serves. Setconfig :fountain, :sprites_public_urls, falseto keep the default instead. A sandbox then keeps its URL, and only a token holder can open it.
Where to go next
- To configure one provider, read Sprites, E2B or Daytona.
- To implement the contract for a fourth backend, read Add a provider.
- To build a Sprites-compatible replacement, read the first backend's wire protocol in the Sprites transport reference.
- To see what we would ask a sandbox platform to support natively, and what each gap costs, read what we need from a platform.