Getting started
Installation
Everything the Quickstart skipped: the two-service setup in detail, every environment variable Ballast reads, database options from SQLite to Postgres, and the one-command Docker path.
Ballast is a Python backend and a Node console that share one database. Nothing else is required to run it. This page walks the manual setup of both services, documents every configuration value, and then shows the Docker path that packages the whole thing into a single command.
Repository layout
ballast/
backend/ # FastAPI app + orchestration engine
main.py # app entrypoint (lifespan, middleware, routers)
server/ # engine, routers, models, schemas, auth, billing
tests/ # pytest suite
requirements.txt # runtime deps (includes the Postgres driver)
.env.example # copy to .env; every value has a working default
frontend/ # Next.js console + marketing site
app/(console)/ # dashboard, builder, runs, audit, settings
app/(marketing)/ # public site + these docs
.env.local.example # copy to .env.local; sets NEXT_PUBLIC_API_URL
docker-compose.yml # production-shaped local deployment
Prerequisites
- Python 3.11+ (the container image uses 3.12)
- Node.js 20+ and npm
- Optional: Docker with Compose v2, if you want the one-command path in the last section
Backend setup
$ cd backend
$ python3 -m venv .venv && source .venv/bin/activate
$ pip install -r requirements.txt
$ cp .env.example .env # optional — every value has a working default
$ uvicorn main:app --reload --port 8000
The server creates its schema on first start and seeds thirteen templates plus a single local workspace. Two behaviours are worth knowing about:
- Auto-migration. Tables are created with
Base.metadata.create_all. On SQLite the startup also applies a small set of additive column migrations, so an older database file gains new columns without a manual step - Interrupted-run recovery. Any run that was
runningorpendingwhen the previous process died is markedfailedwith the message “Interrupted by backend restart — resume from last checkpoint”. Its checkpoint is intact, soPOST /api/runs/{id}/resumepicks up where it left off
Keep --workers 1
Run a single uvicorn worker. The orchestration executor, the run/cancel registry, the cron scheduler, and the retention sweeper all live in the API process. A second worker would double-fire schedules and sweeps. Scale vertically, or move execution to dedicated workers, before adding replicas — see Self-hosting.Console setup
$ cd frontend
$ npm install
$ cp .env.local.example .env.local # sets NEXT_PUBLIC_API_URL=http://localhost:8000
$ npm run dev
The console runs at http://localhost:3000; the operational views live under /dashboard, /runs, /workflows, and /settings. Its only setting is NEXT_PUBLIC_API_URL, which points the browser at the backend.
NEXT_PUBLIC_API_URL is baked at build time
Because it is aNEXT_PUBLIC_ variable, its value is inlined into the client bundle when the frontend is built — not read at runtime. In development npm run dev reads it live, but for a production build you must set it before npm run build (or pass it as a Docker build arg). Changing it later means rebuilding.Environment-variable reference
Every variable is optional in local development. Without LLM keys, agent nodes use a deterministic mock model; without Slack or email keys, those tools run in simulated mode and report "simulated": true in their output. The tables below group the variables by concern. All of them are backend variables read from backend/.env except the last, which is a frontend variable.
Model providers
Ballast is bring-your-own-key: model provider keys are not environment variables. Each workspace adds its own key as a model connection in the console (Settings → Model connections, or the onboarding wizard) — encrypted at rest and injected only at call time. The keyless mock model runs with no setup.
Database
| Field | Type | Default | Description |
|---|---|---|---|
| DATABASE_URL | string | sqlite:///./agentos.db | SQLAlchemy connection string. SQLite runs in WAL mode with a 30s busy timeout. Any postgresql:// URL works unchanged — the psycopg2 driver ships in requirements.txt. |
Authentication
| Field | Type | Default | Description |
|---|---|---|---|
| AUTH_MODE | open | required | open | open: requests with no credentials act as the workspace owner (local dev). required: every /api request needs a session token or API key, else 401. |
| SIGNUP_MODE | open | approval | workspace | closed | open | Self-serve /api/auth/signup policy. open: active immediately in the default workspace. approval: created as pending until an admin approves. workspace: each signup provisions its own isolated workspace (multi-tenant cloud). closed: sign-ups return 403. |
| SECRET_KEY | string | per-boot random | Signs the HMAC-SHA256 session tokens (7-day TTL). If unset, a random key is generated per boot — fine for dev, but sessions do not survive a restart. Required in production. |
| OWNER_PASSWORD | string | unset | First-boot password for the owner account when AUTH_MODE=required. If unset in required mode, one is generated and printed to the server log exactly once. |
Integrations & alerts
| Field | Type | Default | Description |
|---|---|---|---|
| SLACK_WEBHOOK_URL | string | unset | Default incoming-webhook URL for the slack_message tool. Overridable per node with a webhook_url param. Unset → the tool logs a simulated send. |
| RESEND_API_KEY | string | unset | Enables real delivery via Resend for the send_email tool, member-invite emails, and operational alert emails. |
| EMAIL_FROM | string | agentos@example.com | From address used by send_email and alert emails. |
| ALERT_EMAIL | string | unset | Recipient for run-failure and pending-gate alerts. Requires RESEND_API_KEY to actually send. |
Networking & hardening
| Field | Type | Default | Description |
|---|---|---|---|
| CORS_ORIGINS | string | http://localhost:3000 | Comma-separated list of allowed browser origins. Set it to your console's exact origin in production. |
| RATE_LIMIT_PER_MINUTE | int | 0 | Per-IP request ceiling using a sliding 60s window. 0 disables limiting; when exceeded the API returns 429. A value like 300 is a sane production default. |
| HTTP_TOOL_BLOCK_PRIVATE | 0 | 1 | 0 | SSRF guard. 1 makes the http_request tool resolve the target host and refuse loopback, private, link-local, and reserved addresses. Turn this on in production. |
Frontend
| Field | Type | Default | Description |
|---|---|---|---|
| NEXT_PUBLIC_API_URL | string | http://localhost:8000 | Where the console's browser code sends API and WebSocket requests. Set in frontend/.env.local. Inlined at build time (see the callout above). |
Key handling
Model provider keys are bring-your-own: a workspace adds its own key as a model connection in the console — encrypted at rest (Fernet), injected only at call time, never written to the database in plaintext, checkpoints, or logs. They are not set via environment variables.Model providers
Agent nodes choose a model by name. Ballast is bring-your-own-key: real models run through a workspace model connection with your own provider key (Settings → Model connections). The keyless mock model returns stable, structured text with no setup — ideal for templates, evals, and first runs. A real model with no connected key is blocked with a clear error; it never silently falls back to mock.
| Field | Type | Default | Description |
|---|---|---|---|
| claude-sonnet-4-6 | anthropic | — | Frontier reasoning; needs an Anthropic model connection (your key). |
| claude-haiku-4-5 | anthropic | — | Fast and cheap; needs an Anthropic model connection (your key). |
| gpt-4o | openai | — | Needs an OpenAI model connection (your key). |
| gpt-4o-mini | openai | — | Cheaper OpenAI tier; needs an OpenAI model connection (your key). |
| mock | built-in | — | Deterministic and keyless — the only model that runs without a connection. |
SQLite → Postgres
SQLite in WAL mode is the default and is genuinely fine for a single-machine deployment — runs survive restarts because checkpoints are rows, not memory. For anything shared or horizontally durable, point DATABASE_URL at Postgres. The schema is portable SQLAlchemy and is created automatically on first boot:
DATABASE_URL=postgresql://agentos:secret@db.internal:5432/agentos
- The driver already ships —
psycopg2-binaryis inrequirements.txt, used automatically when the URL starts withpostgresql:// - Tables are created on startup; there is no separate migration command for a fresh database
- The SQLite-only additive column migrations do not run against Postgres. For column changes to an existing production Postgres database, use Alembic
- Checkpoints, runs, and audit logs are ordinary rows — backup and retention are standard database operations
Health check
The backend exposes an unauthenticated liveness probe. It is exempt from auth in every mode, which makes it safe to point a load balancer or container healthcheck at:
/api/healthLiveness probe. Returns 200 with a fixed body once the app has booted.
$ curl -s http://localhost:8000/api/health
{"status": "ok", "service": "ballast"}
The Docker path
docker-compose.yml builds and runs both services with a production-shaped configuration: AUTH_MODE=required, SIGNUP_MODE=approval, rate limiting at 300/min, the SSRF guard on, and a persisted SQLite volume. Supply a signing key and an owner password on the command line:
$ SECRET_KEY=$(openssl rand -hex 32) OWNER_PASSWORD=pick-one \
docker compose up --build
The console comes up at http://localhost:3000 and the API at http://localhost:8000. Because this runs in required mode, sign in at http://localhost:3000/loginwith the workspace owner's email and the OWNER_PASSWORDyou set. The frontend's NEXT_PUBLIC_API_URLis passed as a build arg, and the backend's data persists in a named volume across docker compose down and back up.
SECRET_KEY and OWNER_PASSWORD are deliberately obvious placeholders. Read Self-hosting before exposing it to a network.Running the test suite
$ cd backend
$ pip install -r requirements-dev.txt
$ python -m pytest tests/ -q
The suite runs against a throwaway database and needs no network and no API keys. It exercises the full lifecycle: CRUD and versioning, structural validation, state passing between agents, condition branches, bounded loops, genuinely concurrent parallel branches (timing-asserted), gate approve and reject, checkpoint resume, cancellation, idempotency keys, WebSocket streaming, the expression sandbox's security cases, the auth and signup modes, evaluations, templates, audit, and metrics.
Next
With both services running, read Core concepts to understand the execution model, build Your first workflow from scratch, or go straight to Self-hosting for the production checklist.