Data Quality Checks With an AI Agent
Bad data rarely announces itself. A tracking script gets renamed and a column starts filling with nulls. A migration leaves ten thousand order rows pointing at customers that no longer exist. An upstream vendor changes a date format and your revenue dashboard quietly starts double counting. Nobody notices for three weeks, and then someone notices in a board meeting.
The frustrating part is that every one of these failures is detectable with a simple query. SELECT COUNT(*) FROM orders WHERE customer_id IS NULL would have caught the null spike on day one. The problem is not that the checks are hard to write. The problem is that nobody runs them every day, remembers what last week's numbers looked like, and reads the results carefully enough to spot the change.
That is a job shaped exactly like an autonomous agent: repetitive, query-driven, comparison-heavy, and valuable only if it happens on schedule without a human remembering to do it. This article walks through how to build one on Skopx, what checks it should run, how it remembers baselines between runs, and what an honest report looks like. It also covers where an agent is the wrong tool, because some data quality problems need pipelines and constraints, not a watcher.
Why data quality breaks silently
Most teams discover data problems downstream of where they happen. The sequence is familiar:
- Something changes at the source: a form field becomes optional, an API version bumps, an import job half-fails.
- The database accepts the degraded data, because databases accept almost anything you have not explicitly constrained.
- Dashboards, reports, and AI features built on that data drift away from reality.
- A human eventually notices a number that looks wrong, and someone spends a day tracing it back.
The gap between step 2 and step 4 is where the damage accumulates. Backfilling three weeks of malformed rows is dramatically more painful than fixing one day of them, and some damage, like emails sent to the wrong segment, cannot be backfilled at all.
Traditional answers to this gap exist. Database constraints prevent some bad writes. Dedicated data observability platforms monitor warehouses at scale. Both are good, and neither is what most small and mid-sized teams actually have. What most teams have is a Postgres or MongoDB database, a handful of SQL queries someone wrote once during an incident, and a shared intention to "check the data more often" that nobody's calendar enforces.
An agent closes that gap cheaply: it runs the queries on a schedule, compares results against what it saw last time, and writes up only what changed.
What a data quality agent actually is
On Skopx, an agent is not a script and not a flowchart. You describe it in chat at Create Agent and the platform assembles it from parts you can inspect and edit:
- Instructions in plain language: which tables to check, what counts as suspicious, how to report. These are editable and versioned, so tightening a threshold later is a text edit, not a rebuild.
- A trigger: for data quality, almost always a schedule, like "Every day at 06:00 UTC" so results are waiting before the workday starts. The tradeoffs between schedules, webhooks, and manual runs are covered in our guide to scheduled agents.
- Grants: which tools the agent may use, and under what supervision tier. A data quality agent needs your connected data source and, optionally, a Slack or email toolkit for delivery.
- Budgets: caps on tokens per run, tokens per day, steps, and minutes. If a run blows past its budget, it stops. Three budget failures auto-pause the agent entirely.
- Success criteria: the standard each run report is evaluated against, for example "every configured check was executed and every anomaly names the query that found it."
- Memory that persists between runs, which is what turns a query runner into a monitor. More on that below.
Critically, connected data sources on Skopx are queried read-only with bound parameters. The agent can run SELECT statements and aggregations against Postgres, MongoDB, and other connected sources; it cannot UPDATE, DELETE, or otherwise mutate your data. For a data quality watcher, that constraint is not a limitation, it is the entire safety model. The agent observes and reports. Fixing the data remains a human decision.
The checks worth running
You do not need hundreds of checks. In practice, a small set of check families catches the majority of real-world data rot. Here is the core set, what each one detects, and the shape of the query behind it:
| Check family | What it catches | Example query shape |
|---|---|---|
| Null and empty rates | A field that silently stopped being populated | COUNT(*) FILTER (WHERE email IS NULL OR email = '') / COUNT(*) per table, compared to baseline |
| Volume drift | Broken ingestion, duplicate imports, a stalled pipeline | Daily row counts per table vs. the trailing average |
| Orphaned rows | Deletes or migrations that broke referential integrity | LEFT JOIN child to parent, count where parent key is null |
| Freshness | A table that stopped receiving writes | MAX(updated_at) per table vs. expected update cadence |
| Duplicates | Retry bugs, double webhooks, bad merge logic | GROUP BY natural_key HAVING COUNT(*) > 1 |
| Domain violations | Values outside the plausible range | Negative prices, future birthdates, statuses outside the known enum |
| Distribution drift | Subtler shifts: a segment vanishing, a default flooding in | Category share per column vs. last run's share |
Two notes on this list, in the interest of honesty.
First, threshold choice matters more than query sophistication. A null-rate check with a naive "alert if above 5%" rule will either fire constantly on a column that has always been 12% null, or stay silent while a column climbs from 0.1% to 4.9%. This is why the agent's memory and delta comparison matter: the useful signal is almost always "different from before," not "above an absolute number."
Second, distribution drift is the hardest family to get right and the noisiest at first. Start with the mechanical checks (nulls, volume, orphans, freshness) and add drift checks only after two or three weeks of clean baselines. An agent that cries wolf gets muted, and a muted monitor is worse than no monitor because it provides false comfort.
Building the agent: a concrete walkthrough
Here is what setting this up actually looks like, framed as an example rather than a transcript of a real customer.
Suppose you run a small ecommerce SaaS with a Postgres database: customers, orders, order_items, subscriptions, events. In the Create Agent chat you might write:
"Every weekday at 06:00 UTC, check the production Postgres source for data quality problems. For customers, orders, order_items, and subscriptions: measure null rates on the key columns, compare daily row counts against the trailing seven runs, check that every order has an existing customer and every order_item has an existing order, and confirm each table received writes in the last 24 hours. Flag anything that moved meaningfully from baseline. For every finding, include the exact query you ran and the numbers it returned. If everything is clean, say so in three lines. Post the report summary to #data-alerts in Slack only when there is at least one finding."
The chat assembles this into an agent whose parts you then review on the agent screen:
- Trigger: schedule, weekdays at 06:00 UTC.
- Grants: the Postgres data source (reads flow without approval; there is nothing to approve on a read-only source), plus Slack. Slack posting is write-shaped, so you choose its tier: let it run automatically, or set it to "asks first every time" while you are still tuning, so every alert parks as a pending approval showing the exact message before anything reaches the channel. The supervision tiers are worth understanding in depth; our article on agents with human approval covers how the parked-call model works.
- Budgets: this workload is predictable, so budgets can be tight. A daily check across four tables is a bounded number of queries. Set max steps to comfortably cover the query count plus report writing, and a per-run token cap sized to a normal run. If the agent ever loops or a query result balloons unexpectedly, the budget stops the run rather than letting it spiral.
- Success criteria: "All configured tables were checked. Every anomaly names the query, the current value, and the baseline value. No finding is reported without numbers."
- Model: your choice per agent, among Claude, GPT, Gemini, Kimi, and others. Bring your own key with zero markup, or use the $16/seat Team plan with included tokens.
Because the instructions are plain language and versioned, tuning is cheap. When the volume-drift check turns out to be too twitchy on Mondays (weekend row counts are legitimately lower), you edit one sentence: "compare weekday counts against trailing weekdays and weekend counts against trailing weekends." The previous instruction version stays in history.
Memory: the difference between a query runner and a monitor
A stateless check can only compare against fixed thresholds, and fixed thresholds are where data quality monitoring goes to die. Skopx agents carry memory that persists between runs, and for this use case memory does three jobs:
Baselines. After the first run, the agent has recorded the null rate of every checked column, the row count trajectory of every table, and the freshness timestamp of each. From the second run onward, every number is a comparison: "null rate on customers.phone is 3.1%, baseline 2.9%, within normal variance" versus "null rate on orders.customer_id is 4.4%, baseline 0.0%, new since yesterday." The second one is a finding. The first is noise the report can compress to a single line.
Cursors. For append-heavy tables like events, re-scanning everything daily is wasteful. The agent can record a high-water mark and check only rows added since the last run, which also makes second and subsequent runs typically cheaper than the first.
Known issues. If you have acknowledged that subscriptions.cancelled_reason is 40% null and always will be, that fact belongs in memory, so the agent stops re-reporting it. Without this, every run rediscovers the same accepted flaws and buries new problems under old ones.
This is the general pattern behind all delta-style monitoring agents, and it is explained more fully in AI agent memory explained. The practical consequence for data quality: expect the first run to be long, verbose, and mostly baseline-building. The valuable output starts with run two.
Reports that name the exact query
The angle of this whole setup, and the thing worth being strict about in your instructions, is that a data quality finding without the query behind it is barely a finding. "Orders table looks off" forces a human to redo the investigation from scratch. Compare:
Orphaned rows in
orders. Query:SELECT COUNT(*) FROM orders o LEFT JOIN customers c ON o.customer_id = c.id WHERE c.id IS NULL AND o.created_at >= '2026-08-06'Result: 214 rows, all created after 2026-08-06 22:10 UTC. Baseline: 0 in all prior runs. The timing coincides with the window in whichcustomersvolume dropped by 180 rows, which suggests a delete without cascading cleanup rather than an ingestion bug.
A report entry like this is verifiable in thirty seconds: paste the query, confirm the number, start fixing. It is also honest in a way that matters for trust in the agent itself. If the agent must show its query and its numbers, it cannot hand-wave, and you can audit any conclusion it draws.
Every Skopx run ends in a markdown report rendered as a document, sitting on top of a step timeline where each query the agent executed is visible with its raw result expandable. So even if the agent's prose summary misses something, the underlying evidence is in the run history, which is append-only. What makes run reports readable, and how to shape them with success criteria, is its own topic; see AI agent reports for the deeper treatment.
A useful convention for the report structure:
- Verdict line: clean, or N findings.
- Findings, ordered by severity, each with query, current value, baseline, and a plain-language hypothesis clearly labeled as a hypothesis.
- Deltas within normal range, compressed to one line per table.
- Known accepted issues, one line, so nobody wonders whether the agent forgot them.
Guardrails for an agent that touches your database
Even a read-only agent deserves explicit limits. The relevant Skopx mechanics:
Read-only by construction. Connected data sources accept SQL reads and aggregations with bound parameters. There is no grant tier that lets an agent mutate a connected database, so "the agent fixed the data and made it worse" is not a failure mode available here.
Grants are per toolkit. The agent gets the data source and the delivery channel, nothing else. It cannot decide mid-run that it also wants to browse your CRM.
Write-shaped actions can require approval. Slack posts, emails, or ticket creation can be set to "asks first every time" or "agent decides when to ask," and there is a drafts-only mode. A parked approval shows the exact call and arguments; approving executes exactly that call once, rejecting executes nothing, and stale approvals can expire.
Budgets are enforced in the loop, not reviewed after. Token, step, and minute caps stop a runaway mid-flight. Three budget failures auto-pause the agent, which is the platform telling you the agent's scope and its budget disagree and a human should look.
Stop and pause always work. You can stop any run mid-flight, and pausing the agent is a kill switch for queued runs.
One honest caveat: query cost is on you. An agent running heavy aggregations against a production primary at 06:00 UTC is probably fine; the same queries against an undersized instance during peak load might not be. Point the agent at a replica if you have one, keep checks on hot tables narrow (this is what cursors are for), and treat the minute cap as a circuit breaker for queries that run long.
When an agent is the wrong tool
Candor section. An agent-based data quality watcher is a detection layer, and detection is not always what you need.
Prevention beats detection where prevention is available. If orders must always have a customer, a foreign key constraint enforces that at write time, forever, for free. Use constraints, NOT NULL, and enum types for every invariant your schema can express. Point the agent at what constraints cannot express: rates, trends, distributions, cross-system consistency.
Sub-hourly latency needs pipeline tooling. A scheduled agent tells you within a day. If malformed data starts costing you money within minutes, you need validation inside the pipeline itself, not a daily watcher. The agent still earns its keep as a backstop, because pipeline checks only catch what their authors anticipated.
Massive warehouse estates deserve dedicated observability platforms. Hundreds of tables across multiple warehouses with lineage tracking is a different product category. The agent pattern shines for the common case: one or a few databases, a few dozen tables that matter, and no dedicated data engineering team to babysit them.
The agent hypothesizes; it does not diagnose. It will tell you 214 orders lost their customers and when, and it may correlate that with another signal. Confirming root cause is human work, and the report should be written to make that work fast, not to pretend it is finished.
If this general boundary interests you, when not to use AI agents treats it beyond just data quality.
What a realistic first two weeks look like
Framed explicitly as a hypothetical, here is the typical arc:
Days 1 to 2. First runs are baseline-building: verbose reports, a couple of false positives where the agent flags long-standing quirks as anomalies. You mark those as accepted issues, and they move into memory.
Days 3 to 7. Reports shrink to a few lines on clean days. You tune one or two thresholds by editing instructions, for example the weekday-versus-weekend volume comparison. Instruction versions record each change.
Week 2. The agent catches its first real issue, or it does not, and both outcomes are informative. A clean two weeks with verifiable queries behind every "clean" is genuine evidence about your pipeline, which is more than "nobody has complained" ever was.
Ongoing. Marginal cost approaches zero human minutes on clean days. The run history becomes a longitudinal record of your data's health: when someone asks "how long has this column been broken," the answer is in the timeline instead of in nobody's memory.
FAQ
Can the agent fix the bad data it finds?
No, and this is deliberate. Connected data sources on Skopx are read-only with bound parameters, so the agent can measure and report but never mutate your database. Automated "fixes" to production data are how one incident becomes two. The agent's job is to make the human fix fast: every finding names the exact query and the affected row counts, so remediation starts from evidence rather than from a re-investigation.
How is this different from dbt tests or database constraints?
Complementary, not competing. Constraints prevent violations your schema can express and should be your first line. dbt tests validate transformations inside a pipeline you already operate. The agent covers what both miss: trend-based checks (a null rate drifting up, a distribution shifting), tables outside any dbt project, comparisons that need memory of previous runs, and the synthesis step where results become a readable report with hypotheses. Teams with mature dbt setups still use a watcher for the "different from last week" class of problems.
Will it flood us with false positives?
The first week, somewhat, and you should expect that. Every monitoring system needs a calibration period. The mitigations are structural: delta-versus-baseline comparison instead of absolute thresholds, an accepted-issues list in agent memory so known quirks are reported once and then suppressed, and instructions you can edit in one sentence when a check proves too twitchy. If alerts route to Slack, keeping that grant on "asks first every time" during calibration means nothing noisy reaches the channel without you seeing it first.
What does a run cost?
We will not quote per-run figures, because cost depends on the model you choose, the number of tables, and result sizes. The structural answer: this is one of the cheaper agent workloads because it is bounded (a fixed set of queries, a short report) and because memory cursors mean later runs scan less than the first. Token and step budgets cap each run hard, you can see the exact token count on every run in the timeline, and you bring your own key with zero markup or use the Team plan's included tokens.
Does this work with MongoDB, or only SQL databases?
Both. Connected data sources cover Postgres, MongoDB, and other databases, queried with SQL or aggregations, always read-only. The check families translate directly: null-rate checks become missing-field checks, orphan checks become dangling-reference lookups, and volume, freshness, and duplicate checks are the same idea in aggregation-pipeline form. The agent adapts the query shape to the source; the instructions you write stay in plain language either way.
Can one agent watch multiple databases?
Yes, grants are per toolkit, so a single agent can hold grants for several connected sources and check cross-system consistency, for example that Stripe-derived subscription counts in one database agree with the application database. That said, one focused agent per domain often beats one sprawling agent: smaller instruction sets are easier to tune, budgets are easier to size, and a pause affects only one area. Start with one database, and split when the report gets long.
Skopx Team
The Skopx engineering and product team