Picture the tools you give a finance-operations agent: get_order(id), lookup_customer(id), get_payment_status(order_id). During training or evaluation, those tools read from a database you provide. The agent never sees the database directly. It sees only what the tools return, and it takes those returns as ground truth. That is the whole design. Which means the coherence of the database is not a detail. It is the thing that decides whether the agent learns to reason or learns to hallucinate with confidence.
A team learned this when their agent started approving refunds that were slightly wrong. Not randomly wrong. Wrong in a way that traced straight back to the synthetic database behind the tools.
The tool returned a total that did not add up
Their synthetic orders had a total generated independently of the line items. So get_order() would return an order whose total did not equal the sum of its items, and the agent, trusting the tool, would reason from the wrong number and approve a refund against it. The agent was not broken. It was correctly acting on incorrect ground truth. When the records are generated so that total = sum(line_items) holds on every row, the tool returns a number the agent can actually reason from.
{
"order_id": 84120,
"line_items": [ 42.00, 18.50, 9.99 ],
"total": 70.49, = sum(line_items)
"status": "shipped", legal for a refund flow
"customer_id": 3310 resolves to a real customer
}The tool returned an impossible status
Because statuses were random, get_payment_status() would sometimes report a payment as refunded for an order still marked pending, a combination that cannot occur in a real system. The agent had no way to know it was impossible, so it learned that any status combination is plausible, and it stopped using status as a reliable signal. Records whose statuses walk a legal state machine give the agent a world where status means something, which is the only world where it can learn to use it.
The tool returned a reference to nothing
A customer_id that resolved to no customer meant lookup_customer() failed or returned an empty record mid-episode, and the agent learned to paper over missing data rather than treat it as the exception it should be. When every foreign key resolves, a missing record becomes a genuine signal again, not background noise the agent has been trained to ignore.
The fix is upstream of the agent
None of this is fixed by prompting the agent better. It is fixed by the data its tools return. CrossRow generates the records behind the tools so they are coherent by construction: derived fields compute exactly, statuses are legal, references resolve, and the numbers a tool hands back are numbers the agent can trust. It works across 125 schemas, so the backing systems for a whole suite of tools, orders, tickets, accounts, inventory, can each be realistic. And it uses no production data, so a tool can return a lifelike customer record without a real customer ever being involved.
The boundary worth stating plainly: CrossRow does not write your tools or your agent. It generates the records your tools return. But in a tool-calling system that is precisely the ground the agent stands on. An agent trained or evaluated against incoherent tool outputs does not fail loudly. It succeeds at the wrong task, confidently, which is the most expensive way for an agent to be wrong.