Data checks¶
A pipeline that runs at 5 am and writes the report someone opens at 8 should not be trusted
blindly. A check step looks at the data before it is delivered: fixed expectations catch the
obvious, an optional agent review catches the odd, and a failure stops the run before yesterday's
good file is overwritten.
name: daily-sales
llm:
default: { provider: anthropic, model: claude-opus-5-5 }
steps:
- id: sales
use: postgres.read
with: { dsn: "${DATABASE_URL}", query: "select * from sales where day = current_date - 1" }
- id: verify
check: sales
expect:
rows: { min: 1000, max: 50000 }
not_null: [order_id, total]
unique: order_id
accepted: { region: [north, south, east, west] }
range: { total: { min: 0 } }
fresh: { field: day, max_age: 2d }
vs_last_run: { rows: 30%, sum: { total: 40% } }
review: >
Daily sales report for the finance team. Flag anything that looks wrong:
totals out of the usual range, a region with no orders, odd categories.
on_fail: stop # or warn
- id: report
use: excel.write
input: verify
with:
path: "smb://fileserver/finance/sales_{date}.xlsx"
latest: "smb://fileserver/finance/sales.xlsx"
keep: 30
notify:
channels:
finance: { type: teams, webhook: "${TEAMS_WEBHOOK}" }
rules:
- on: check.failed
to: [finance]
The check passes the records through unchanged, so the writer reads from it (input: verify).
Fixed expectations¶
Deterministic and free. Each failure explains itself, with the numbers.
| Expectation | Example | Fails when |
|---|---|---|
rows |
{ min: 1000, max: 50000 } |
the row count is outside the range |
not_null |
[order_id, total] |
a column has empty values |
unique |
order_id or [a, b] |
a column has duplicates |
accepted |
{ region: [north, south] } |
a column has a value outside the list |
range |
{ total: { min: 0, max: 10000 } } |
a number is outside the range |
fresh |
{ field: day, max_age: 1d } |
the newest date is older than max_age (30m, 6h, 1d, 2w) |
vs_last_run |
{ rows: 30%, sum: { total: 40% } } |
the row count, or a column's sum, changed more than that since the last run that passed |
vs_last_run compares with the profile of the last run whose check passed, kept in the run store.
On the first run there is nothing to compare with, so it passes.
Agent review¶
With review:, an agent step looks at the data too. It does not read every row: it gets a profile
(row count; nulls, distinct values, min, max, mean and sum or the most common values for each
column; 15 sample rows), the profile of the last delivered run, and the fixed checks that failed.
That keeps the cost to one small call per run.
The agent answers with a verdict and findings in plain language, for example "The north region
has no orders today; on the last run it had 312." When it passes, its findings are kept as
notes. Its tokens count in the run's budget and in the step's spend.
When a check fails¶
on_fail: stop(default): the step fails with the findings, so nothing after it runs and the last good output stays in place.on_fail: warn: the data goes on and the findings are reported.
Both emit check.failed with findings (a list of {check, detail}), so any notification rule
can route them; a passing check emits check.passed. Both show on the dashboard's timeline.