Validate a WrenAI semantic model's references before an agent queries through it
Before letting an agent query through WrenAI's governed semantic layer (MDL), validate that every relationship and metric resolves to a model and column that actually exist, so a stale definition fails a check instead of quietly returning wrong-but-plausible numbers.
Run this workflow
CI-verified, 2/2 fixtures passing.
Build this with your agent
One copy-paste hands Claude Code, Codex, or Cursor the full recipe, steps included, nothing to fetch.
Intended Use
Teams putting an agent in front of a warehouse through WrenAI's MDL semantic layer. CI parses the MDL and asserts referential integrity: every relationship references models that exist, and every metric and dimension resolves to a real model and column. No warehouse connection, no key, no model call. The step where the agent turns a question into SQL through the layer is fenced, because no green check can promise a model's query is correct.
Not for
- Promising a generated answer is correct, this checks the semantic model is internally consistent, not that a query result is right
- A model maintained by nobody, the layer only pays off if you keep it current; a dangling reference here is exactly the wrong-but-plausible-number risk made visible
- Following old GenBI tutorials, WrenAI restructured in May 2026, the turnkey web app now lives on the `legacy/v1` branch and main is the context layer and SDK
The Stack
Tested Against
github.com/Canner/WrenAI (2026-07; legacy/v1 holds the old app)python@3.11node@20Side effects & data flow
- Network
- none, local only
- Writes
- ./mdl.json, ./validate.py
- Credentials
- none required
Prerequisites
- Python 3 to run the validator
- A real MDL exported from your WrenAI project to check against
Steps
- 1
Parse the MDL and assert every reference resolves
Build the model-to-columns map from the MDL, then check each relationship's endpoints and join condition, and each metric's measure and dimension, against columns that actually exist. Rename orders.amount and the metric's measure stops resolving here, loudly, instead of the agent returning a plausible wrong total. CI runs this against a fixture MDL. Pointing an agent at the layer is fenced.
cat > mdl.json <<'JSON' { "models": [ { "name": "orders", "columns": [{"name":"id"},{"name":"customer_id"},{"name":"amount"}] }, { "name": "customers", "columns": [{"name":"id"},{"name":"region"}] } ], "relationships": [ { "name": "orders_customer", "models": ["orders","customers"], "condition": "orders.customer_id = customers.id" } ], "metrics": [ { "name": "revenue_by_region", "baseObject": "orders", "measure": {"column":"amount","expression":"sum"}, "dimension": {"model":"customers","column":"region"} } ] } JSON python3 - <<'PY' import json, sys with open("mdl.json") as f: mdl = json.load(f) cols = {} for m in mdl.get("models", []): cols[m["name"]] = set(c["name"] for c in m.get("columns", [])) def resolve(model, column): if model not in cols: return "model '" + str(model) + "' does not exist" if column is not None and column not in cols[model]: return "column '" + str(model) + "." + str(column) + "' does not exist" return None errors = [] for rel in mdl.get("relationships", []): for mm in rel.get("models", []): e = resolve(mm, None) if e: errors.append("relationship '" + rel.get("name", "?") + "': " + e) cond = rel.get("condition", "") if " = " in cond: for side in cond.split(" = "): side = side.strip() if "." in side: a, b = side.split(".", 1) e = resolve(a, b) if e: errors.append("relationship '" + rel.get("name", "?") + "' condition: " + e) for met in mdl.get("metrics", []): meas = met.get("measure", {}) e = resolve(met.get("baseObject"), meas.get("column")) if e: errors.append("metric '" + met.get("name", "?") + "' measure: " + e) dim = met.get("dimension") if dim: e = resolve(dim.get("model"), dim.get("column")) if e: errors.append("metric '" + met.get("name", "?") + "' dimension: " + e) if errors: print("BAD: semantic model has dangling references (the wrong-but-plausible-number risk):") for x in errors: print(" - " + x) sys.exit(1) print("MDL OK: " + str(len(mdl.get("models", []))) + " models, " + str(len(mdl.get("relationships", []))) + " relationship, " + str(len(mdl.get("metrics", []))) + " metric; all refs resolve (orders.customer_id -> customers.id, revenue_by_region measures orders.amount by customers.region)") PY - 2
Point the agent at the governed layer (the model step, not checked by CI)
Install WrenAI's skills into your coding agent (npx skills add Canner/WrenAI --skill '*'), start a session and run the wren-onboarding skill to wire up the MDL and connectors, then ask questions in plain English. The agent's translation of a question into SQL through the layer is the fenced step, no green check can promise a model's query is correct.
Eval, 2 fixtures
Last passed: verified todaymdl-okcontainstimeout 30s · max $0Expected:
MDL OK: 2 models, 1 relationship, 1 metric; all refs resolve (orders.customer_id -> customers.id, revenue_by_region measures orders.amount by customers.region)clean-exitexit_codetimeout 30s · max $0Expected:
0
Results
WrenAI's thesis is that agents fail on business data not because they cannot write SQL, but because they do not know what your warehouse means: you describe entities, relationships, and metrics once in a semantic model (their MDL) and any agent queries through that governed layer across 20-plus sources. The catch is that the semantic layer is both the value and the cost, it only pays off if you keep it current, and a stale definition returns wrong-but-plausible numbers. This recipe makes the layer's internal consistency a check: every relationship, metric, and dimension must resolve to a model and column that exist. Note WrenAI restructured in May 2026, the old turnkey GenBI web app now lives on the `legacy/v1` branch and main is the context layer and SDK, so old tutorials may not match.
Did this work for you?
Our CI checks the setup runs. You tell us if the whole thing worked. Tell us straight.
Related workflows
- Wire the DeepWiki MCP into your agent so it looks up repos instead of hallucinating
- Verify an agent-skills plugin before you ship or install it
- WebMCP: declare a site's agent tools, and gate the ones that spend money
- Wire GLM-5.2 into Hermes: valid route, 64k-context check, no key in config
- Voyager pattern: validate a procedural skill store before you trust a saved skill
- Hermes MoA: stack frontier models into one virtual model for hard turns
Liked this workflow?
Get new verified workflows in WebAfterAI, three issues a week (Tue, Thu, Sat).