Groundplane
2026 · open source · python
Groundplane is a small Python library that checks an agent's answer against the tool results it was given. If the model reports the wrong winner or total, it raises an error showing what the data actually said.
Where it came from
I first built this inside an AI platform at TikTok. Some of our summaries had to rank a table of numbers, and a senior stakeholder pointed out that naming the wrong winner wasn't acceptable, even if the model usually got it right. So I moved the ranking into code: code worked out the winner and the model only described it. Groundplane is that idea pulled out into a library, extended beyond picking a winner, and published under MIT.
before
rows = warehouse.query("select campaign, ctr from campaign_daily")summary = llm(f"Which campaign performed best?\n{rows}")# says "north". It was "harbour".
after
from groundplane import FactRegistry, boundary, superlative reg = FactRegistry()reg.record_ranking( "campaign_ctr", {"north": 0.0412, "harbour": 0.0455, "delta": 0.0301}, key="ctr", tool="warehouse.query", args={"table": "campaign_daily", "window": "7d"},) with boundary(reg, facts=["campaign_ctr"], checks=[superlative(fact="campaign_ctr")]) as b: b.submit(llm_structured(facts=b.facts())) # {"winner": "north", ...}
raised
UnsupportedClaim: unsupported claim in field 'winner':model said 'north', registered facts support 'harbour'| fact='campaign_ctr'| provenance=warehouse.query(table='campaign_daily', window='7d')| computed argmax on 'ctr' is 'harbour' (0.0455); 'north' ranked #2 at 0.0412
What it checks, and what it doesn't
It only checks the fields the model declares. If the model names the right winner and then says something misleading around it, that passes. I kept it that narrow on purpose. A judge model could read the prose too, but then the check would be probabilistic, and I wanted the same facts to give the same result every time.
Figure 02 · The whole library
Record the tool facts, then check the fields the agent declares
- Facts
- Model output
- Verdict
Tool results go into a FactRegistry, each tagged with the tool call and arguments that produced it. A fact can't be overwritten; a new value becomes a new fact, so the old one and its source stay around. A boundary wraps the part of the agent's work being checked and says which facts it may use. The model has to submit structured fields there. A plain string is rejected, and leaving the block without submitting also raises, so you know when validation never ran.
The six checks
A normal validator looks at each field on its own. These look at how the values relate, because two valid values can still end up on the wrong rows.
- superlative
- the named winner and any quoted score match the computed top value
- ranking_prefix
- a top-k list matches the computed order, including ties
- aggregate_reconciles
- a stated sum, mean, count, min, max or median matches the recorded rows
- entities_recorded
- every name the model used came from the recorded data
- row_integrity
- every field comes from the named row, not its neighbour
- comparison
- “A beat B by 12%” is recomputed; when it’s wrong, the error says which convention it does match
Numbers are compared exactly unless the caller sets a tolerance.
Errors that explain themselves
The error says which field failed, what the model said, what the data says and which tool call it came from. For a ranking it also shows where the model's pick actually placed. Inside LangGraph, a failed check can go back to the model as a correction instead of crashing the run. A misconfigured check still crashes, since that's a bug for the developer to fix, not something to ask the model about again. An MCP adapter records tool results as facts the same way.
The core has no dependencies, and 169 tests run on five Python versions in CI. Many of them are cases where the plausible answer is wrong.
Package details
- check families
- 6
- fact types
- 4 · Fact, Ranking, Table, Domain
- tests
- 169, all deterministic
- python
- 3.10 – 3.14 in CI
- runtime dependencies
- 0
- adapters
- 2 · LangGraph, MCP
- typing
- mypy, disallow_untyped_defs
- library
- ~2.6k lines · tests ~2.2k
- status
- v0.1.0 · MIT · install from GitHub
// AI TOOLING
All four layers
01 · Memory
Workbase (opens in a new tab)
A notes folder per job that the agent keeps up to date.
02 · Context
Agent skills
The reusable instructions my agents load.
03 · Evaluation
Skillsmith
Drafts a skill, then a blind test decides whether it stays.
04 · Runtime checks
Groundplane
Checks agent output against recorded tool results.
// NEXT
Compoze
Document-grounded assistants, a company I ran alone and sold to a client