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.

Python SDK

The Python SDK turns the conversation API and its event feed into one job:

from fountain import Fountain
fountain = Fountain()
result = fountain.run(
"Upgrade us to Phoenix 1.8 and open a PR",
agent="reposage",
vault="github-bot",
).result()
print(result.text)
print(result.url)

The source is in sdk/python/. It supports Python 3.9 and newer and has no runtime dependencies.

pip install fountain-agent-sdk

Credentials

Fountain() resolves credentials the same way as the CLI:

api_key: argument -> FOUNTAIN_API_KEY -> FOUNTAIN_TOKEN -> ~/.fountain/credentials
base_url: argument -> FOUNTAIN_BASE_URL -> ~/.fountain/credentials -> hosted Fountain

Use Fountain(profile="work") for another credentials-file profile. Inside a Fountain sandbox, the client uses its conversation-scoped token and marks new conversations as children of the current one.

Waiting and streaming

run() starts work immediately and returns a Run handle. result() waits for the finished turn. The handle's iterator streams lifecycle, text, thinking, tool, block, and permission events from that same run.

run = fountain.run("Review this repository", agent="reviewer")
for event in run:
if event["type"] == "tool":
print("->", event["name"])
elif event["type"] == "text":
print(event["text"], end="", flush=True)
result = run.result()

Use run.text_stream when you only need the answer text. Start several runs before you call result() to provision and run them in parallel.

The handle is awaitable and asynchronously iterable too. The SDK keeps HTTP in its background thread, so the wait does not block an asyncio event loop.

result = await fountain.run("Review this repository", agent="reviewer")
run = fountain.run("Review another repository", agent="reviewer")
async for event in run:
print(event)

A failed agent turn is a result with state == "failed". A transport failure, a rejected request, or an SDK timeout raises an exception. The timeout passed to run(..., timeout=300) stops the SDK's wait in seconds; it does not stop the agent. Call run.interrupt() to ask the agent to stop.

Follow-ups

first = fountain.run("Find every N+1 query", agent="reposage").result()
second = fountain.resume(first.conversation_id).send("Fix the worst three.").result()

The second turn uses the same sandbox, checkout, and agent session.

Permission requests

An agent with an ask permission rule stops before a tool call that matches. The run emits a permission event with the options the runtime offered:

for event in run:
if event["type"] != "permission":
continue
request = event["request"]
allow = next(
option for option in request["options"]
if option.get("kind") == "allow_once"
)
run.answer(request["request_id"], allow["option_id"])

You can also answer through fountain.resume(conversation_id).answer(...) from another process.

Resources

Agents, environments, and vaults have list, get, create, update, and delete. Environment and vault secret values are write-only.

environment = fountain.environments.create({
"name": "fountain-ci",
"packages": {"apt": ["ripgrep"]},
"repositories": [{
"url": "https://github.com/BinaryBourbon/fountain",
"mount_path": "/work/fountain",
}],
})
vault = fountain.vaults.create({"name": "github-bot"})
fountain.vaults.secrets.set("github-bot", "GITHUB_TOKEN", token)
agent = fountain.agents.create({
"name": "reposage",
"runtime": "claude",
"model": "anthropic/claude-sonnet-5",
"environment_id": environment["id"],
"allowed_vault_ids": [vault["id"]],
})

Resource dictionaries keep the API's snake_case keys. You can use the same definition in Python, the REST API, and a fountain.yml manifest.

The team

fountain.team.add("watchtower", name="Watchtower")
reply = fountain.team.message(
"watchtower",
"Any disks over 80%?",
).result()
fountain.team.schedules.create("watchtower", {
"cron": "0 9 * * *",
"prompt": "Check disk usage and say only what changed.",
})

team.list, get, rename, remove, history, fresh_conversation, and stream cover the rest of the teammate lifecycle. team.schedules has the five resource verbs and run for an immediate invocation.

Errors and the raw API

Catch ConversationBusyError, NotReadyError, QuotaExceededError, ValidationError, AuthError, or the base FountainError. Each one carries status, code, body, retry_after, and retryable. A validation error also exposes field_errors.

Endpoints without a wrapper stay available through the same authentication and error behavior:

rows = fountain.request("GET", "/api/audit", query={"limit": 50})