Getting started
Quickstart
Go from an empty terminal to a running, approved workflow in under ten minutes — no API keys, no cloud account, no external services. Every command below is copy-paste runnable against a fresh checkout.
Ballast is a durable runtime for production AI agents, and it ships as exactly two processes. A FastAPI backend hosts the orchestration engine: it validates workflow graphs, compiles them into an executable plan, writes a checkpoint to the database after every node, pauses on human gates, retries failed nodes, attributes cost per step, and streams progress over a WebSocket. A Next.js console gives you the visual builder, the run timeline, and the dashboard.
Both run locally with zero external dependencies. The seeded templates use the deterministic mock model, so they execute end-to-end out of the box — including the cost figures, which come from the same pricing table real models use. Ballast is bring-your-own-key: add your Anthropic or OpenAI key in-app as a model connection when you want real inference — no workflow changes needed.
curl for every step so you can see exactly what the console does under the hood. If you would rather click, each step also names the equivalent console screen. The two are interchangeable — they hit the same API.Prerequisites
- Python 3.11 or later for the backend (the container image uses 3.12)
- Node.js 20 or later for the console
- Optional:
curlandpython3 -m json.toolto follow the API examples — both are already on macOS and most Linux distributions - Optional: your own Anthropic or OpenAI key to run real models — added in-app as a model connection (bring-your-own-key). You do not need one for this walkthrough; the keyless
mockmodel runs everything here — see Installation when you want them
1. Start the backend
From the repository root, create a virtual environment, install the dependencies, and launch uvicorn with auto-reload:
$ cd backend
$ python3 -m venv .venv && source .venv/bin/activate
$ pip install -r requirements.txt
$ uvicorn main:app --reload --port 8000
On its first start the backend does several things automatically:
- Creates a SQLite database (
agentos.db) in WAL mode — no database server to install - Seeds three complete, runnable templates and a single local workspace with an owner account
- Marks any run that was mid-flight when a previous process died as a resumable failure, so nothing is silently lost across restarts
- Serves the REST API at
http://localhost:8000and interactive OpenAPI docs athttp://localhost:8000/docs
Confirm it is alive with the health probe:
/api/healthUnauthenticated liveness probe. If this returns ok, the backend is ready.
$ curl -s http://localhost:8000/api/health
{"status": "ok", "service": "ballast"}
AUTH_MODE=open (the default), so requests without any credentials are treated as the workspace owner. That is why none of the curl commands below carry a token. For production you switch to AUTH_MODE=required — see Self-hosting.2. Start the console
In a second terminal, install the frontend dependencies and start the dev server:
$ cd frontend
$ npm install
$ npm run dev
The console runs at http://localhost:3000. It already knows where the backend is via the default NEXT_PUBLIC_API_URL=http://localhost:8000. Open http://localhost:3000/dashboard — because the backend is in open mode there is no login screen; you land straight on the operational dashboard as the workspace owner.
3. Instantiate a template
Templates are complete workflow graphs, ready to run. We will use the Support Triage Crew: an agent classifies an inbound ticket, a second agent drafts a reply, a human gate holds that reply for review, and a tool notifies the team once it is approved. First, list the seeded templates to get its id:
$ curl -s http://localhost:8000/api/templates | python3 -c "
import sys, json
for t in json.load(sys.stdin):
print(t['id'], '·', t['name'])"
# 4f1c9a2b7d30 · Support Triage Crew
# a91e0c48f6b2 · Data Enrichment Pipeline
# c72d5b1804af · RevOps Lead Qualification
Now instantiate the Support Triage Crew. This copies the template graph into a new, editable workflow and returns the full workflow object — keep its id, you will run it next.
$ curl -s -X POST http://localhost:8000/api/templates/4f1c9a2b7d30/instantiate \
-H 'content-type: application/json' \
-d '{"name": "Support triage"}' | python3 -m json.tool
# → 201 { "id": "9bc62f209c52", "name": "Support triage", "status": "draft", ... }
The console equivalent is Templates → Use template, which drops you into the visual builder with the same graph on the canvas.
4. Trigger a run
Triggering a run validates the graph first (returning 400 on a structural error), then returns 201 immediately — execution is asynchronous. Substitute the workflow id from the previous step:
$ curl -s -X POST http://localhost:8000/api/workflows/9bc62f209c52/runs \
-H 'content-type: application/json' \
-d '{"input": {"ticket": "I was charged twice for my March invoice"}}' \
| python3 -m json.tool
# → 201 { "id": "af772b3602ef", "status": "pending", ... }
The inputobject becomes the run's initial state. The classify agent's user_message references {ticket}, so your text flows straight into the first model call. Grab the returned run id for the next two steps.
Idempotency-Key header to make the trigger safe to retry — a repeated key returns the original run instead of starting a duplicate. Handy when the same ticket might fire the workflow twice.5. Watch it stream
The engine pushes every step transition over a WebSocket. The console does this for you: open http://localhost:3000/runs, click the run, and watch the timeline light up node by node — classify, then draft, then a pause on the gate. The raw stream lives at:
ws://localhost:8000/api/runs/af772b3602ef/stream
Prefer the terminal? Poll the run detail endpoint. Within a second or two the status settles on paused: both agents have run and the human gate is holding the drafted reply.
$ curl -s http://localhost:8000/api/runs/af772b3602ef | python3 -m json.tool
{
"id": "af772b3602ef",
"status": "paused",
"input": { "ticket": "I was charged twice for my March invoice" },
"cost_usd": 0.0038,
"pending_gate": { "node_id": "review", "prompt": "Review the drafted reply…" },
"steps": [
{ "node_id": "classify", "node_type": "agent", "status": "success",
"cost_usd": 0.0021, "output": { "text": "billing" } },
{ "node_id": "draft", "node_type": "agent", "status": "success",
"output": { "reply": "Hi — I'm sorry about the duplicate charge…" } },
{ "node_id": "review", "node_type": "human_gate", "status": "waiting" }
]
}
Every step carries its own input, output, cost, and timing. The pending_gate object tells you exactly what is waiting on a decision. Full payload shapes are documented in Events & streaming.
6. Approve the human gate
Resolve the gate by approving it. Execution resumes from the checkpoint — the two agents that already ran are never re-executed — the notify step fires, and the run finishes as success.
$ curl -s -X POST http://localhost:8000/api/runs/af772b3602ef/approve \
-H 'content-type: application/json' \
-d '{"approved": true}'
In the console, the same action is the Approvebutton on the run's pending-gate panel. Poll the run one more time and you will see status success with the final output — classification, drafted reply, approval decision, and notification result all in one object. Rejecting instead ("approved": false) fails the run without running the notify step.
Try the durability story
While a run ispaused, kill the backend process (Ctrl-C) and start it again. The run is still paused — its state lives in the database as a checkpoint, not in process memory. Approve it and it completes normally. Crash-safe, resumable execution is the whole point of Ballast; read how it works in Reliability & checkpoints.What just happened
You compiled a graph into an execution plan, ran it with real checkpointing, paused deterministically on a human gate, and resumed from the exact node where it stopped — with per-step cost attribution and an audit trail, on a machine with no API keys and no cloud account. That is the same engine that runs in production; you only change configuration, never workflows.
Where to go next
- Installation — the full configuration reference: every environment variable, model providers, Postgres, and the Docker path
- Your first workflow — build the triage flow from scratch, node by node, instead of instantiating a template
- Core concepts — workflows, runs, state, checkpoints, gates, and cost attribution