Skip to content

Latest commit

 

History

397 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Dex: analytics engineering for your coding agent. Explore, transform, maintain.

Dex

Analytics engineering for your coding agent, with guardrails and lifecycle built into the engine.

Dex lets your agent build your analytics: data warehouse exploration, dbt data transformation and semantic modeling, and schema-drift maintenance.

Built by Exmergo

PyPI License data-eng-bench ADE-bench CI

Docs LinkedIn X Star the repo!

Install

Any agent (run in your terminal):

npx skills add exmergo/dex

Claude Code (run these inside Claude Code, one at a time):

/plugin marketplace add exmergo/exmergo-agent-plugins
/plugin install dex@exmergo

Codex:

npx skills add exmergo/dex --agent codex

Important

Dex needs uv on your PATH. No agent installs it for you. Run brew install uv or pipx install uv, or see the uv installation docs for other platforms. uv supplies Python and the engine. Use uv 0.13.4 or later (uv self update): earlier releases still trust the emSign CA roots withdrawn after their keys were compromised.

In Claude Code the skills appear as /dex:explore, /dex:transform, and /dex:maintain and trigger on matching intent. Update later with /plugin marketplace update exmergo. Codex also gets a native plugin, with no MCP server needed. Any other agent can be pointed at AGENTS.md.

Run Dex from your project directory, so it finds your .dex/config.yml and keeps its cache next to your project. Dex needs an agent with a local shell, so it does not run in claude.ai chat.

Tip

Skip the first-run wait. The first command installs the engine, which takes tens of seconds on a cold uv cache. Ask your agent to warm Dex once after installing, or run the command below in a CI or container build step. Add --connector snowflake (or any connector) to warm one before a project exists.

uv run --no-project --script skills/<skill>/scripts/run.py --warm

What you get:

  • Explore a warehouse you have never seen. Ranked inventory, column profiles built from aggregates, PII flags, verified joins, and an ER diagram.
  • Transform your dbt project and semantic layer. Models, tests, docs, and metrics arrive as reviewable plans and build only on a dev target.
  • Maintain it as the world changes. Schema, volume, grain, and metric-definition drift, each with a proposed fix.

Your source data is never written to, and builds run on a dev target only. Cost quoted before any spend. Every change is a diff you review. Works in Claude Code, Codex, or any agent that loads Agent Skills.

Why

Most deadly errors arise from obsolete assumptions.

Frank Herbert, Children of Dune

A general coding agent is a good SQL author and a poor analytics engineer. It:

  • re-learns the schema every session,
  • has no strategy for a warehouse with thousands of tables,
  • is blind to what a query costs,
  • will happily pull personal data into its context,
  • does not treat a dbt project or a semantic layer as an object to keep coherent over time.

Dex owns exactly that loop, and enforces its rules in the engine rather than in a prompt.

Try it in three commands

No warehouse, no credentials, no cloud account, no network.

pip install "exmergo-dex-core[duckdb]"
dex demo
dex explore map

dex demo writes a small e-commerce DuckDB warehouse (dex_demo.duckdb, 7 tables, 29,512 rows) and a .dex/config.yml into the current directory, so every command after it runs with no flags. It refuses rather than overwrite anything. The data comes from a pinned seed, so what you see is what is written here.

explore map maps all 7 tables, infers 5 joins, flags 6 columns as personal data, and reports 6 data-quality findings. Every command answers with one JSON envelope. An excerpt, for one table:

{
  "identifier": "dex_demo.main.order_items",
  "row_count": 14000,
  "grain": null,
  "data_quality": [
    "order_item_id is not unique: 13000 distinct over 14000 rows (1000 rows would have to be removed for it to be unique, so it is unique for 92.9% of rows); joins on it will fan out"
  ]
}

It is broken on purpose

A first run that reports a clean bill of health teaches you nothing, so the demo ships with real defects. Dig into them:

dex explore profile order_items products
dex explore relationships --verify
dex explore query "select email from customers"
  • A broken grain. A batch was loaded twice, so order_item_id is unique for only 92.9% of rows. Dex reports the duplicates and leaves the grain unknown rather than guessing a key.
  • A key that mixes id schemes. sku is 90% numeric, 10% 32-character hexadecimal (md5-shaped). Cast it to a number and you silently drop 10% of products.
  • A join that looks right and is not. web_events.customer_id shares a name with the CRM's key, but verification finds 100% of its values have no match.
  • A refusal. The query firewall will not put customers.email into context. select count(distinct email) from customers runs, because a statistic is not a value.

There is also a table an interrupted load left empty, two columns whose declared type contradicts their content, and two designed PII false positives. See the dex demo reference.

Note

DuckDB is free and local, so nothing here asks you to confirm a spend. On a metered warehouse the same commands return an estimate first and run only once you agree to it.

How it works

flowchart LR
  Agent[Coding agent] --> Skills[Dex skills]
  Skills --> Engine[exmergo-dex-core engine]
  Engine -->|SELECT-only SQL, cost-capped| Warehouse[(Your warehouse)]
  Engine -->|dbt builds, dev target only| Warehouse
  Engine -->|one sanitized JSON envelope| Agent
  Engine -->|plans as diffs| Repo[Your repository: dbt project + semantic layer]
  Engine <--> Cache[.dex/ cache and snapshot]
Loading

The skills are thin. All the logic, and every guardrail, lives in one engine (exmergo-dex-core) behind a small command contract. Your repository is the source of truth on two independent axes: the transformation project (dbt) and the semantic layer. The .dex/ directory is only a cache.

Explore

Rank what matters, profile selectively, infer and verify joins, answer ad-hoc questions through a PII-aware query firewall, read and query the semantic layer, and draw the map as a Mermaid ER diagram. Fully read-only. Skill · methodology · commands

Explore

Transform

Author and refactor dbt models from staging to marts, with tests and docs, plus the semantic layer on top. Every change is a stored plan you review, then apply. Builds run against a dev target only. Skill · what Dex writes · commands

Transform

Maintain

Diff the warehouse, the project, and the semantic layer against the last snapshot, rank the drift by blast radius, and propose the reconciling edits. maintain verify checks a project as it stands, with no baseline needed. Skill · commands

Maintain

What to commit in .dex/

Dex keeps its working state in .dex/. Most of it is safe to commit; the exception is any file that can carry data values. Commit the lockfile and the config, and keep the exploration cache out of a repo that must not publish data values:

File Commit? Can it contain data values?
.dex/config.yml Yes No: connector targets, pii_overrides (column names, not values), budgets and limits. Credentials live in environment variables or dbt profiles, never in this file.
.dex/snapshot.json Yes, like a lockfile Yes: when snapshotted from a cache it pins the cache's datasets wholesale, including each non-PII column's capped value domain and min/max. A metadata-only baseline (no cache) carries schema and volume only. PII-flagged columns never carry value domains.
.dex/cache.json Only if values may be public Yes: column profiles with capped value domains (actual values with counts). Never for PII-flagged columns.
.dex/plans/*.json Optional, as an audit trail No warehouse values: the proposed contents of project files.
.dex/queries.jsonl Optional, as a ledger SQL text, which may hold literals.
.dex/drift.json No: regenerate with maintain check Aggregates only: counts, names and schema metadata in drift findings.

For a repo that must not publish data values, ignore the rest:

# Dex state that can publish data values, or is local-only output.
.dex/cache.json
.dex/dex.db
.dex/dex.db-journal
.dex/queries.jsonl
.dex/drift.json
.dex/spend.jsonl
.dex/spend.lock

.dex/dex.db is the SQLite backend's equivalent of cache.json, so it gets the same caution. .dex/spend.jsonl is the billed-command ledger: byte counts, job ids and statement hashes only, never SQL values or credentials, but it is local spend history, so there is no reason to commit it.

What Dex will never do

  • Write to your source data. Dex's own SQL is SELECT-only. The only writes to your warehouse are the dbt builds you confirm, on a dev target.
  • Put personal data in your agent's context. PII is flagged by column, category, and confidence, and never surfaced. Only a human clears a flag. (PII policy)
  • Spend before quoting. A metered scan returns an estimate and runs only with your confirmation and a budget. (cost controls)
  • Change your project behind your back. Every edit is a diff, and a human edit made since planning is never overwritten. (source of truth)
  • Build anywhere but dev. Prod-looking targets are refused outright.

Connectors

Connector Type Self-hostable Extra / --connector Cost surfaced as Credentials from
Snowflake Cloud warehouse ❌ snowflake Warehouse-seconds, with credits connections.toml, SNOWFLAKE_* env, or a dbt profile
BigQuery Cloud warehouse ❌ bigquery Bytes scanned Application Default Credentials
Databricks Cloud warehouse ❌ databricks Warehouse-seconds, with DBUs databricks auth login, DATABRICKS_* env, or a dbt profile
Amazon Redshift Cloud warehouse, Serverless-first ❌ redshift Compute-seconds, with RPU-hours The AWS credential chain or REDSHIFT_* env
ClickHouse Analytical database ✅ clickhouse Compute-seconds on Cloud, database-seconds self-hosted CLICKHOUSE_URL, CLICKHOUSE_* env, or a dbt profile
Trino Federated query engine (Trino, Starburst Enterprise, Trino on EMR) ✅ trino Cluster-seconds TRINO_URL, TRINO_* env, or a dbt profile
DuckDB Embedded analytical ✅ duckdb Free and local None, just a file path
Postgres Operational database ✅ postgres Database-seconds pg_service.conf, DATABASE_URL, PG* env, or a dbt profile

Credentials are discovered, never asked for, and never enter the agent's context. Every scan is estimated, confirmed, and capped server-side, and settled spend is recorded in a local ledger (cost controls). Self-hosted databases bill no dollars and are still gated, because an unbounded scan on a production Postgres primary is a real cost too.

Coming next: MotherDuck, Starburst Galaxy, Microsoft Fabric.

Semantic layers

The semantic layer is its own axis, chosen once per repository in .dex/config.yml. With Ossie or SLayer, it needs no dbt project at all. (overview)

Semantic layer Hosted Queryable vendor / deployment Read through
dbt ✅ ✅ dbt / local or dbt_cloud MetricFlow locally, run through the Dex connector and its cost guard, or the hosted dbt Cloud Semantic Layer API
Apache Ossie (incubating) ❌ ❌ ossie / local Native documents in the repository, no dbt or MetricFlow in the path
SLayer ❌ ✅ slayer / local SLayer model files, validated by one pinned release; SLayer renders, Dex runs it under the cost guard

Ossie is catalog-first because the format specifies interchange metadata and no query runtime, so Dex reads, authors, and checks an Ossie layer but refuses a metric query by name rather than inventing semantics. See the Ossie page and the Ossie tutorial.

Coming next: Cube, Snowflake Semantic Views, Looker / LookML, and Databricks Metric Views.

See it on a real project

github-repo-analytics is a template that tracks GitHub traffic, stars, downloads and contributors for your open-source projects, free on GitHub Actions.
Every model and metric was built and is maintained with Dex. Its audit trail holds the raw envelopes, including the drift Dex blocked.

The Core Python Library you can use

The engine is a plain Python package, exmergo-dex-core:

pip install exmergo-dex-core     # or: uv add exmergo-dex-core

Drive the same loop from Python instead of parsing CLI output:

from exmergo_dex_core import DexEngine

with DexEngine(connector="duckdb", path="shop.duckdb") as eng:
    mapped = eng.map()                  # a DexCache, not a JSON blob
    rows = eng.query("select status, count(*) from orders group by status")

print(mapped.relationship_count, rows.cells)

Every guarantee the CLI makes holds here, because it is the same code. An unconfirmed billed call raises ConfirmationRequiredError carrying the estimate. By default nothing is written to disk; DexEngine.from_repo("path/to/repo") opts in to a project on disk, the way the CLI does.

For hosts embedding Dex:

  • Values-free mode keeps every table value out of the model's view, aggregates only. (values-free mode)
  • Per-user credentials: pass a ConnectionSource so each request reaches the warehouse as its own principal.
  • Multi-process lifecycles: export a plan, verify and apply it elsewhere, and get typed evidence of what a build established. (host integration)
  • Other formats and stores: plug in a project format or a storage backend.

More in the package README.

Benchmarks

We run Dex on two public analytics-engineering benchmarks and commit every run's raw per-task results, including the ones that flatter us least.

Benchmark Setup Result Details
data-eng-bench (Snowflake, 103 tasks) Dex + Claude Sonnet 5 57.3% resolved, 89.7% of assertions pass README
ADE-bench (dbt Labs, 75 tasks) Dex + Claude Sonnet 5 76% resolved, at 2.5x lower cost than Fable 5 README
ADE-bench accuracy and cost by model

We believe that benchmarks are important, but not as important as user/agent experience.
Trust in a diff, cost surfaced before spend, and sensitive data kept out of context. We optimize for that experience and treat these scores as guide posts. (evaluation)

What Dex sends where

  • On install, uv downloads the pinned engine (and a Python 3.11 to 3.14 if you have none) from PyPI or your configured index.
  • Your warehouse gets SELECT-only SQL from Dex, plus the dbt builds you confirm on a dev target, with credentials from your existing credential chain.
  • dbt Cloud is contacted only if you enable the hosted semantic layer.
  • dbt-core, when Dex runs it, sends dbt Labs its own anonymous usage statistics unless you turn them off. transform deps downloads the packages your packages.yml declares.
  • Your repository holds Dex's state in .dex/. dbt writes its usual target/ and logs/.

Important

Dex sends nothing to Exmergo. No telemetry, analytics, license check, or update ping. See the Dex privacy policy.

Further reading

Dex Enterprise

Dex Enterprise runs Dex as automated workflows in your own CI. It turns tickets into draft pull requests with explore and transform, and runs maintain on a schedule to turn drift into draft pull requests for your engineers to review. No Exmergo service sits in the request path, and everything in this repository stays Apache-2.0. Learn more.

Community

Community Integrations

dagster-dex, maintained by CatInCloud Labs, lets Dex read a Dagster asset graph instead of a dbt project, so exploration and drift maintenance run natively against your orchestrated assets.

Connect with the Community

Join the agent-native data/analytics community!

Contributing

See CONTRIBUTING.md for local setup, linting, the pre-commit hook, and changelog fragments. Every pull request into main must pass Lint and CI, and adds a fragment under changelog.d/ unless no user can see the change. Please also read the code of conduct.

License

Apache-2.0. See LICENSE and NOTICE.