Post #2 · Understand TGMS

Why AI tools need
output contracts

Input validation cannot stop a model from inventing fields in a tool's result.

First published July 2026 · TGMS 0.4.0 · snapshot matrix-dev-oss · 7 min read · Status: Current

In one sentence

A tool definition that describes its arguments but not its results is half a contract, and a model will fill in the other half by guessing field names that sound right.

Why this matters

Here is the failure, exactly as it happened. We pointed a small model at our data operations for the first time. It asked one of them for a reachability result, and then, to use that result, wrote this:

The model read count. The operation returns rows_total.

That is the entire bug. No such field exists, so at run time the reference resolved to nothing and the step collapsed — and every step after it. Across 22 tasks, the share that executed successfully was 0%.

The part worth sitting with is that every one of those plans passed validation. Nothing was malformed. The model had a schema for each operation's arguments and honoured all of them. It invented a name on the way back out, where nobody was checking.

The guess is not even unreasonable. A model has read a great many APIs in which the thing that counts returns count, so absent any statement to the contrary it emits the name the rest of the world uses. Nothing in a standard tool-calling stack contradicts it: the tool definition ships an input schema, the call succeeds, and the invention surfaces later as a dangling reference, an empty value, or — worst — a plausible answer computed from nothing.

The idea in plain language

First, the word this post needs. A plan here is just a short list of tool calls where later calls use the results of earlier ones: fetch the messages in this window, then count what that returned, then report the number. Because step three refers to step two, the steps have to agree about what step two hands over. That agreement is the thing that was missing.

Written out, the asymmetry is stark. One direction of the boundary is governed; the other is not.

temporal_reachability ARGUMENTS — schema-checked src: string ✓ window: interval ✓ as_of_tt: int64 ✓ RESULT FIELDS — undeclared ? ? ? earlier step sends arguments validated later step reads s2.count unchecked
Everything entering the tool was governed by a schema; the field names a later step read back out were governed by nothing, so the model supplied them from habit. Red marks the ungoverned direction, which is where the failure lives. The asymmetry is a property of how tools are usually defined, not of this particular operation.

And here is the same plan before and after the contract existed — one field name is the whole difference between nothing running and everything running:

BEFORE — passes validation, fails at run time s2 · reachability s3 · reads s2.count ✕ no such field — plan dies here AFTER — rejected before running, repaired, then executes s2 · reachability s3 · reads s2.rows_total ✓ answer
The repair is one identifier. What changed is not the model's reasoning but whether the mistake was catchable before execution — green marks the check passing, not a smarter plan. A plan can still be wrong in ways this figure does not show; it just cannot be wrong in this way any more.

How TGMS handles it

Every data operation in the TGMS registry now declares the fields it returns, beside its arguments — and that registry generates the tool definitions the model is given, so the manual and the enforcement cannot drift apart. Before a plan runs, the verifier walks every cross-step reference and rejects any whose field the producing operation does not emit.

The rejection is not a bare error. It carries the real field list back to the planner, which is what makes the next attempt succeed rather than guess again:

E_SCHEMA: temporal_reachability outputs no field 'count'
  (available: rows, rows_total, truncated, cursor)

A readable manual would not have done this. The model already had a readable manual; what it lacked was a checkable one. Documentation is a prompt. A contract is a gate.

Sidebar · what else the verifier checks before a plan runs Output fields are one gate among several. A plan is also rejected if it fails: All of these were already in place when the run described above scored zero, which is the point: they are input-side checks, and the failure was on the output side.

What we measured

Evidence box
Question
Does declaring and enforcing result fields change whether plans execute?
Workload
The 22-task development split over a message-log dataset, including 3 correction-probe tasks.
Compared
One system, one model, the check disabled versus enabled — a controlled re-run, not a model comparison.
Metric
Execution success: the share of tasks whose plan ran to completion and produced a result. Higher is better. This is not answer correctness — a plan can execute and still answer wrongly.
Held constant
Qwen2.5-7B-Instruct, temperature 0, same prompts, same tasks, same repair budget.
Run
Single deterministic run per condition.

What we found

On the three correction-probe tasks the change is total: none of them ran before, all of them ran after. Across the full 22-task split the same check moves execution success from nothing to just over half. Both numbers describe the same check; they differ because the rest of the split contains tasks a 7B model fails to plan for other reasons.

EXECUTION SUCCESS — output-field validation OFF ▸ ON Qwen2.5-7B-Instruct · temperature 0 · same tasks, same prompts · higher is better The 3 correction-probe tasks OFF · 0% — 0 of 3 ON · 100% — 3 of 3 All 22 development tasks OFF · 0% — 0 of 22 ON · 55% — 12 of 22
The headline 0% → 100% is 3 of 3 tasks on the probe subset, and it is execution success — whether the plan ran — not whether the answer was right. On the full 22-task split the same check yields 12 of 22. A three-task subset cannot support a rate; it shows that a specific, total failure was removed.

With the check in place the bottleneck moved from “can the model address a result” to “can the model plan”, which is the better problem to have — and one that model capacity does affect. On this split, the share of first-attempt plans that pass validation rises from 23% at 7B to 50% at 14B.

What this means in practice

If you ship tools for models to call — MCP servers included — the transferable rule is that result schemas deserve the same rigour as argument schemas. Concretely:

  1. Declare your result fields where you declare your arguments, and generate the model-facing tool definition from that one source so the two cannot diverge.
  2. Reject unknown field references before execution, not after — a dangling reference discovered at run time has already cost you the call.
  3. Put the real field list in the error. “Unknown field” makes a model guess again; “no field count; available: rows, rows_total, …” makes it correct itself.
  4. Do not name a field something the model will assume. If your counting endpoint returns rows_total, expect count to be attempted, and make that attempt fail loudly.
  5. Test with a small model. Large models paper over an absent contract often enough that you may never see the gap; a 7B model finds it on the first run.

What this result does not show

The check is narrower than “validating the shape of a result”. It verifies the first component of a reference path against the producing operation's declared fields. A reference to a field that exists is accepted whatever lies deeper inside it — nested shapes, element types, and whether a row really carries the sub-field a later step assumes all remain unchecked at plan time. This closed one specific hole completely; it did not make results statically typed.

The measurement is also small and single-condition: one model, one dataset, one deterministic run per condition, and a probe subset of 3 tasks behind the 100% figure. Execution success says a plan ran, not that its answer was correct — the accuracy of what those plans returned is a separate measurement.

Finally, no experiment here compares how often models of different sizes invent result field names. The effect holds at both 7B and 14B, but that is a statement about the mechanism, not a measured law about model scale.

Takeaway

Input validation tells you a model called your tool correctly. Only an output contract tells you the model is entitled to believe what it thinks your tool said — and the gap between those two is where a perfectly valid plan quietly stops working.

Evidence and reproduction

The check is a short helper in tgms/agent/verifier.py, called from the plan walker and from answer-spec resolution; the ablation flag that produced the OFF condition is ablate_output_contracts. The result-field declarations live with the operation definitions in the registry. The finding is written up in §6 of the paper. Every number on this page is quoted from docs/site_facts.json and checked in CI.

Continue reading

Prerequisite: why TGMS stores two kinds of time — the design these operations exist to serve.