DEV Community

Your Agent Needs a Schema, Not a Better Prompt

frank coyle on November 11, 2026

The failure nobody demos The agent demo always works. The customer asks for a refund, the agent calls the right tool, and the money move...
Collapse
 
onelifevibe profile image
OneLifeVibe •

The refund examples landed for me. A prompt that says never refund more than the order total competes with forty other rules once it grows to 4,000 tokens, so it is easy to see how an undelivered order still gets refunded. I liked the loop where a failed SHACL shape goes back to the agent as a tool result with the exact field, instead of a bare rejection. Starting with the one action that would hurt most if it went wrong feels like the right way to try this without modelling a whole domain first.

Collapse
 
mp3sss31 profile image
mp3sss •

Those refund and expense examples are exactly where prompt-only rules break down: a natural-language guideline competes with every other instruction for attention, while a schema or ontology lives outside the model and can be enforced deterministically before an action fires. This mirrors how we treat trusted boundaries elsewhere — the model proposes, but cardinality, referential integrity, and business invariants should be checked in code or a rule layer, not trusted to the model to remember. The practical split is to let the LLM handle intent and selection, and make the schema the last gate that rejects an inconsistent or invalid action before it reaches a tool.

Collapse
 
paul_irolla_6b5ae261d0224 profile image
Paul Irolla •

A prompt asks the model to follow a rule; a validator on the tool call refuses the call when the rule breaks. The refund limit belongs in the validator, because the model can lose a sentence from the prompt, while the check still runs each time the call is made.

Collapse
 
launchgatecheck profile image
Launch Gate •

The piece I'd pin down is what a validation event consists of, because "a refund must reference exactly one delivered order" is really two checks wearing one message. The shape check is legality: does this action reference exactly one order of class DeliveredOrder. The truth check is freshness: is order 8812's status in the graph current with the order system right now. pySHACL can only see the graph you hand it, so a stale snapshot makes the guardrail confidently enforce last Tuesday's world. The ontology defines what's legal; the data defines what's true; the report should name which snapshot it judged against.

Worth storing three pins per validation, pass or fail: the shapes version, the data snapshot identifier, and the action as proposed. That makes every rejection replayable - same action, same shapes, same snapshot, same verdict - which is what turns "rules are versioned in git" from a hygiene note into an audit property. A rejection you can replay is evidence; one you can't is a log line.

Collapse
 
kling4ai-production profile image
Kling4 AI •

Completely agree that deterministic schemas beat prompt tweaks every time when building production agents. Without strict JSON schema enforcement at the tool call interface, prompt drift and hallucinated parameter structures quickly snowball into unhandled runtime errors. Well articulated!

Collapse
 
hitensethiya profile image
Hiten Sethiya •

the rejection message going back as a tool result is the part most people skip, a bare 'action rejected' just gets you the same call again. same thing in travel, a model will confidently pick a hotel that doesnt fit the dates or the group, which is why at searchspot we'd rather that check sit outside the prompt

Collapse
 
alexcodebytes profile image
Oleksandr •

"Prompts ask. Ontologies define" is the line that makes this click, and "the LLM proposes, the ontology disposes" is going straight into my notes.

One thing I'd push on: the validation report is written for a compliance officer to read, but it's delivered to the model as a tool result. Those are different readers with different needs. A shape with five failed constraints could easily produce a wall of text a human audits fine and a model drowns in. Have you found that one message per shape works better than one message per violation in practice?

The "how many of those cases your prompt had already handled" line is the one I'd put in bold — that week of logging is the whole argument.

Collapse
 
mudassirworks profile image
Mudassir Khan •

the part that stuck with me is the validation report going back to the agent as a tool result with the real order status in the message. a bare action rejected teaches it nothing, agreed. we did something close with typed errors on a refund tool and retries got way cleaner once the error named the field and the current value. my gotcha tbh is shape drift, the shapes slowly disagree with the database after a few migrations and nobody notices until the validator rejects good actions. how do you keep the ontology in sync with the schema, generate it or review it by hand?

Collapse
 
promptalo profile image
PromptAlo •

I would version the shapes in the same change as the migration, and fail the build if a stored example no longer validates. That way a schema change cannot leave the checker describing a world the database has already left.

Collapse
 
lucalullo profile image
Luca Lullo •

Interesting read! I recently built a small AI agent from scratch in Python as a learning exercise, and separating the LLM from tool validation was one of the most useful things I learned.

One thing I'm curious about is how you would approach this in smaller projects. Would you introduce something like SHACL from the start, or begin with simple Python validation and move to an ontology only when the domain becomes more complex?

Collapse
 
maqron profile image
Maqron •

Strong framing - "the LLM proposes, the ontology disposes" is the right shape. One extension worth making explicit: validity constraints and budget constraints are different classes, and both belong in the disposal layer.

"A refund must reference exactly one delivered order" is checkable against a static shape. "Refunds today must not exceed $500" is also mechanical, but it's stateful - it can't live in the shape itself, it needs a ledger the validator queries. We ended up with three separate checks between proposal and execution: a per-action cap, a periodic cap, and a prepaid balance for metered actions, each refusing independently.

The nice properties you describe carry over: checks run in milliseconds with zero tokens, a compliance person can read the rule, and the refusal path is auditable - you can show exactly which constraint disposed of which proposal. The awkward part we hit: enforcement has to live server-side. A cap checked in client code is a suggestion.

Collapse
 
x1y1co profile image
X1Y1 •

One failure survives even a good validator: the refusal comes back, and the agent tells the user it worked anyway. In Eon's recent alignment benchmark, every write to the simulated tools was refused, and "false outcome reported" was the biggest failure category, 290 runs, with all 19 models doing it at least once. So the validator's answer should reach the user directly, not only through the agent's summary of it.