Skip to main content

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​

ComponentWhat it does
customer_demo Postgres database5 tables — customers (~200), products (~80), orders (~900), order_items (~2,300), events (~3,000) — seeded deterministically.
Drift loop daemonInserts 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 containerPicks 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-level CLAUDE.md for setup).
  • A hosted Measured Cloud dev tenant you control, with:
    • An agent token (dqa_…) issued from Settings → Agents. A fresh environment seeded with dqc seed already has one: the command prints it once.
    • The email and password of a super admin in that org, for the setup script.

Environment variables​

VariableRequired forNotes
DQ_CLOUD_BASE_URLagent + setup scripte.g. https://dev.dq.example.com.
DQ_CLOUD_AGENT_TOKENagentThe dqa_… token.
DQ_CLOUD_EMAIL / DQ_CLOUD_PASSWORDsetup scriptA 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_KEYsetup scriptOptional instead of the email and password: a dqk_… personal API key. The password-only steps are then skipped with a warning.
DQ_CLOUD_WORKSPACE_IDsetup scriptOptional. Defaults to your current workspace.
DQ_CLOUD_DATASOURCE_IDsetup scriptUUID returned when the agent registered customer_demo (see step 3).
DQ_SETUP_DATASOURCE_DSNsetup scriptOptional 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_KEYsetup scriptOptional key for --llm-provider / --llm-model.
CUSTOMER_DEMO_DSNseed + drift loopOptional — defaults to postgresql://postgres:postgres@localhost:5432/customer_demo.
DQ_AGENT_DATASOURCE_DSNSagentJSON 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:

  1. Create the customer_demo database (if missing).
  2. Drop and rebuild the five tables, inserting the seeded row counts.
  3. Start the drift loop as a background process. PID + log live in .demo/drift_loop.{pid,log}.
  4. If DQ_CLOUD_BASE_URL and DQ_CLOUD_AGENT_TOKEN are 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:

  1. a team in your workspace, and an invite per --invite email[:role];
  2. a default LLM config when you pass --llm-provider and --llm-model;
  3. the data source (DQ_CLOUD_DATASOURCE_ID, or one created from DQ_SETUP_DATASOURCE_DSN);
  4. one data asset per table (customers, products, orders, order_items, events);
  5. rules on each asset (each also joins its asset's default validation);
  6. one validation, "Customer demo checks", holding every rule, plus a schedule when you pass --cron;
  7. an email alert to you when the validation fails;
  8. 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​