Inspiration

  • Every supplier ships inventory data in its own spreadsheet shape. Fixing that by hand means someone remapping columns each time, and getting it wrong occasionally.
  • Stock drifts into the wrong zone because demand shifts quietly. A SKU turns hot and nobody notices until the pick times already show it.
  • Ask why a SKU got flagged or moved a week ago, and the honest answer is usually "nobody remembers." The reasoning doesn't outlive the person who made the call.
  • Built for Build with DataHub: The Agent Hackathon, on the bet that a shared metadata catalog beats four agents each keeping private notes nobody else can read.

What it does

  • Four agents - Data Engineer, Putaway Planner, Market Trend Analyst, Warehouse Optimization hand off work through DataHub instead of reading each other's database tables.
  • Data Engineer Agent proposes column mappings for supplier uploads, grounded in DataHub's own schema and glossary.
  • Putaway Planner scores candidate locations with plain arithmetic, then asks an LLM for one sentence explaining the pick.
  • Market Trend Analyst checks a live DataHub freshness assertion before classifying anything. If the assertion is failing, it declines out loud and writes down why instead of scoring on stale data.
  • Warehouse Optimization Agent reads the demand tag Market Trend Analyst wrote, checks it against a real lineage timestamp to confirm it's still trustworthy, then flags SKUs sitting in the wrong zone.
  • A per-SKU audit trail shows one product's full route: onboarded, received, put away, shipped, classified, reslotted, with the responsible agent named at every stage.

How we built it

  • Next.js 15 frontend, FastAPI backend, Postgres on Supabase for operational rows, DataHub GMS for the meaning of that data: schema, lineage, tags, trust signals.
  • Every agent runs on Google ADK against Vertex AI, wired to DataHub through the Agent Context Kit's tools.
  • Only one agent's LLM ever touches DataHub directly, and even then read-only: search, get_entities, list_schema_fields. Every write happens in separate, reviewed, deterministic code, run only after a human confirms a decision.
  • One idempotent bootstrap script seeds domains, a glossary, nine dataset entities mirrored from the Postgres schema, a custom "AI Agent" ownership type, and a freshness assertion left deliberately failing for the trust gate to check against.
  • Lineage runs through real DataFlow/DataJob process entities, not just dataset-to-dataset edges: one for the inventory import's generated transform script, one terminal decision node with no outputs for each reslot call the optimization agent makes.

Challenges we ran into

  • The optimization agent's staleness check called get_aspect() with a string instead of the real aspect type. The SDK raised an error, our own broad error handling swallowed it, and the agent would have silently declined every SKU forever.
  • Writing a tag to a shared per-SKU entity overwrote another agent's ownership and domain on that same entity, because DataHub's write helper replaces full entity state instead of merging it. Fixed by reading current owners first.
  • DataHub's own add_lineage() helper always wrote a zero timestamp on every edge, something we only found by reading a result straight back off the live server. We filed it as an upstream fix: PR #19084.
  • A null-pointer bug in DataHub's own GraphQL layer broke the Lineage tab entirely on any entity whose lineage touched the new aiAgent entity type, because Apollo discards the whole response when any part of it errors. Traced it to the exact resolver and filed a second upstream fix: PR #19085.
  • A two-way PO/GRN matching rule flagged every normal partial delivery as a variance, because it checked for an exact quantity match instead of over-receipt. A unit test had encoded that bug as correct behavior.
  • The bulk GRN upload path and the manual finalize path disagreed on what counts as a finalized GRN. The bulk path had no way to express a linked PO at all, so a receiving exception that should've been held back for a human silently became stock.
  • One freshness assertion, shared globally across every tenant, meant one tenant's order activity couldn't move the trust gate on its own and could leak into another tenant's result. Replaced with a per-tenant assertion created on first use.

Accomplishments that we're proud of

  • Every agent's LLM is structurally unable to write to the catalog except through separate, reviewed, deterministic code. Reasoning and writing are different code paths.
  • A trust gate you can watch decline live. Ship no fresh orders, click Run Analysis, and Market Trend Analyst refuses to classify anything and points at the real assertion that's failing.
  • One walkable lineage graph in DataHub, from raw upload through transform, canonical table, classification, and final decision, spanning all four agents. Confirmed against the live server's own traversal, not just asserted in a script.
  • Two real bugs found in DataHub's own server code, not just our integration with it, each reproduced live and turned into upstream pull requests.
  • Every agent's defined-of-done scenario was independently re-verified against a live DataHub instance and live Vertex AI calls, not just passed against a mock. That's what caught three of the optimization agent's four bugs, all invisible to the automated test suite.

What we learned

  • A green test suite doesn't mean a working agent. Ours stayed green through three real bugs shipping, because the fake DataHub client and the code under test had quietly agreed on the same wrong answer.
  • DataHub's write helpers replace an entity's full state by default. Reading current state and merging before writing has to be the caller's job.
  • Multi-tenancy layered on top of entities DataHub treats as global needs deliberate isolation. A shared assertion or a shared lineage edge will leak across tenants unless it's explicitly scoped.
  • Confirming an SDK method's actual behavior against the live server before writing code against it is worth the extra ten minutes. The two features built that way shipped with zero bugs; the one built from an assumed constructor signature shipped with three.
  • An experimental DataHub entity type can be writable at the registry layer while still unsupported two layers up, in GraphQL. Worth checking both before demoing on it.

What's next for AntWMS

  • Get the two DataHub pull requests merged, and pick up full aiAgent registry support once it ships, so all four agents register as first-class entities with real lineage into what they consume.
  • Add stock reservation at order creation, closing the gap where a shipment can outrun on-hand inventory with only a logged warning.
  • Move batch agent runs off the request thread and onto a background worker instead of one synchronous LLM call per task.
  • Teach Putaway Planner about actual location capacity instead of scoring purely on distance and demand.
  • Extend the Data Engineer Agent's generated-transform-script path to GRN uploads, which currently only get the shorter, one-hop lineage edge.

Built With

  • datahub
  • fastapi
  • google-adk
  • nextjs
  • supabase
Share this project:

Updates