What the agent does
The agent reads the project before the diff. In dbt-labs/jaffle-shop, dbt_project.yml names the project jaffle_shop, requires dbt >=2.0.0, points at a profile called default that no file in the repository defines, keeps data tests in data-tests/, builds staging models as views and marts as tables, and enables the CSV seeds under seeds/jaffle-data/ only when the variable load_source_data is true. packages.yml asks for dbt-labs/dbt_utils 1.4.1 and dbt-audit-helper from git at main, and package-lock.yml freezes that revision. Two macros matter for running anywhere: cents_to_dollars dispatches to default__, postgres__, bigquery__ and fabric__ versions, and generate_schema_name sends seeds to a raw schema.
The repository's own CI cannot run on a fork. .github/workflows/ci.yml starts dbt Cloud jobs on Snowflake, BigQuery and Postgres with a DBT_CLOUD_API_KEY secret, so a pull request from outside gets no build. The agent builds instead on DuckDB in its sandbox: it installs dbt-core and dbt-duckdb, writes a profiles.yml for the default profile with a local database file, runs dbt deps, dbt seed --full-refresh --vars '{"load_source_data": true}' and then dbt build on the pull request branch. The project's SQL is portable enough for that: stg_orders uses dbt.date_trunc, stg_supplies uses dbt_utils.generate_surrogate_key, and metricflow_time_spine uses dbt.date_spine, all of which dispatch for DuckDB. .sqlfluff names the dbt-cloud templater and the Snowflake dialect, so the agent lints with the dbt templater against its own profile and leaves that file unchanged.
Then it reads the diff against the grain the YAML declares. models/marts/orders.yml says one row per order and tests order_id with unique and not_null, customer_id with a relationships test to stg_customers, and two dbt_utils.expression_is_true checks, order_items_subtotal = subtotal and order_total = subtotal + tax_paid; it also carries a unit test, test_order_items_compute_to_bools_correctly, a semantic model and a saved query. orders.sql keeps its grain by aggregating order_items to one row per order_id before the left join, and order_items.sql does the same with supplies, summing supply_cost per product_id before joining. A change that joins a one-to-many table without that summary step, or adds a column to orders without a line in orders.yml, is what the review is for.
For each candidate finding the agent runs a query in DuckDB rather than arguing from the SQL: count(*) against count(distinct order_id) on the built orders table for grain, row counts before and after a new join for fan-out, and dbt test --select <model> for whether an existing test already pins the behavior. A new metric or semantic model is checked for a matching entity and agg_time_dimension. One GitHub review is posted per run with the dbt build summary quoted, blocking issues separated from suggestions, and every finding citing file and line. The prompt forbids pushing commits, so the agent pushes nothing.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Review this dbt pull request. Check it out, install dbt-core and dbt-duckdb, write a profiles.yml for the project's `default` profile on a local DuckDB file, run dbt deps, seed the sample data with load_source_data true, and run dbt build. Read the diff against the grain each model's YAML declares. For every candidate finding run a query: compare count(*) with count(distinct <key>) for grain, compare row counts before and after any new join for fan-out, and check whether a column added to a model has a test and description in its YAML. Post one GitHub review that quotes the dbt build result, separates blocking issues from suggestions, and cites file and line for every finding. Do not push commits.",
"resources": [
{
"url": "https://github.com/your-org/your-app/pull/42"
}
]
}- Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
- Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
- Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.
What comes back
What comes back is a review on the pull request. Its summary quotes the dbt build result from DuckDB, the number of models, tests and unit tests that ran and how they ended, then lists the findings with file and line in models/marts/ or models/staging/, marked as blocking or suggestion. Inline comments sit on the cited lines, and a grain or fan-out finding includes the query the agent ran and the two counts it compared.
The session's trajectory keeps the profiles.yml it wrote, the seed and build output, every query behind a finding, and the YAML files it opened while checking tests. When the review says a new column has no test, the trajectory shows the model's YAML as it read it.
Before merging, a reviewer runs the quoted queries on their own warehouse, since DuckDB proves the logic and not the warehouse-specific behavior, and dismisses suggestions that do not block. A blocking fan-out finding is closed by adding the aggregation step the other marts use, not by adding distinct.
Run it on every pull request opened
Create an automation in the Project with a github trigger on pull_request.opened and pull_request.synchronize, limited to the branches you merge to, and this prompt. The trigger adds the pull request after the prompt, so every pull request gets its own sandbox, its own DuckDB build and its own review, with no change to the dbt Cloud jobs that run for maintainers.
The review is the result: it appears on the pull request, and the run shows in the Project like one started from the console. The automation's max runs per hour, 1 to 30, caps a burst of small pull requests; a fire past it is skipped as rate_limited and that pull request waits for its next push, so set the cap to the busiest hour you expect.
The use case this job follows, with its automation and trigger: Review every pull request.
Why run it on Rush?
Reviewing a dbt change from the diff alone misses what only the data shows: a join that doubles rows, a mart whose declared grain no longer holds, a metric over a column nobody tested. Running the project needs a warehouse and credentials most reviewers do not have on hand, and the repository's CI runs only for people with the dbt Cloud key. On Rush the agent builds the whole project on DuckDB in its own sandbox and keeps every command and query, so the review rests on an executed build.
One sandbox per pull request keeps reviews parallel when several model changes land in a week, each with its own seeded database. The author keeps their branch, the reviewer gets findings with the queries behind them, and nothing touches the production warehouse.
Frequently asked questions
The project targets Snowflake, BigQuery and Postgres. Is a DuckDB build meaningful?
For grain, fan-out and test coverage, yes: those are properties of the joins and the YAML, not the warehouse. The macros in this project dispatch by adapter, so the build runs. Behavior that depends on a warehouse-specific function is named in the review as something to confirm there.
Does it need the dbt Cloud API key?
No. It never calls dbt Cloud. The CI jobs in .github/workflows/ci.yml stay as they are for maintainers, and the agent's build is separate and local to its sandbox.
What about the semantic layer YAML?
The agent parses it with the same dbt build, which fails on a malformed semantic model or metric. It checks that a new metric has an entity and a time dimension on its model, but it cannot query metrics without a Semantic Layer endpoint and says so if that is what the pull request changes.
Will it run the unit tests?
Yes. dbt build runs unit tests such as test_order_items_compute_to_bools_correctly in orders.yml before the model builds. A pull request that changes a model's logic without updating its unit test fails there, and the review quotes the failing case.
Can it lint with the project's SQLFluff config?
Mostly. .sqlfluff uses the dbt-cloud templater, which needs a dbt Cloud session, so the agent runs sqlfluff lint with the dbt templater pointed at its own profile and the same rules. Lint findings are posted as suggestions, not blockers, unless the prompt says otherwise.