For the complete documentation index, see llms.txt. This page is also available as Markdown.

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.

compass

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 API capabilities. 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-quality via post_slack_message

What You'll Build

By the end of this guide you'll have:

  • A test-maintainer DinoAI agent YAML that reads changed models, infers appropriate tests, writes them to the schema YAML, runs dbt test, iterates until green, and commits the result

  • A Python driver script that collects changed .sql files from the PR and triggers one agent session per model in parallel

  • A 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:

Test Type
When Written

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.

1

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.

2

Create the Driver Script

Create scripts/test_maintainer.py. This script runs inside GitHub Actions and is responsible for:

  • Collecting all changed .sql files from the PR diff

  • Firing one test-maintainer agent session per changed model in parallel

  • Posting 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.

3

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_KEY

  • PARADIME_API_SECRET

  • PARADIME_API_ENDPOINT

GITHUB_TOKEN and GITHUB_REPOSITORY are provided automatically by GitHub Actions — you do not need to add them as secrets.

4

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:

  1. They open a PR adding or modifying a dbt™ model

  2. 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-quality for live updates.

  3. A new commit appears on their branch: test: add missing tests for fct_orders [DinoAI]

  4. A completion comment appears on the PR with a per-model breakdown:

    🧪 DinoAI test maintainer — complete

    fct_orders

    Tests added: unique + not_null on order_id, not_null on customer_id, accepted_values on status (values: pending, shipped, delivered, returned) All 4 tests passing. Committed to branch: feat/add-revenue-mart

    stg_payments

    Tests added: unique + not_null on payment_id, relationships on order_idstg_orders All 3 tests passing. 1 data quality warning on amount (severity: warn — negative values found, investigate before promoting to error)

    2/2 models had tests written and committed to feat/add-revenue-mart.

  5. The same summary is posted to #data-quality on 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:

Last updated

Was this helpful?