Quickstart¶
From an empty directory to a governed answer over the bundled demo warehouse, then over your own. Everything runs from the terminal against the scaffolded project — the dashboard is optional throughout.
Prerequisites: Python 3.12+, Docker (for the project's Postgres), and an API key for at
least one model provider — Anthropic, or any openai-compatible endpoint (DeepSeek,
Ollama, vLLM, Groq, most gateways; services/config.py).
1. Install¶
pip install dst-core
dst --version
One package, one command: dst. The wheel carries the database migrations and the
bundled DuckDB demo warehouse, so nothing below needs the source tree
(pyproject.toml).
Building the wheel from a source checkout instead (vendored snapshot, pinned
fork)? Build the dashboard first or you get an API-only wheel:
pnpm -C apps/web install && pnpm -C apps/web build, then uv build --wheel
— the build prints whether the dashboard was bundled.
2. Scaffold a project¶
dst init analytics --warehouse demo --yes
cd analytics
--yes takes the defaults instead of prompting (required when scripting — there is no
tty to answer the prompts). If ports 8000/5432 are taken, add --api-port/--db-port:
the flags write the compose mapping, the database URLs, and DST_URL in one pass.
dst init writes a dbt-style project (services/cli/init.py): dst.yaml
(providers + connection declarations), a gitignored .env with a generated
DST_SECRET_KEY and local database URLs, a docker-compose.yml for Postgres, a
shared semantic layer under semantic/ with example assets, an example lens at
lenses/customer_value/ over the demo warehouse, an AGENTS.md guide for AI agents,
and a git init. dst.yaml and lens.yaml end with a commented reference block
rendered from the schema itself — uncomment fields instead of guessing names. See
Project files.
Fill the one secret the scaffold declares — open .env and set:
DST_API_KEY_ANTHROPIC=sk-ant-...
Secrets live only in .env; the tracked files refer to them by env-var name. An inline
key in dst.yaml is a parse error, not a lint warning
(services/project/schema.py).
3. Start the server¶
dst dev
One command: brings up the project's Postgres via docker compose if nothing answers at
DATABASE_URL, runs migrations, then serves the API (and the dashboard, when bundled)
on port 8000 (services/cli/main.py). Leave it running; the rest happens in a second
terminal in the same directory.
4. Bootstrap, then deploy the files¶
dst bootstrap --org me --email you@example.com
dst apply
bootstrap prompts for the admin password (pass --password to script it), creates
the org, and mints an admin token, saved into .env as
DST_ADMIN_TOKEN — every later command reads it from there, so nothing below needs
flags. --email also creates the first dashboard admin (log in at
http://localhost:8000); omit it if you don't want the dashboard yet. Rerunning
bootstrap is idempotent: it reuses the org and only mints a fresh token.
apply deploys the project directory to the server: connection declarations are probed
(connect + read) before landing — a dead credential never replaces a working one — then
shared assets and lenses land in one transaction. Any error aborts everything and the
prior versions keep serving (services/project/apply.py).
5. First governed answer¶
dst query customer_value "How many customers are repeat customers?"
The answer comes back with the SQL that produced it and a confidence grade — the receipts every governed answer carries.
Clarify
Ask dst query customer_value "What is the average value of a customer?" and
you get a clarify prompt instead of a number: the scaffolded value definition is
status: ambiguous, so dst asks which meaning is intended rather than guessing.
See Clarify & refusal.
— scaffolded demo lens, services/lenses/demo.py
6. Connect your own warehouse¶
Declare the connection in dst.yaml — warehouse types: duckdb, postgres,
mysql, bigquery, snowflake; the same block also declares object stores (s3,
gcs) (services/project/schema.py):
connections:
wh:
type: bigquery
config: {project: my-gcp-project}
secret_env: DST_API_KEY_WH
and put the credential in .env. A value of @/path/to/file loads that file's
contents — the idiom for a BigQuery service-account JSON (services/config.py):
DST_API_KEY_WH=@/path/to/service-account.json
Then author the semantic layer from the warehouse itself:
dst introspect --connection wh --profile # schema + facts, agent-legible — reads
# dst.yaml, so it runs BEFORE the
# first apply
dst apply # probes the connection before landing it
Introspect searches every non-system schema and qualifies names
(spider.player); one column per line, printing the value its fields[].type
takes with the warehouse type in parentheses. --profile samples the warehouse
for enum values, null rates and ranges (row-capped, PII-safe reads, one pass per
table in scope) — in a warehouse whose status holds 'A'/'C'/'X', those codes
are the business knowledge you are here to write down. Without it the listing is
schema only and says so.
Add --json when something parses the output instead of reading it.
Write semantic/entities/*.yaml and semantic/definitions/*.md from the introspect
output (the scaffolded .claude/skills/dst-semantic/ skill walks an agent through
it), select them in a lens's lens.yaml, then dst plan → dst apply →
dst query to verify. Details: Connect a warehouse
and Lenses.
7. Let callers in¶
dst keys create --caller alex
One key per person — agents ask on a person's behalf, and attribution is the point.
Access is deny-by-default: a caller queries a lens only with a matching entry in that
lens's lens.yaml (services/contracts/lens_config.py):
access:
allow:
- caller: alex # or: - group: everyone (any valid key in the org)
dst apply again, then prove the grant — ask as that caller, and check that an
ungranted one is still refused:
dst query customer_value "how many customers?" --key dst_alex... # → the answer
dst query customer_value "how many customers?" --key dst_other... # → exit 1, 403
--key is not a convenience: your admin token bypasses every allow-list, so without it
both of those return an answer and the grant is never actually tested.
Then connect an agent to the governed MCP door with the caller's key:
claude mcp add dst http://localhost:8000/mcp --transport http \
--header "Authorization: Bearer dst_..."
The agent gets the same governed pipeline as every other caller — see Agents over MCP, and the API reference for the REST and OpenAI-compatible doors.
Upgrading later? Run dst migrate after every pip install -U — dst serve
refuses a schema behind its build. See Upgrading.