scripts/demo.py runs the happy-path approval flow against a local gdev-agent stack: login, signed webhook, wait for the pending audit row, approve, and exit 0 on success.
No video or GIF demo artifact is committed in this repository yet. Recording and publishing that file is a manual packaging task; the checklist below is the repo-side script for producing it without claiming the artifact already exists.
- Docker and Docker Compose are installed.
- The local stack is running from the repo root:
cp .env.example .env
docker compose up --build -dThe copied file supplies local-only GDEV_OWNER_PASSWORD and
GDEV_APP_PASSWORD values required by Compose. Replace them for any shared
environment; the sample values are not production secrets.
- The API is reachable at
http://localhost:8000unless you override it with--url. - The one-shot
migrateservice has completed successfully. It runs Alembic, verifies the database revision withpython scripts/cli.py migrations check, and then applies demo seed data before the API container becomes healthy. - If your local app is not using the seeded demo tenant values, set the environment overrides below before running the script.
- For the free deterministic path, run the API with
LLM_MODE=demo. Live mode is optional and requiresLLM_MODE=live, a realANTHROPIC_API_KEY, and a tenant budget cap.
These defaults match the seeded local Compose stack in
docker/seed.sql, scripts/seed_db.py,
and docker-compose.yml. They are synthetic local
demo values only.
| Tenant | Tenant ID | Webhook secret | Admin user | Admin password | Approval secret | Reviewer |
|---|---|---|---|---|---|---|
test-tenant-a |
aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa |
test-webhook-secret-a |
admin-a@example.com |
password123 |
approve-secret |
demo-runner |
test-tenant-b |
bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb |
test-webhook-secret-b |
admin-b@example.com |
password123 |
approve-secret |
demo-runner |
The default scripts/demo.py run uses tenant A:
DEMO_TENANT_SLUG=test-tenant-aDEMO_TENANT_ID=aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaaDEMO_WEBHOOK_SECRET=test-webhook-secret-aDEMO_ADMIN_EMAIL=admin-a@example.comDEMO_ADMIN_PASSWORD=password123DEMO_APPROVE_SECRET=approve-secretDEMO_REVIEWER=demo-runner
Optional timing controls:
DEMO_POLL_INTERVAL=1.0DEMO_TIMEOUT_SECONDS=30.0DEMO_LLM_MODE=demo
The local review path should use deterministic demo mode:
cp .env.example .env # once, if .env does not exist
docker compose up --build -d
python scripts/demo.py --llm-mode demoDemo mode routes model calls through committed fixture logic inside
app/llm_client.py; it still uses the same input guard, policy, output guard,
approval, audit, cost, and webhook service boundaries as live mode.
Live LLM mode remains available for manual experiments:
printf "\nLLM_MODE=live\nANTHROPIC_API_KEY=sk-...\n" >> .env
docker compose up --build -d
python scripts/demo.py --llm-mode liveUse live mode only with an explicit API key, a small tenant daily_budget_usd,
and awareness that paid provider calls may occur.
The demo fixture contract includes representative webhook payloads in
load_tests/fixtures/sample_messages.jsonl.
scripts/seed_db.py records the same message IDs in DEMO_SUPPORT_CASES so
tests fail if docs, fixtures, or seed assumptions drift.
| Case type | Message ID | Tenant | Purpose |
|---|---|---|---|
| normal | sample-normal-01 |
test-tenant-a |
Low-risk gameplay/settings request |
| risky | sample-risky-01 |
test-tenant-a |
Billing refund case that should require approval |
| adversarial | sample-adversarial-01 |
test-tenant-b |
Prompt-injection shaped support text |
| low_confidence | sample-low-confidence-01 |
test-tenant-a |
Ambiguous issue report for escalation behavior |
| duplicate | sample-duplicate-01 |
test-tenant-a |
Two committed rows with the same message_id for replay/idempotency checks |
Use the single-command wrapper for the deterministic local path:
make demoEquivalent direct wrapper:
bash scripts/demo.shVerify migrations against the running Compose stack:
docker compose exec agent python scripts/cli.py migrations checkExpected output:
migration_status=ok current=<alembic_head> heads=<alembic_head>
If this command reports migration_status=drift, stop and inspect Alembic
before running the demo; the local stack should not be treated as a valid
review environment until the database revision matches the repository head.
Use the project environment that already has httpx installed if calling the
Python runner directly:
python scripts/demo.py --llm-mode demoOverride the API URL if needed:
python scripts/demo.py --url http://localhost:8001 --llm-mode demoExample with explicit env overrides:
DEMO_TENANT_SLUG=test-tenant-a \
DEMO_WEBHOOK_SECRET=test-webhook-secret-a \
DEMO_ADMIN_EMAIL=admin-a@example.com \
DEMO_ADMIN_PASSWORD=password123 \
python scripts/demo.py --url http://localhost:8000Each step prints a UTC timestamp plus per-step timing. A successful run looks like:
[2026-03-20 15:00:00 UTC] [START] Base URL http://localhost:8000
[2026-03-20 15:00:00 UTC] [MODE] Expected server LLM mode: demo
[2026-03-20 15:00:00 UTC] [STEP] Health check
[2026-03-20 15:00:00 UTC] [DONE] Health check (0.02s)
[2026-03-20 15:00:00 UTC] [STEP] Auth token
[2026-03-20 15:00:00 UTC] [DONE] Auth token (0.08s)
[2026-03-20 15:00:00 UTC] [STEP] Send signed webhook
[2026-03-20 15:00:01 UTC] [DONE] Send signed webhook (0.41s)
[2026-03-20 15:00:01 UTC] [PENDING] Pending approval created: ...
[2026-03-20 15:00:01 UTC] [STEP] Audit lookup for pending row
[2026-03-20 15:00:01 UTC] [DONE] Audit lookup for pending row (0.05s)
[2026-03-20 15:00:01 UTC] [STEP] Approve pending action
[2026-03-20 15:00:01 UTC] [DONE] Approve pending action (0.06s)
[2026-03-20 15:00:01 UTC] [APPROVED] Approval decision status=approved
[2026-03-20 15:00:01 UTC] [STEP] Metrics check
[2026-03-20 15:00:01 UTC] [DONE] Metrics check (0.03s)
[2026-03-20 15:00:01 UTC] [OK] Demo completed for pending_id=... in 0.62s
On failure the script exits non-zero and prints the failing step or HTTP error to stderr.
Target length: 90-150 seconds. Use deterministic demo mode unless the recording explicitly calls out that paid live-provider calls are being used.
- Setup and status:
git status --short
docker compose up --build -d
docker compose exec agent python scripts/cli.py migrations checkShow that only expected local files are dirty, the stack is up, and
migration_status=ok.
- Deterministic demo run:
make demoShow the signed webhook, pending approval creation, audit lookup, approval, and
metrics check lines from the command output. Keep the terminal large enough that
the timestamps and [STEP] / [DONE] labels are readable.
- Approval and audit evidence:
python scripts/demo.py --llm-mode demoCapture the PENDING, APPROVED, and final [OK] lines. If using the API
directly, show POST /webhook, POST /approve, and GET /audit with the same
tenant context.
- Metrics and observability evidence:
curl -fsS http://localhost:8000/metrics | rg "gdev_|http_"Show at least one workflow metric and one HTTP metric. The local observability stack and alerting notes are documented in docs/observability.md.
- Test and evidence pointers:
.venv/bin/python -m pytest tests/ -q
rg -n "tests pass|eval|load|tenant isolation|observability" README.md docs/CASE_STUDY.mdShow where an operator can inspect the current test baseline, eval summary, load summary, tenant-isolation proof, and observability proof. If the full test run is too long for the recording, show the command and the latest committed baseline in README, then keep the full run in terminal history for follow-up.
Recommended output name once manually produced: docs/assets/gdev-agent-demo.gif
or a linked external video. Until that file or link exists, docs should link to
this checklist instead of implying a finished demo artifact.
| Symptom | Likely cause | Fix |
|---|---|---|
stack unavailable |
API container is not running or URL is wrong | Run docker compose up --build -d and check BASE_URL |
migration_status=drift |
Database schema revision does not match the repository Alembic head | Re-run docker compose up --build -d migrate or reset the local Compose volumes before demo review |
auth failed; verify docker/seed.sql was applied |
Seed data is missing or demo credentials drifted | Re-run the migrate service or restart Compose after checking docker/seed.sql |
signed webhook failed |
Tenant slug or webhook secret mismatch | Use the defaults table above or update DEMO_TENANT_SLUG and DEMO_WEBHOOK_SECRET together |
webhook did not produce pending state |
Server is not in deterministic mode or billing policy changed | Set LLM_MODE=demo in .env and keep approval_categories including billing |
metrics endpoint did not include gdev workflow metrics |
Metrics route unavailable or app did not initialize metrics | Check GET /metrics and container logs |
GET /healthis an application liveness probe. It returns200when the FastAPI process is running and settings loaded.- Compose readiness is stricter:
agentstarts after Postgres and Redis are healthy and aftermigratecompletes Alembic, migration verification, and seed data. - Prometheus, Grafana, n8n, Tempo, and Loki depend on service/container
health checks in
docker-compose.yml; these checks are local review proof, not a production SLA.