-
-
Six write-back kinds in one receipt — incidents, assertions, tags, notes, postmortem doc, structured properties. All GraphQL-verified.
-
Real output: incident-crew resolve verifies the fix, closes incidents, flips assertions to SUCCESS, and removes the tags.
-
Blast radius from DataHub's live lineage graph (get_lineage + paths-between) — no hard-coded URNs, incident badge on the broken table.
-
Four Claude agents that catch the silent data failures your catalog can't see — and write the whole investigation back into DataHub.
-
The full root-cause report saved as a DataHub knowledge document — the next engineer inherits the investigation, not the symptom.
-
The crew creates its own assertions and reports FAILURE — DataHub's Quality view now agrees with the data, not just the metadata.
-
The Scribe appends an incident note to the dataset docs: what broke, since when, the evidence, and the remediation.
-
After the run: an ACTIVE freshness incident on staging_trips, with the stale_data tag and 2 structured properties — raised by the crew.
Inspiration
Every data platform team knows this pattern: a pipeline stage stops writing real data, or a load "succeeds" with zero rows, and the catalog doesn't notice. Ingestion timestamps still update. Lineage still renders. Nobody gets paged. By the time a human notices, dashboards have been wrong for days. Metadata says fine — the data is lying.
We wanted to build an agent crew that does the runbook a human on-call engineer actually follows: confirm the failure by looking at the real data, figure out what's downstream, find the owner, write up what happened, and leave a trail so the next person doesn't re-derive it from scratch. And we wanted DataHub to be the substrate the crew writes to, not just a read-only source it summarizes.
What it does
Data Incident Crew is four specialized Claude agents — Detector, Blast-Radius, Root-Cause, and Scribe — that run in sequence against a real nyc-taxi SQLite pipeline (raw_trips → staging_trips → mart_daily_summary, ingested into DataHub with lineage):
- Detector queries the actual pipeline data (not catalog metadata) and finds a planted, catalog-invisible defect:
staging_tripssilently stops 9 days before its upstream table, plus a disclosed empty-load day in the mart. - Blast-Radius walks DataHub's live lineage graph (never a hard-coded URN list) to compute exactly what's downstream and who owns it.
- Root-Cause builds a grounded RCA from the query history and the same evidence.
- Scribe writes every finding back into DataHub: an incident, a tag, a description note, a postmortem knowledge document, structured properties (
io.incident_crew.severity/io.incident_crew.detected_at), and an assertion + result.
And the loop closes. Once an engineer fixes the underlying job, a deterministic incident-crew resolve command verifies the fix against the data, resolves the open incidents, flips the crew's assertions to SUCCESS, and removes the stale_data tags — live-verified end-to-end: the repair backfilled 39,640 staging rows, then resolve closed both incidents and cleaned both tags. The catalog round-trips instead of accumulating stale incident debris: detect → write back → fix → verify → resolve.
A --control mode runs the same crew against a clean instance and reports healthy with SUCCESS assertions — proving nothing is hard-coded to always find a problem.
How we built it
- Claude Agent SDK (Python) orchestrates four
ClaudeSDKClientsessions, one per agent role, each scoped to its own least-privilege tool allowlist. Sequencing, typed JSON contract validation (DetectorFindings → ImpactMap → RcaReport → WriteBackReceipt), and retry-once-on-failure are handled by our own Python orchestrator — not left to an LLM deciding when to hand off — so failures are deterministic and every stage failure leaves a forensic trail (runs/<ts>/events.jsonl). - DataHub MCP server (
mcp-server-datahub==0.6.0, stdio, mutations enabled):search,get_entities,list_schema_fields,get_lineage,get_lineage_paths_between,get_dataset_queriesfor reads, andadd_tags,update_description,add_structured_properties,save_documentfor catalog-enrichment write-back. - DataHub Python SDK / GraphQL, dual write path: the MCP server has no incident or assertion tools, so we built an in-process SDK MCP server wrapping
DataHubGraph.execute_graphql(raiseIncident,updateIncidentStatus) andupsert_custom_assertionplus a hand-rolledreportAssertionResultmutation — both paths behind one module boundary. - Vendored DataHub Skills: the official
datahub-lineageanddatahub-qualityskill instructions are injected verbatim into the Blast-Radius and Detector agent prompts. - Official
nyc-taxidataset (datahub-project/static-assets), pinned to a specific upstream commit, as both the incident instance and a clean control instance — no synthetic data for the core scenario. - A deterministic detection rule implemented in code (not LLM judgment) so the pass/fail boundary is unit-testable; 181 unit + 8 live integration tests;
ruffclean; STRIDE security audit before release.
Challenges we ran into
- GraphQL schema drift between SDK 1.6.x and the 1.5.x quickstart server, found by live-probing rather than trusting docs:
updateIncidentStatustakesurnas a top-level argument (not nested ininput), and the SDK'sreportAssertionResultdocument references a type the server rejects even when null. We hand-rolled the exact mutation shapes the live server accepts. - A search-index race: the lineage-linking step discovers entities via search, which silently found zero entities right after ingestion. Fixed with an index-catch-up poll.
- The first true end-to-end run found a critical bug the deterministic layer couldn't catch: the DataHub MCP server had never actually connected — its PyPI pin was actually the MCP protocol version (3.4.4), not a real release, and the CLI's MCP spawner doesn't merge our env (so bare
uvxhad no PATH). Fixing that surfaced four more real bugs, each found only by running the crew: an unscoped--instancewrite guard, a never-ingested control instance, an agent-controllablefield_paththat silently broke assertion reporting, and agent-supplied assertion timestamps that corrupted DataHub's health view in both time directions (we removed the parameter from the tool surface entirely). All were root-caused against the live server and fixed with regression tests.
Accomplishments that we're proud of
- A real write-back trail landing in DataHub across two distinct write paths (MCP mutations + hand-corrected GraphQL), kept behind one clean module boundary.
- The full incident lifecycle, live-verified: detect → write back → fix → verify → resolve — the catalog round-trips instead of accumulating stale incident debris.
- Catching and fixing live GraphQL schema mismatches that no amount of reading SDK documentation would have surfaced — only probing the running server did.
- A deterministic, unit-tested detection rule underneath a non-deterministic LLM crew, so the demo's pass/fail boundary never depends on how an agent phrases its findings.
- A STRIDE security audit with zero CRITICAL findings, done before public release rather than after; every hazardous agent-controllable tool parameter removed from the tool surface.
What we learned
- SDK documentation for a fast-moving GraphQL API is not a substitute for probing the actual running server version — the mutations that needed hand-rolling were exactly the ones the docs described incorrectly.
- Typed, validated Python contracts between agents turn "the demo might say something different every run" into "the demo's structure is guaranteed every run, only the phrasing varies."
- Least-privilege tool scoping per agent (no agent has both SQL read access and any write tool) is cheap to design up front and meaningfully shrinks the blast radius of a prompt-injected stage.
- The bugs that matter most are only findable by running the full agent system against the live server — unit tests and even live tests of the deterministic layer all stayed green while the flagship path was broken.
What's next for Data Incident Crew
- Slack/PagerDuty hand-off: notify a channel or on-call rotation the moment an incident is raised, instead of requiring someone to open the catalog.
- Real warehouse adapters: swap the SQLite read-only adapter for a thin interface over Snowflake/BigQuery/Postgres so the same crew runs against production warehouses.
- Scheduled sentinel mode: run the crew on a cron so silent failures get caught within an SLA window rather than whenever someone thinks to run it.
Log in or sign up for Devpost to join the conversation.