What the agent does
The agent clones the project into its own cloud sandbox and reads dbt_project.yml first. In a project like dbt-labs/jaffle-shop it is jaffle_shop at version 3.0.0 with require-dbt-version: ">=2.0.0", a dbt-cloud block with the project id, staging models materialized as views and marts as tables, and seeds under jaffle-data enabled only when the load_source_data var is true. packages.yml pins dbt-labs/dbt_utils 1.4.1 and dbt-audit-helper from git. The mart YAML files under models/marts/ already carry a lot: semantic_model, metrics, saved_queries, data_tests with dbt_utils.expression_is_true, and unit_tests such as test_order_items_compute_to_bools_correctly in orders.yml. None of them sets config.contract.enforced, and no column has a data_type.
A contract needs every column the model produces, named and typed, so the agent reads the SQL rather than the YAML to find them. models/marts/orders.sql selects orders.* from stg_orders and adds order_cost, order_items_subtotal, count_food_items, count_drink_items, count_order_items, is_food_order, is_drink_order and customer_order_number; stg_orders.sql in turn renames id to order_id, store_id to location_id, customer to customer_id, keeps subtotal_cents, tax_paid_cents and order_total_cents, derives subtotal, tax_paid and order_total through the cents_to_dollars macro, and truncates ordered_at to the day. orders.yml documents nine of those columns. The agent lists all of them, with the types the macro and the staging casts produce, and does the same for customers, order_items, products, locations and supplies.
Types are where contracts bite, because macros/cents_to_dollars.sql dispatches per adapter: numeric(16, 2) by default and on Postgres, a rounded numeric on BigQuery, numeric(16,2) on Fabric, and .sqlfluff says the project's dialect is Snowflake. The agent writes the data_type for each column in the warehouse's own spelling, taken from the macro branch for that adapter rather than from a generic list, and keeps the existing description, data_tests, entity and dimension keys on each column so the semantic layer is untouched. It adds config: contract: enforced: true to each mart and leaves the staging views alone, since contracts on the layer downstream dashboards read is where they pay off.
To check its work without warehouse credentials, it runs dbt parse, which validates the YAML and the contract shape, and then a local build against DuckDB with a profiles.yml it writes in the sandbox only, seeding with dbt seed --vars '{"load_source_data": true}' and running dbt build --select marts, which fails when a column in the SQL is missing from the YAML or the other way round. It runs the repository's pre-commit hooks (check-yaml, end-of-file-fixer, ruff) on the changed files. The sandbox profiles.yml and target/ are not committed.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Add enforced model contracts to the marts in this dbt project. Read dbt_project.yml, packages.yml, .sqlfluff for the dialect, macros/cents_to_dollars.sql for the per-adapter cast, and every model under models/marts with its SQL and YAML. For each mart, list every column the SQL produces (follow select * back through the staging model it reads) in the model YAML with a data_type in the warehouse's spelling, keep every existing description, data_tests, entity, dimension, metrics, saved_queries and unit_tests key, and add config.contract.enforced: true. Leave staging models as they are. Run dbt parse, then write a sandbox-only profiles.yml for DuckDB, run dbt seed --vars '{\"load_source_data\": true}' and dbt build --select marts, and fix any column the build reports as missing or extra. Run pre-commit on the changed files. Commit no profiles.yml or target/. Open a pull request that lists the columns added per model and says that the data_type check against the production warehouse happens in the dbt Cloud CI job on this pull request.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- 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 pull request that changes only the YAML files under models/marts/: each mart gains a config block with contract.enforced: true and a complete column list with data_type, with the semantic model, metrics, tests and unit tests left where they were. The description holds one table per model naming the columns that were added, the type each got and the SQL line it came from.
The pull request's own check is .github/workflows/ci.yml, which starts a dbt Cloud job for Snowflake, BigQuery and Postgres through .github/workflows/scripts/dbt_cloud_run_job.py with the branch's schema override. That job is where the contract meets the real warehouse: a data_type spelled for the wrong adapter fails there with the column named. The sandbox build on DuckDB catches the missing and extra columns before that, which is most of the work.
A reviewer reads the column tables against the SQL once, then watches the three dbt Cloud jobs. If one adapter rejects a type, a follow-up on the same session fixes that spelling without redoing the rest, and the transcript shows the dbt parse and dbt build runs that passed in the sandbox.
Run it on every Linear issue labelled agent
In the console, add an automation to the Project with this prompt and a linear trigger on linear.issue.labeled, filtered to the agent label. When an analytics engineer labels an issue such as "enforce contracts on the marts before the dashboard migration", the trigger puts the issue after the prompt and starts a run in the project's repository, and the pull request links back to the issue. Only changes made by a person in your Rush org fire it.
The same request can be sent from a script with POST /o/{org}/sessions, with the repository in resources and the Linear issue beside it, read through your Linear connection. A request_id made from the issue id keeps a retried script from starting a second run.
The use case this job follows, with its automation and trigger: Turn a Linear label into a PR.
Why run it on Rush?
Writing contracts by hand means tracing every select * back to its source and typing out dozens of columns per mart, and the first dbt build usually says a column is missing. The agent does the tracing from the SQL, builds the marts on DuckDB in its own sandbox before the pull request exists, and the transcript shows the parse and the build that passed, so the reviewer reads tables rather than retyping them.
The warehouse-specific part, the data_type spelling, is left to the project's own dbt Cloud CI on the pull request, and the description says so rather than claiming the sandbox proved it. Several marts, or several projects, can be done at once, each in its own sandbox, and a type the CI rejects is fixed with a follow-up on the same run.
Frequently asked questions
Will it touch the semantic layer or the unit tests?
No. The column entries keep their entity, dimension and granularity keys, and the metrics, saved_queries and unit_tests blocks are left byte for byte. The agent adds data_type to existing columns, adds the columns that were undocumented, and adds the config block. dbt parse in the sandbox confirms the semantic manifest still builds.
Why DuckDB in the sandbox rather than my warehouse?
The sandbox has no warehouse credentials, and the project's own CI already runs the dbt Cloud jobs on every pull request. DuckDB with the seeds is enough to catch a column missing from the YAML or present in the YAML but not the SQL, which is where most contract work goes wrong. The profiles.yml it writes stays in the sandbox.
Does it add contracts to staging models too?
Not in this run. Staging models are views that rename source columns, and the prompt scopes contracts to the marts that downstream tools read. A follow-up on the same session can extend them to models/staging/, where the column lists are shorter and the types come straight from __sources.yml.
What if a data_type is wrong for Snowflake?
The dbt Cloud Snowflake job on the pull request fails and names the column and the type it found. The agent chose the spelling from the adapter branch in cents_to_dollars and the staging casts, but a type it could only infer is listed in the pull request as such. Send the job's message as a follow-up and the same session corrects it.