Model monitoring tells you a model's behaviour changed. It cannot tell you why, because it has never been told your warehouse exists. Ariadne answers the next question: which column, which file, which commit, how many hops away. It stands on the DataHub lineage graph and writes what it finds back into DataHub as an incident.
The metric said ship it. The graph said look again.
Benefits administration asked for a coverage flag so onboarding could skip a prompt for people who already had cover. One line of dbt. No health detail, just a flag. The model retrained and accuracy improved by three and a half points, so every dashboard, every model monitor and every approval gate read the change as an improvement.
What also went up was how much the model could tell about who is disabled, by 12 times the measured noise. Nothing drifted. Nothing degraded. There was no distribution shift to detect, because a column arrived. The only artefact in the building that could see both halves at once was the lineage graph.
Root cause is the wrong half to reach for first. By the time you run it, the model is already serving decisions off the thing nobody meant to add. The cheap moment is the pull request, when the change is still a diff.
It separates what is deployed from what is merely registered, because a change reaching forty archived models and no live one is a different change entirely.
See both agentspython tools/blast.py analytics_marts.dim_person \
--column public_coverage_flag --policy eu_ai_act
examine in scope because it carries personal_data
reaches 1 deployed model
workforce-classifier_3 mlflow_production, 5 hops
and 1 registered but not serving
workforce-classifier_1 mlflow_archived
It starts from the measurement rather than the diff, because plenty of columns get added that change nothing. Then it walks the graph back to where the column entered.
Note the last line. The column is called something else where it enters, so nobody searching the warehouse by name would ever have found it.
Watch it runpython tools/rootcause.py --model workforce-classifier --via mcp
accuracy went up, 0.6955 to 0.7310 (+0.0355)
so nothing that watches model quality had a reason to object
public_coverage_flag
enters at postgres:raw_person, 7 hops back
called pubcov where it enters, so a search
by name would not have found it
defined in models/marts/workforce_features.sql
cause: public_coverage_flag
A governance tool that prints to a terminal gets run once. Every finding is written back into DataHub as an incident on the table it concerns, so it sits next to that table in the catalog the data team already opens.
They come back on their own, too. A later lineage walk sees them as a health signal without being told they exist.
GET /dataset/.../Incidents
workforce_features 6 active
HIGH disability became easier to rebuild
from workforce-classifier
CRITICAL age reaches workforce-classifier_3
LOW marital_status_code reaches ...
income_features 12 active
HIGH race (Black alone) became easier ...
CRITICAL race_code reaches income-classifier_3
Which columns count as restricted is declared in a policy pack, not written into the checks. The same graph and the same tags reach different verdicts depending only on which statute is named, which is what makes this portable to a warehouse governed by a rule nobody here has read.
Title VII, the ADA and the ADEA. Watches columns tagged as protected attributes.
coverage flag: nothing to reportFair lending. Watches the protected bases, with prohibited and conditional tiers.
coverage flag: nothing to reportCanadian Human Rights Act section 3, for federally regulated banks, telecoms and transport. No age carve out, so it is stricter than ECOA on the same column.
coverage flag: nothing to reportRegulation (EU) 2024/1689, Annex III. Asks for examination of bias affecting fundamental rights, so it watches personal data too.
coverage flag: examine, Article 10(2)(f)Two of the three are correct to stay quiet: insurance coverage genuinely is not a protected characteristic in US employment law. The difference between silence and a finding is a line of YAML, not a code change.
Every claim Ariadne makes is a claim about the DataHub graph, read through DataHub's own agent surfaces and written back through DataHub's own APIs. Nothing in the lineage was hand emitted, because a graph you wrote yourself proves nothing.
Column level, back to the raw extract and forward to whatever is serving.
7 hops, 3 connectors mcp serverThe official server as a child process over stdio, tools discovered at the handshake.
6 tools discovered agent context kitBoth agents run on the kit, over either transport, and the two are checked against each other.
3 of 3 walks agree datahub skillsAn ML impact skill in the registry format, covering what the existing five do not.
4 failure modes encoded incidents apiFindings filed on the entity they concern, and read back through the API.
18 raised, two regimes everythingThe full table, including the Analytics Agent pattern and what each one returned.
8 surfacesEach one fails silently, which is why they were worth writing up rather than working around quietly. A loud failure teaches you something the first time. These teach you nothing until much later.
| what | why it matters | where |
|---|---|---|
The self-hosted MCP server stalls before answering initialize |
Its usage ping is sent before the handshake, so on a restricted host the client sees no error and no response at all | datahub#18684 |
| Incidents accept seven entity types, and no ML entity | The docs said "dataset, dashboard, chart, dataFlow, etc" and that "etc" is not open ended | datahub#18685 |
| An ML impact skill for the registry | The existing five do not cover whether a warehouse change reaches something that is serving | datahub-skills#66 |
| The Agent Context Kit cannot import its own registration module | It fails against the dependency version it pins, so the LangChain path breaks on a clean install | datahub#18686 |
Start with the demo. Five steps, and every figure in it was recorded.
Real data throughout: 378,817 US Census records for California 2018, pulled live through folktables. Two models registered in MLflow and promoted to Production. The failure mode is native to the dataset, not injected for a demonstration.