The four primitives¶
Everything in Fountain is built from four concepts.
Environment¶
An Environment is a named, reusable baseline for a coding agent:
- Encrypted secrets - key/value env vars, encrypted per-tenant with AES-256-GCM. Write-only: once stored, the API never returns a value (listing returns keys and timestamps only).
- Plain env vars - a non-secret
env_varsmap for values that aren't sensitive (feature flags, endpoints). Returned by the API as-is; put anything sensitive in secrets instead. - Runtime config - packages to install, repos to clone, a setup script
- Networking policy -
networking_type: unrestrictedorlimited. Sprites are open by default, sounrestrictedis a no-op.limitedrestricts egress to the domains innetworking_config.allowed_hosts(the onlynetworking_configkey honored today); underlimitedwith noallowed_hosts, nothing is allowlisted.
Environments attach to Agents at creation time. Many agents can share one environment.
apiVersion: fountain.dev/v1
kind: Environment
metadata:
name: python-data-env
spec:
packages:
python: "3.12"
networking_type: limited
secrets:
- key: OPENAI_API_KEY
value: sk-... # encrypted at rest, never returned by the API
Vault¶
A Vault is a free-floating bag of env-var overrides.
Key rule: vault values win on key collision. When Fountain materializes env vars for a conversation, it merges environment secrets -> vault secrets. The vault always takes precedence.
Typical uses: per-customer API keys, staging vs. production credentials, temporary overrides.
apiVersion: fountain.dev/v1
kind: Vault
metadata:
name: staging-creds
spec:
secrets:
- key: DATABASE_URL
value: postgres://staging-host/mydb
Agent¶
An Agent is a named, re-runnable configuration for an AI coding assistant:
model-provider/model-id(e.g.anthropic/claude-sonnet-4-6)runtime- one ofclaude,codex,gemini,opencodeenvironment- optional Environment to attachsystem/description- system prompt and human-readable descriptionskills- each entry is either inline ({name, content}— a full SKILL.md written to the sandbox) or GitHub-sourced ({source: "owner/repo"}— installed via the skills.sh CLI)mcp_servers- MCP server definitions, with${VAR}substitution in their envmetadata- free-form map for callers' own bookkeeping
apiVersion: fountain.dev/v1
kind: Agent
metadata:
name: researcher
spec:
model: anthropic/claude-sonnet-4-6
runtime: claude
environment: python-data-env
skills:
- source: BinaryBourbon/fountain-api-skill
mcp_servers:
github:
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_PERSONAL_ACCESS_TOKEN: "${GITHUB_PAT}"
${GITHUB_PAT} is a substitution reference resolved from the merged env + vault secrets at spawn time.
Conversation¶
A Conversation is a running session of an Agent inside a sandboxed VM. It starts with one prompt and can continue over multiple turns:
- POST to
/api/conversationswithagent_id(and optionalvault_id,prompt,images) - Fountain resolves the full env-var set and spawns a Sprites sandbox
- The agent runs; log events stream in real time over SSE (
GET /api/conversations/:id/stream) - Follow-up prompts go to
POST /api/conversations/:id/prompts; a running turn can be interrupted (POST .../interrupt) and the whole conversation ended early (POST .../terminate) - The sandbox exits when the conversation terminates, or when it is reclaimed for being idle (default 60 minutes with no turn activity) or for reaching its maximum lifetime (default 24 hours)
Reclaiming ends the sandbox, not the conversation. The conversation stays resumable: the next prompt provisions a fresh sandbox and the runtime resumes the same session, so history is preserved and the only cost is the provisioning wait. Self-hosters can widen or disable both bounds with SANDBOX_IDLE_TIMEOUT_MINUTES and SANDBOX_MAX_LIFETIME_HOURS (0 disables).
Status lifecycle¶
Any non-terminal state can move to terminated via POST /api/conversations/:id/terminate.
Substitution¶
All string values in Agent configs support ${VAR} interpolation:
| Syntax | Result |
|---|---|
${VAR} |
Value of VAR from the merged env map |
$$ |
Literal $ |
Substitution is recursive (works inside maps and lists) and fail-complete - all missing variables are reported at once.