dbt™ Test Maintainer
Write missing dbt™ tests for changed models, validate them with `dbt test`, and commit the result back to the PR branch automatically from GitHub Actions or a parent agent.
A specialist DinoAI agent that writes missing dbt™ tests for any model added or changed in a pull request, runs them to confirm they pass, and commits the result directly to the PR branch. Triggered automatically by a GitHub Actions workflow whenever a .sql file is touched — so test coverage gaps never reach main.
The agent can also be invoked as a sub-agent by the PR reviewer, allowing the reviewer to delegate test-writing as part of a broader code review rather than just flagging the gap in its verdict.
Before You Start
Paradime
Your Paradime API endpoint, API key, and API secret — generate these under Workspace Settings → API. Make sure to enable
DinoAI agent APIcapabilities. Requires Admin access.
GitHub
Write access to the repository you want to run the workflow on
Ability to add repository secrets and create GitHub Actions workflows
Recommended reading
Before proceeding, read the Programmable Agents section under Products → DinoAI:
Quick Start
YAML Configuration
Tools Reference
Agent-to-Agent Delegation — required reading if you plan to invoke this agent from the PR reviewer
Integrations
The following must already be connected in Paradime:
Slack — the agent posts a test summary to
#data-qualityviapost_slack_message
What You'll Build
By the end of this guide you'll have:
A
test-maintainerDinoAI agent YAML that reads changed models, infers appropriate tests, writes them to the schema YAML, runsdbt test, iterates until green, and commits the resultA Python driver script that collects changed
.sqlfiles from the PR and triggers one agent session per model in parallelA GitHub Actions workflow that runs automatically on every PR that adds or modifies a dbt™ model
What the Agent Does Per Model
Once triggered for a model, the agent works through five steps without stopping:
The agent always runs dbt test before reporting completion — it never commits tests it hasn't verified pass. If a test fails after being written, the agent diagnoses the failure, adjusts the test definition or SQL, and retries before giving up.
The absence of invoke_agent from the tool allowlist means this agent cannot delegate further. When used as a child of the PR reviewer, the delegation graph stays exactly two levels deep — the reviewer delegates, the test maintainer executes.
Tests the Agent Writes
The agent infers test candidates by reading the model SQL and upstream source definitions. It writes the following categories of tests:
unique + not_null
Any column that appears to be a primary key based on name (_id, _key, _sk) or usage
not_null
Columns used in JOIN conditions or GROUP BY clauses in downstream models
accepted_values
Columns with a small, finite set of values inferred from CASE WHEN or WHERE clauses
relationships
Foreign key columns that reference a known ref() or source() model
Custom generic tests
Where dbt™ built-in tests are insufficient and a simple SQL assertion would be more expressive
The agent never writes a test it cannot justify from the SQL. When a test candidate is ambiguous it adds a # TODO: confirm test logic with owner comment in the YAML rather than guessing.
Architecture Overview
How It Works
When a PR is opened or updated, GitHub Actions runs test_maintainer.py, which collects all changed .sql files from the diff and fires one test-maintainer agent session per model in parallel. Each session reads the model, infers and writes tests, runs dbt test, iterates until green, and commits to the PR branch. The driver script polls all sessions concurrently and posts a single completion comment to the PR once every session has finished, summarising what was written per model.
Create the Agent YAML
Create the following file in your repository at .dinoai/agents/test-maintainer.yml.
notify_parent_session is included in the allowlist so this agent can be invoked as a sub-agent by the PR reviewer. When triggered directly from the GitHub Actions workflow, the tool is present but never called — it only activates when a parent session ID is passed via invoke_agent.
Create the Driver Script
Create scripts/test_maintainer.py. This script runs inside GitHub Actions and is responsible for:
Collecting all changed
.sqlfiles from the PR diffFiring one
test-maintaineragent session per changed model in parallelPosting a "started" comment to the PR immediately
Polling all sessions concurrently until each completes
Posting a single completion comment to the PR with a per-model summary
Sessions run in parallel via ThreadPoolExecutor — if a PR changes three models, all three agent sessions start at the same time. Total wall-clock time is bounded by the slowest single model, not the sum of all three.
The script exits with code 1 if any session fails or times out, which marks the GitHub Actions job as failed. This makes test-writing failures visible in the PR checks panel alongside lint and CI results.
Add Your Paradime Credentials to GitHub Secrets
The driver script authenticates with Paradime using three values. Add the following as GitHub Actions secrets in your repository under Settings → Secrets and variables → Actions:
PARADIME_API_KEYPARADIME_API_SECRETPARADIME_API_ENDPOINT
GITHUB_TOKEN and GITHUB_REPOSITORY are provided automatically by GitHub Actions — you do not need to add them as secrets.
Create the GitHub Actions Workflow
Create .github/workflows/test-maintainer.yml. This triggers the driver script automatically whenever a PR adds or modifies a file inside models/.
The paths: filter restricts the workflow to .sql files inside models/ only. PRs that touch only YAML, Python, or documentation files will not trigger the agent.
contents: write is required so the agent can commit and push the updated schema YAML files back to the PR branch. Without it, the git push inside the agent session will fail with a permissions error.
What the PR Experience Looks Like
Once the workflow is set up, the experience for a PR author is:
They open a PR adding or modifying a dbt™ model
A comment appears on the PR immediately:
🧪 DinoAI test maintainer started — PR #42: feat/add-revenue-mart
Writing and running missing tests for 2 model(s):
fct_orders(models/marts/fct_orders.sql)stg_payments(models/staging/stg_payments.sql)
Changes will be committed to
feat/add-revenue-mart. Check#data-qualityfor live updates.A new commit appears on their branch:
test: add missing tests for fct_orders [DinoAI]A completion comment appears on the PR with a per-model breakdown:
🧪 DinoAI test maintainer — complete
✅fct_ordersTests added:
unique+not_nullonorder_id,not_nulloncustomer_id,accepted_valuesonstatus(values: pending, shipped, delivered, returned) All 4 tests passing. Committed to branch:feat/add-revenue-mart✅stg_paymentsTests added:
unique+not_nullonpayment_id,relationshipsonorder_id→stg_ordersAll 3 tests passing. 1 data quality warning onamount(severity: warn — negative values found, investigate before promoting to error)2/2 models had tests written and committed to
feat/add-revenue-mart.The same summary is posted to
#data-qualityon Slack.
Using as a Sub-Agent of the PR Reviewer
If you have the PR reviewer set up, you can add test-maintainer to its agents_squad so the reviewer delegates test-writing rather than just flagging the gap in its verdict.
Add the following to your pr-reviewer-e2e.yml:
The PR reviewer can then call invoke_agent("test-maintainer", ...) when it detects missing test coverage, and the test maintainer will call notify_parent_session with its findings once complete so the reviewer can include the result in its final verdict.
When used as a sub-agent, the test maintainer receives the branch name and model name via the invoke_agent message rather than from the GitHub Actions context. Make sure the PR reviewer includes both in its delegation message.
File Structure
Your repository should look like this after completing the setup:
Related Docs
Last updated
Was this helpful?