Run the on-prem agent demo
The on-prem agent demo stands up a believable customer environment on your laptop: five tables in a local Postgres database, a drift loop that keeps writing fresh rows (and the occasional bad one), and the DQ Agent pointed at the hosted dev cloud. The first 60 seconds of UI look like a real production deployment instead of a 7-row toy.
This is a demo runbook only — none of the components are intended for production use.
What you get
| Component | What it does |
|---|---|
customer_demo Postgres database | 5 tables — customers (~200), products (~80), orders (~900), order_items (~2,300), events (~3,000) — seeded deterministically. |
| Drift loop daemon | Inserts a small batch of valid rows every 30-60 s. 5-10% of ticks insert one bad row that violates exactly one expectation. 3% of ticks corrupt an existing row and heal it 60 s later. |
| DQ Agent container | Picks up validation runs from the hosted cloud, executes them locally against customer_demo, and posts metadata back. |
Setup script (scripts/setup_env.py) | Walks the hosted side end to end: a team and invites, an optional LLM config, the data source, 5 data assets, 14 rules, one validation holding them all, an optional schedule, an email alert on failures, and a first run. |
End-state: a dashboard with a >95% pass rate, a small share of runs showing 1-2 failed rules, and a visible "anomaly recovered" arc on the timeline whenever the corrupt+heal flow fires.
Prereqs
- Docker (for the local Postgres + the agent container).
uv(the workspace package manager — see the top-levelCLAUDE.mdfor setup).- A hosted Measured Cloud dev tenant you control, with:
- An agent token (
dqa_…) issued from Settings → Agents. A fresh environment seeded withdqc seedalready has one: the command prints it once. - The email and password of a super admin in that org, for the setup script.
- An agent token (
Environment variables
| Variable | Required for | Notes |
|---|---|---|
DQ_CLOUD_BASE_URL | agent + setup script | e.g. https://dev.dq.example.com. |
DQ_CLOUD_AGENT_TOKEN | agent | The dqa_… token. |
DQ_CLOUD_EMAIL / DQ_CLOUD_PASSWORD | setup script | A super admin's sign-in. The server asks for a recent password sign-in before it creates a data source, an alert rule, an LLM config or an invite. |
DQ_CLOUD_API_KEY | setup script | Optional instead of the email and password: a dqk_… personal API key. The password-only steps are then skipped with a warning. |
DQ_CLOUD_WORKSPACE_ID | setup script | Optional. Defaults to your current workspace. |
DQ_CLOUD_DATASOURCE_ID | setup script | UUID returned when the agent registered customer_demo (see step 3). |
DQ_SETUP_DATASOURCE_DSN | setup script | Optional instead of DQ_CLOUD_DATASOURCE_ID: a Postgres connection string the hosted worker can reach, to create a direct-mode data source. |
DQ_SETUP_LLM_API_KEY | setup script | Optional key for --llm-provider / --llm-model. |
CUSTOMER_DEMO_DSN | seed + drift loop | Optional — defaults to postgresql://postgres:postgres@localhost:5432/customer_demo. |
DQ_AGENT_DATASOURCE_DSNS | agent | JSON object mapping datasource UUID → local DSN. See step 4. |
1. Stand up local Postgres
The same compose file the rest of the dev environment uses:
make dev
Postgres lands on :5432 with postgres / postgres as the default
credentials. The seed script creates customer_demo for you on first run.
2. Seed customer_demo and start the drift loop
make demo-up
The first time you run it make demo-up will:
- Create the
customer_demodatabase (if missing). - Drop and rebuild the five tables, inserting the seeded row counts.
- Start the drift loop as a background process. PID + log live in
.demo/drift_loop.{pid,log}. - If
DQ_CLOUD_BASE_URLandDQ_CLOUD_AGENT_TOKENare set, start the agent container too.
Tail the drift log to confirm it's alive:
tail -f .demo/drift_loop.log
You should see entries like:
INFO drift_loop valid_insert order_id=901 customer_id=27
INFO drift_loop valid_insert order_id=902 customer_id=143
WARNING drift_loop bad_insert kind=order expectation=orders.status in (...) customer_id=84
INFO drift_loop heal_applied order_id=412
3. Register customer_demo with the cloud
From inside the agent container (one-shot registration):
docker exec dq-demo-agent python -c "
import httpx, os
r = httpx.post(
os.environ['DQ_AGENT_CLOUD_URL'] + '/api/agent/v1/datasources',
headers={'Authorization': f'Bearer {os.environ[\"DQ_AGENT_TOKEN\"]}'},
json={'name': 'customer_demo', 'type': 'postgres'},
)
print(r.status_code, r.json())
"
Copy the returned datasource UUID. Save it as DQ_CLOUD_DATASOURCE_ID.
4. Wire the agent's local DSN
The agent needs to know how to reach customer_demo from inside its
container. Restart it with DQ_AGENT_DATASOURCE_DSNS populated:
docker rm -f dq-demo-agent
DQ_AGENT_DATASOURCE_DSNS='{
"<datasource-uuid>": "postgresql://postgres:postgres@host.docker.internal:5432/customer_demo"
}' \
make demo-up
host.docker.internal resolves to the host machine on macOS and Linux
(with the --add-host=host.docker.internal:host-gateway flag the
Makefile passes). If you're on a host without that resolver, use the
literal 172.17.0.1 (Docker bridge default) or your VPN-side IP.
5. Set up the hosted side
Run the setup script. It signs in, then creates (or finds) each piece in order and prints a JSON summary:
- a team in your workspace, and an invite per
--invite email[:role]; - a default LLM config when you pass
--llm-providerand--llm-model; - the data source (
DQ_CLOUD_DATASOURCE_ID, or one created fromDQ_SETUP_DATASOURCE_DSN); - one data asset per table (
customers,products,orders,order_items,events); - rules on each asset (each also joins its asset's default validation);
- one validation, "Customer demo checks", holding every rule, plus a
schedule when you pass
--cron; - an email alert to you when the validation fails;
- a first run, which it waits for and reports.
DQ_CLOUD_BASE_URL=https://dev.dq.example.com \
DQ_CLOUD_EMAIL=admin@example.com DQ_CLOUD_PASSWORD=... \
DQ_CLOUD_DATASOURCE_ID=... \
uv run python -m scripts.setup_env --cron '*/5 * * * *' --invite ken@example.com:editor
The script is idempotent: it matches everything by name (rules by type and
settings) and reuses what exists, so re-running it only starts another run.
--no-run skips the run, --no-wait starts it without waiting, and
--tables orders,events limits the tables. uv run python -m scripts.setup_env --help
lists every option. The old
python -m infra.terraform.compositions.services.demo_setup command still
works and runs the same script.
Within a few minutes the dashboard starts to fill in:
- The validation ticking on its schedule, with an execution run per tick.
- A pass rate climbing past 95%.
- A timeline with the occasional red dot, plus the recover-after-60s arc whenever the drift loop's corrupt+heal flow fires.
Stopping
make demo-down # stop drift loop + agent container, keep data
make demo-reset # stop daemons AND re-seed customer_demo from scratch
make demo-down is non-destructive — the customer_demo data stays, so
restarting with make demo-up resumes where you left off.
Troubleshooting
could not translate host name "host.docker.internal". Your Docker
build doesn't add the host gateway automatically. The Makefile passes
--add-host=host.docker.internal:host-gateway, which works on Docker
20.10+; older Engines need a manual --network host swap or the literal
bridge IP.
relation "customers" does not exist. The drift loop ran before the
seed. Run make demo-reset to drop + reseed, then make demo-up again.
Pass rate stays at 100%. The drift loop hasn't injected a bad row
yet — bad inserts are 5-10% of ticks, so on average one every ~10 ticks
(5-10 minutes). Tail .demo/drift_loop.log and grep for bad_insert to
confirm; once one fires, the next validation run for that table picks it
up. If bad_insert lines are present but the UI still shows green,
check that the agent container is up and not throwing
agent_poll_failed warnings (docker logs dq-demo-agent).
error: no data source named 'customer_demo' from the setup script.
Pass the agent-registered id as DQ_CLOUD_DATASOURCE_ID (step 3), or set
DQ_SETUP_DATASOURCE_DSN.
skip invites: needs a password sign-in (or llm_config / alert_rule).
You ran the script with an API key. Those steps need a recent password
sign-in: set DQ_CLOUD_EMAIL and DQ_CLOUD_PASSWORD instead.
Re-registering the datasource returns 409. The hosted API prevents
the same agent token from registering two datasources with the same
name. Either deregister the old one (DELETE /api/agent/v1/datasources/<id>
from inside the agent container) or use a different name.
See also
- Deploy the on-prem agent — production runbook.
- Agent mode — architecture and metadata cut.
scripts/demo_data/— seed + drift sources.scripts/setup_env.py— the hosted-side setup script.