-
-
Full architecture: dbt/DuckDB builds SCD2 history, DataHub ingests lineage, MCP grounds the agent that drives Pingpin's real A1-A5 pipeline.
-
MCP handshake: agent connects to DataHub via mcp-server-datahub and confirms description_history's real schema before querying anything.
-
Real staleness report: SQL calculates exact idle days per SKU (S003-S006: 12 days, S001: 1 day) — no hardcoded thresholds shown to the user.
-
DataHub UI: description_history dataset page showing real ingested schema (sku_sk, description_text, dbt_valid_from, dbt_valid_to).
-
DataHub lineage graph: raw_description_updates -> description_history, generated automatically by dbt + datahub ingest, not MCP.
Pingpin Context
Inspiration
I built Pingpin, a multi-agent system that auto-generates Etsy listings, and put it into real use on an actual shop. That's when I ran into a gap Pingpin never solved: once a listing goes live, nothing tracks whether its description has actually been kept current. New sellers especially live and die by their title/description keywords — visibility comes from testing and retesting them, and that's manageable for one listing. But once you're running ten, or refreshing everything for a promotion season, tracking what changed and when turns into a spreadsheet nobody wants to maintain.
That's a state-management problem underneath a content problem. And solving it properly meant an agent that could actually understand the data it was querying — not guess at column meanings from a table name. That's what pointed me toward DataHub.
What it does
Pingpin Context is an inspection agent that:
- Connects to DataHub through the MCP Server and confirms the real schema of a live dbt project before querying anything.
- Queries a DuckDB-backed SCD Type 2 table for listings whose descriptions have gone stale (haven't changed in N days).
- Builds a layered report — separating facts (measured, non-negotiable) from inference (a rule-derived recommendation) — and hands it to a human via a LangGraph
interrupt(). - Once a human picks which SKUs to rewrite and supplies keywords/tone, it drives the request through Pingpin's real, already-running A1–A5 pipeline — not a shortcut, the same path a fresh listing takes.
- Once approved, the new version writes back to the raw table,
dbt snapshotversions it as a new SCD2 record, anddatahub ingestreflects the change in DataHub's lineage graph — so the next inspection, or any other agent querying the same graph, inherits the updated history.
How I built it
- dbt + DuckDB for the data layer: a
dim_skuregistry (persisted surrogate keys, not hashed — because sellers can rename SKUs without losing history), an incrementalraw_description_updatesmodel, and adescription_historySCD Type 2 snapshot. - DataHub (local quickstart) ingests the dbt project's
manifest.json/catalog.jsonto build a real, queryable catalog and lineage graph. - DataHub's MCP Server (
mcp-server-datahub) is the agent's read path into that catalog — verified non-hallucinated by checking thatget_entitiesreturns this project's actual schema fields, not an invented example. - LangGraph powers both the inspection agent's own single-node graph (needed because
interrupt()only works inside a compiled graph context) and Pingpin's existing 5-agent pipeline (compliance check → SEO extraction → draft → hard-gate + LLM audit → final approval), which the inspection agent drives directly rather than reimplementing. - Pydantic models keep the facts/inference/rewrite data contracts structured and typed end to end.
Challenges I ran into
- A silent SKU-identity bug. The write-back path originally looked up SKUs by product name instead of by ID, so an approved rewrite for a known SKU (
S001) was silently treated as "not found" and minted a brand-new, duplicate SKU. Caught by checking the actual database row, not by trusting a log that said "success." - A dbt model quietly wiping real data. The raw table was materialized as
tableinstead ofincremental— everydbt runrebuilt it from scratch, erasing whatever the agent had just written, including via an unrelated background process re-runningdbt runperiodically. Same lesson: verify the data, not the log output. - Getting MCP working from real Python code, not just a CLI. Verifying the connection worked from
mcp-cliwas one thing; wiringstdio_client+ClientSession+ asyncinterrupt()handling into a working script was a different, more fiddly problem — solved by building it up method by method and testing each layer before combining them.
Accomplishments that I'm proud of
Getting a fully verified, real end-to-end run: a genuinely stale SKU flagged → schema confirmed via MCP → human approval → routed through Pingpin's actual production pipeline, including a real hard-gate rejection and retry. From there it was written back, with dbt snapshot correctly closing out the old version and opening a new one, and DataHub's lineage graph reflecting the change. Every step backed by a real database query, not a screenshot I trusted on faith.
What I learned
That "the code ran without an error" and "the result is actually correct" are two different claims, and conflating them is how bugs like the SKU-identity mixup and the data-wiping materialized='table' issue slip through. Also: MCP's real value here wasn't in writing to DataHub — it was proving, before any query ran, that the agent's understanding of the schema was grounded in something real.
What's next for Pingpin Context
- Move Pingpin's A4 hard-gate use-case check from exact-phrase matching to semantic matching, since it can currently reject listings that convey a valid use-case in different wording.
- A persisted "original brief" store, so rewrites can draw on the seller's original creative intent rather than the prior description alone.
- Extending the inspection signal beyond time-since-update — e.g. incorporating sales/conversion signals — to prioritize which stale listings actually matter most.
Built With
- datahub
- dbt
- duckdb
- langchain
- langgraph
- mcp
- pydantic
- python
Log in or sign up for Devpost to join the conversation.