DVT RUN
dvt run works exactly as you know it — same selection syntax, same flags and short forms, same status lines, same exit codes. What changes is what it can run: federation models that read from more than one engine sit in the same DAG as everything else, and one invocation runs them all.
THE MINIMAL INVOCATION
No flags runs the whole project on the default target. Here's a project with three regular models and two federation models — orders_enriched joins Postgres orders to MySQL customers, daily_revenue reads from one connection only:
$ dvt run 06:24:02 Found 5 models, 3 sources, 428 macros 06:24:03 Concurrency: 4 threads (target='pg_warehouse') 06:24:03 06:24:03 1 of 1 START sql view model analytics.stg_orders ................... [RUN] 06:24:04 1 of 1 OK created sql view model analytics.stg_orders .............. [CREATE VIEW in 0.38s] 06:24:04 06:24:04 Finished running 1 view model in 0 hours 0 minutes and 0.92 seconds (0.92s). 06:24:04 Completed successfully 06:24:04 Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1 06:24:05 Running 2 federation models (threads=4) 06:24:05 1 of 2 START federation table model pg_warehouse.orders_enriched ............................... [RUN] 06:24:05 2 of 2 START federation incremental model pg_warehouse.daily_revenue ........................... [RUN] 06:24:08 2 of 2 OK created federation incremental model pg_warehouse.daily_revenue ...... [SLING 1204 in 2.81s] [1204 rows inserted] [sling direct — computed on the source engine] 06:24:10 1 of 2 OK created federation table model pg_warehouse.orders_enriched .......... [SLING 8412 in 4.63s] [8412 rows inserted] 06:24:10 Finished running 2 federation models in 0 hours 0 minutes and 4.87 seconds (4.87s). 06:24:10 Completed successfully 06:24:10 Done. PASS=2 WARN=0 ERROR=0 SKIP=0 TOTAL=2 06:24:11 Concurrency: 4 threads (target='pg_warehouse') 06:24:11 06:24:11 1 of 1 START sql table model analytics.revenue_report .............. [RUN] 06:24:12 1 of 1 OK created sql table model analytics.revenue_report ......... [SELECT 311 in 0.74s] 06:24:12 06:24:12 Finished running 1 table model in 0 hours 0 minutes and 1.21 seconds (1.21s). 06:24:12 Completed successfully 06:24:12 Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1
Three phases, one DAG. stg_orders runs first because a federation model reads it — extraction must see fresh data. Then the two federation models run as a parallel wave: daily_revenue's sources all live on one connection, so the whole query ran there ([sling direct] — nothing extracted, nothing staged); orders_enriched spans two engines, so DVT extracted, computed and loaded it — 8,412 rows landed on pg_warehouse. Finally the rest of the project runs, and revenue_report reads the landed orders_enriched table like any other relation. Each phase prints its own honest tally; if any phase fails, the run exits nonzero.
IN PLAIN DBT
A model that joins tables on two different engines has nowhere to run — a project is wired to one connection, and everything else means an EL tool, a staging schema, and a second pipeline to keep in sync. In DVT it's one more model in the same DAG, run by the same command — and federation models never touch the materialization layer, so no custom macros, no plugins, nothing to install on either engine.
ONE DAG, THREE PHASES
Every run begins with the parse gate: dvt run starts from a passing dvt parse, and a broken project refuses with the parse error itself and exit 2 — zero graph work, zero engine connections, nothing touched.
A healthy project is then compiled quietly and partitioned. Regular models that are ancestors of a federation model run first — they must be fresh before extraction reads them. Federation models run next, in dependency waves: each wave is every federation model whose federation dependencies are complete, executed in parallel up to --threads, wave after wave until the graph is done. Everything else runs last, with the federation models excluded — including their descendants, which by then can read the relations federation landed.
Note what this is not: DVT never reorders your DAG. The phases are just your dependency graph, walked honestly across engines — every model still runs after everything it depends on.
THE RESULT TAGS — WHAT EACH LINE TELLS YOU
Federation result lines carry colored tags that say what actually happened, per model, right where you're looking:
[N rows inserted] (green) is what landed. [sling direct — computed on the source engine] (cyan) means every source lived on one connection and push-down was allowed, so the entire query ran on that engine — the fastest path. [federation compute — push-down declined by config] (cyan) is its counterpart: the model could have gone direct, but federation_direct: false said otherwise — and the tag names the level that declined, model config() or dvt_project.yml, so you know which file to edit. A model whose sources genuinely span connections shows no lane tag at all: federation compute is simply its natural path. [hooks: N pre, M post](cyan) appears when the model's hooks ran.
One tag is yellow, and it's a protection. A model materialized as table or viewwhose dependencies are federated onto another engine can't run as plain SQL there — so instead of failing, DVT runs it as f_table (incremental as f_incremental) and says so on its own result line, counted in the tally's WARN column:
06:52:14 4 of 4 OK created federation table model pg_warehouse.orders_summary ............ [SLING 311 in 1.66s] [311 rows inserted] [ran as f_table instead of view]
SELECTION — EXACTLY AS YOU KNOW IT
--select (or -s) takes the selection syntax you already use: bare names, graph operators, unions and intersections. Spaces mean union, commas mean intersection, and + walks the graph — with an optional hop count:
$ dvt run --select fct_sales # one model $ dvt run --select +fct_sales # the model plus everything upstream $ dvt run --select fct_sales+ # the model plus everything downstream $ dvt run --select 2+fct_sales # at most two hops upstream $ dvt run --select stg_orders fct_sales # union: either one $ dvt run --select tag:nightly,fct_sales+ # intersection: tagged AND downstream $ dvt run --exclude tag:slow # everything except
Richer selectors — tag:, path:, state:, @ — resolve exactly as they always have. --models / -m takes bare model names and folds them into the same selection, and --exclude carves out of whatever --select chose. A federation model caught by the selection runs in its wave; everything else runs in its phase — you select models, not lanes.
And a selection that matches nothing says so instead of silently exiting — it's usually a typo:
$ dvt run --select stg_odrers Nothing to do: the selection matched no models (--select stg_odrers). Check the name against `dvt ls`.
--FAIL-FAST — AN HONEST STOP
--fail-fast (or -x) stops the run at the first failure — but stops it honestly. Models already executing run to completion (killing an extraction mid-flight would leave half-loaded relations behind); nothing new starts, in the current wave or any later one; and everything that never started is reported as SKIP in the tally, never quietly dropped:
$ dvt run --fail-fast 06:31:38 Running 6 federation models (threads=4) 06:31:38 1 of 6 START federation table model pg_warehouse.customer_regions .............. [RUN] 06:31:38 2 of 6 START federation table model pg_warehouse.orders_enriched ............... [RUN] 06:31:38 3 of 6 START federation incremental model pg_warehouse.daily_revenue ........... [RUN] 06:31:40 1 of 6 OK created federation table model pg_warehouse.customer_regions ........... [SLING 96 in 1.94s] [96 rows inserted] 06:31:41 2 of 6 ERROR creating federation table model pg_warehouse.orders_enriched ........... [ERROR in 2.87s] 06:31:43 3 of 6 OK created federation incremental model pg_warehouse.daily_revenue ...... [SLING 1204 in 4.71s] [1204 rows inserted] [sling direct — computed on the source engine] 06:31:43 Aborting: --fail-fast — 3 model(s) skipped. 06:31:43 Finished running 6 federation models in 0 hours 0 minutes and 5.61 seconds (5.61s). 06:31:43 Completed with 1 error(s): 06:31:43 Failure in federation model orders_enriched 06:31:43 Error: column "amout" does not exist in pg_prod.public.orders 06:31:43 Done. PASS=2 WARN=0 ERROR=1 SKIP=3 TOTAL=6
daily_revenue was mid-flight when orders_enrichedfailed, so it finished — that's the PASS=2. The three models that hadn't started never ran, and the tally says exactly that: SKIP=3, not a page of phantom results. Without --fail-fast, the run keeps going and reports every failure at the end. The flag also reaches the engine phases, so an early failure among regular models stops the run the same way.
THE KNOBS — --VARS, --THREADS, --TARGET, --FULL-REFRESH
--vars takes a YAML/JSON dict string and reaches every phase of the run — one set of variables, no matter which lane a model executes in:
$ dvt run --vars '{run_date: 2026-08-01}'--threads overrides the thread count from your profile (which defaults to 4). One number governs everything: engine-phase concurrency, how many federation models run per wave, and a process-wide cap on extraction subprocesses — so --threads 8 can never fan out into 64 extractions behind your back.
--target (or -t) runs against a different output from your profile — dvt run -t pg_prod — and every phase follows it. Federation models pinned to an explicit target in their own config()keep writing where they're pinned, exactly as on any other run.
--full-refresh rebuilds from scratch: incremental models rebuild rather than append, and f_incremental models discard their local state and re-extract in full — the honest reset when backfills or schema changes make history suspect.
ONE ARTIFACT, ONE LEDGER
When the run finishes, federation results are merged into target/run_results.json alongside everything else — one artifact, one schema, so CI checks and observability tools see federation models exactly as they see the rest. Skipped models appear there too, as skips.
And every relation the run lands — either lane, any engine — is recorded at load time in DVT's materialization ledger: what landed, where, as what. That record is what dvt retract reads back when you want it all gone.
REFERENCE — EVERY FLAG
| FLAG | DEFAULT | WHAT IT DOES |
|---|---|---|
| --select, -s | everything | Selection syntax — which models to build. Repeatable; spaces union, commas intersect, +/N+ walk the graph, and tag:/path:/state:/@ resolve as you know them. |
| --models, -m | — | Specific model names (space-separated), folded into the same selection as --select. |
| --exclude | none | Selection syntax — which models to leave out (repeatable). Carves out of whatever --select chose. |
| --threads | profile setting | Threads for execution — engine concurrency, federation wave parallelism, and the cap on extraction subprocesses, all governed by one number. Your profile's target supplies the default (4 if unset). |
| --vars | — | Project variables as a YAML/JSON dict string, applied across every phase of the run. |
| --target, -t | profile default | Target to execute against. Every phase follows it; federation models pinned to an explicit target in their config keep their pin. |
| --full-refresh | off | Rebuild from scratch: incremental models rebuild rather than append, and f_incremental models discard local state and re-extract in full. |
| --fail-fast, -x | off | Stop at the first failure instead of finishing the rest. Models already executing run to completion; everything unstarted is reported SKIP in the tally. |
| --project-dir | . | Project directory (default: the current directory). |
| --profiles-dir | standard resolution | Directory holding profiles.yml. Resolves the same way as every other command: this flag, then $DBT_PROFILES_DIR, then the project directory, then your home profiles directory. |
| --debug | off | Debug logging — the engine's internal narration, hidden on a normal run, in full. |
Exit codes: 0on success — including an honest "nothing matched the selection" — 1 when any phase failed, 2 when the project failed to parse (nothing ran, nothing was touched).