"We told the model not to do that" is the sentence I hear most often after an incident. Instructions in a prompt shape behaviour; they do not guarantee it. A guardrail is a check the model cannot talk its way around because the model never gets to see it.
Where guardrails sit
- Before the model: input validation, allow-lists, rate limits, PII stripping.
- Around the model: tool permissions. The agent only has the tools you hand it, scoped to the minimum.
- After the model: output checks in plain code. Caps, floors, ceilings, policy rules, format validation.
- At the action: the system that executes the action re-validates. Never trust the orchestrator alone.
The refund example
In the demo on my homepage, the agent proposes a refund amount. Three checks run after it, in code, with no model involved: a per-agent cap, a fraud score threshold, and a policy check. If any fails, the money does not move. The model's opinion is an input to that pipeline, never the final word.
Writing a guardrail
def check_refund(proposal, order, customer):
if proposal.amount > CAP_PER_AGENT:
return clamp(proposal, CAP_PER_AGENT, reason="cap")
if customer.fraud_score > FRAUD_LIMIT:
return route_to_human(proposal, reason="fraud_score")
if not policy_allows(order, proposal):
return reject(proposal, reason="policy")
return approve(proposal)
Notice what is not here: no prompt, no model call, no natural language. That is the point.