Deploy Guide¶
This guide covers running your own Agent on Demand instance in production.
Prerequisites¶
- Python 3.11 or later
- uv (recommended) or pip
- A Sprites account and API token
- PostgreSQL 14+ — required for both dev and production. Procrastinate (the job queue that drives session execution) only supports Postgres.
Database
The default DATABASE_URL points at a local Postgres container started by
make db-up (docker compose up -d db). For production, override
DATABASE_URL with your own Postgres DSN
(e.g. postgres://user:pass@host:5432/aod). SQLite is not a supported
backend — it's only wired up for the unit-test suite, which stubs the
job queue. Sessions enqueued against SQLite will never execute.
Environment variables¶
All configuration is passed through environment variables. The full list,
sourced from src/config/settings.py:
| Variable | Required | Default | Description |
|---|---|---|---|
DJANGO_SECRET_KEY |
Yes (prod) | dev-insecure-key-change-in-prod |
Django secret key for session signing — safe to rotate |
FIELD_ENCRYPTION_KEY |
Yes (prod) | Falls back to DJANGO_SECRET_KEY |
KEK for encrypted UserSpritesKey / UserRuntimeKey rows — durable; rotating requires a re-encrypt migration |
DJANGO_DEBUG |
No | true |
Set to false in production |
DJANGO_ALLOWED_HOSTS |
No | * |
Comma-separated list of allowed host headers |
DATABASE_URL |
Yes | postgres://agent_on_demand:agent_on_demand@localhost:5460/agent_on_demand (matches make db-up) |
Postgres DSN parsed by dj-database-url. Postgres is required — SQLite is only used by the test suite. |
SPRITES_BASE_URL |
No | https://api.sprites.dev |
Override the Sprites API base URL |
SPRITE_NAME_PREFIX |
No | aod |
Prefix applied to all Sprite names created by this instance |
DEFAULT_TIMEOUT |
No | 600 |
Default session timeout in seconds |
A minimal production .env:
DJANGO_SECRET_KEY=your-long-random-secret-key
FIELD_ENCRYPTION_KEY=your-separate-long-random-key
DJANGO_DEBUG=false
DJANGO_ALLOWED_HOSTS=aod.example.com
Installation¶
git clone https://github.com/ravi-hq/agent-on-demand
cd agent-on-demand
uv sync --all-extras # or: pip install -e .
Database migration¶
Apply all migrations before starting the server:
Creating the first API token¶
Agent on Demand uses bearer tokens prefixed with aod_ for authentication. Create the
first token via the Django shell:
uv run python manage.py shell
# Inside the shell:
from django.contrib.auth.models import User
from agent_on_demand.models import APIKey
user = User.objects.create_user("admin", password=input("Set admin password: "))
_, raw_key = APIKey.create_key(user, "admin-key")
print(raw_key) # aod_<random> — copy this now, it won't be shown again
Pass the token in the Authorization header:
Running in production¶
Agent on Demand is a two-process deploy: a web service that accepts HTTP
and enqueues jobs, plus a worker service that executes them. Both processes
share one Postgres database. Skip the worker and every POST /sessions will
succeed but the session row will stay pending forever — no Sprite is ever
created.
Web service (ASGI)¶
The session-stream endpoint is async, so the web service must run under ASGI.
The ASGI entry point is config.asgi:application:
This matches the production deployment in render.yaml. Gunicorn also works
if you front it with an ASGI worker class such as uvicorn.workers.UvicornWorker:
pip install gunicorn uvicorn
gunicorn config.asgi:application \
-k uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000 --workers 3
A WSGI entry point exists at config.wsgi:application for tooling that
expects one, but the GET /sessions/{id}/stream SSE endpoint will not work
under a sync WSGI worker — use ASGI.
Worker service¶
Run the Procrastinate worker as a separate long-lived process:
The worker shells out to the Sprites API to provision sandboxes and stream
agent output back into the database, so it needs the same DATABASE_URL,
FIELD_ENCRYPTION_KEY, and SPRITES_BASE_URL as the web service. See
render.yaml for a working two-service config.
Note
The make dev target (uvicorn config.asgi:application --reload --port 8777)
runs only the web side. Pair it with make worker in a second terminal
for a complete local environment. Django's runserver is not used in
production.
Sprites credentials¶
Agent on Demand authenticates to the Sprites platform using per-user tokens stored encrypted at rest. Each user brings their own Sprites token and their own model API keys; there are no shared/service-level credentials. At session creation time, Agent on Demand:
- Looks up the caller's
UserBackendCredential(backend="sprites")and decrypts the token. - Calls
SpritesClient(token=..., base_url=SPRITES_BASE_URL)to obtain a client. - Creates a Sprite via
client.create_sprite(name), where the name is{SPRITE_NAME_PREFIX}-{12-char-hex-id}. - Writes a
run-agent.shwrapper script onto the Sprite's filesystem. The script exports the per-user model API key (e.g. the user's Anthropic key) and runs the agent CLI. - Model API keys are stored encrypted at rest in
UserCredentialrows and are never present in Sprites API calls — only inside the agent's execution environment.
If a user has no Sprites credential configured, session create, multi-turn prompt, and
session termination endpoints return 400 No backend credentials configured.
To set a user's Sprites token (required before they can run sessions):
from agent_on_demand.models import UserBackendCredential
from django.contrib.auth.models import User
user = User.objects.get(username="alice")
cred, _ = UserBackendCredential.objects.get_or_create(user=user, backend="sprites")
cred.set_token("your-sprites-api-token")
cred.save()
To set a user's model API key (required before they can run sessions on a given runtime):
from agent_on_demand.models import UserCredential
from django.contrib.auth.models import User
user = User.objects.get(username="alice")
cred, _ = UserCredential.objects.get_or_create(user=user, kind="provider:anthropic")
cred.set_value("your-anthropic-api-key")
cred.save()
The kind field maps to the env var written into the session:
kind |
Env var written | Used by |
|---|---|---|
provider:anthropic |
ANTHROPIC_API_KEY |
claude, opencode |
provider:openai |
OPENAI_API_KEY |
codex, opencode |
provider:google |
GEMINI_API_KEY |
gemini, opencode |
runtime_token:claude-oauth |
CLAUDE_CODE_OAUTH_TOKEN |
claude (OAuth variant) |
Health check¶
No authentication required. Use this endpoint for load balancer or uptime monitor checks.