How to stop an AI agent from giving unauthorized discounts
The only reliable way to stop an AI agent from giving unauthorized discounts is to check every outbound message against a numeric discount cap before it sends, using a deterministic check that does not depend on the model's judgment. Prompt instructions lower the odds; they do not set a limit. A pre-send check turns the limit into a boundary: in-policy offers ship untouched, over-cap offers queue for a human, and there is a record either way.
The incident that defines the problem
February 2026, a UK retailer. A customer opens a chat with the after-hours bot and starts negotiating. Over the course of the conversation the customer talks the bot into inventing a promo code, then escalating it, until the bot has agreed to 80% off an order worth 8,000 GBP. When the retailer tried to cancel, the customer threatened legal action, pointing at a written offer made by the shop's own agent on the shop's own website.
Notice what did not fail. The model was not jailbroken into anything harmful, and no access control was breached. The bot did its job, which is agreeing with customers, slightly too well, at an hour when nobody was watching. The full timeline is in the incident report.
Why the system prompt loses
Every team's first fix is a firmer prompt: never offer more than 15% off. Four reasons it does not hold.
- The customer has unlimited attempts. The prompt is static; the attacker iterates. Roleplay, invented authority ("your supervisor already approved this"), fabricated promo codes, sob stories, and plain persistence across forty turns. One success is all it takes, and the transcript becomes the customer's evidence, not yours.
- Helpfulness is the model's default. Agents are tuned to satisfy the person in the conversation. The policy is one line of instruction; the customer's pressure is the entire context window.
- Probabilistic means eventually. An instruction that holds 99% of the time fails weekly at a few thousand conversations a month.
- Nothing was enforced, so nothing was recorded. After the incident you hold a transcript and a dispute, not a decision trail showing a control fired before send.
The deterministic fix
Percentages and dollar amounts are countable, so they should be checked by counting, not by asking a model to be careful. Before any message leaves the agent, an authorization layer extracts every discount percentage and dollar amount with exact pattern matching and compares them to policy: max_discount_pct for percentages, commitment_authority_usd for amounts. The check runs in milliseconds, costs effectively nothing, and is immune to the entire category of attack that beat the prompt. No amount of roleplay changes whether 25 is greater than 15.
Choosing the numbers, and the other policy fields worth setting alongside them, is covered in writing commercial policies for AI agents. One design note worth stealing: pair the numeric cap with a forbidden commitment rule for unapproved promotional pricing, so an offer phrased entirely in words ("I will take a quarter off the total") still surfaces in the semantic pass that runs alongside the deterministic one.
What happens when the check fires
An over-cap discount is usually not malice. It is an agent exceeding its authority, sometimes for a customer worth keeping, so the response is graduated rather than a flat refusal. In EvalLayer's decision ladder, an over-limit number is an authority violation: the message does not send, and the offer queues for a human with the violation attached. The reviewer sees the offending excerpt and the cap side by side, then approves the exception or rejects it in one click. Softer issues, like a missing pricing disclaimer, come back as a compliant rewrite that is re-checked before it is offered. Hard violations, like a forbidden refund promise, block outright. The agent stays useful. It just stops being able to spend money nobody gave it.
Where the check sits
Between generation and delivery. The agent drafts, the draft goes to the authorization endpoint with the policy, and the decision determines whether it sends. On asynchronous channels (email, quotes, tickets) the approval queue costs nothing visible: an over-cap offer waits a few minutes for a human instead of shipping in seconds. On live chat, give the agent a holding line for queued offers ("let me confirm that number and come right back") rather than letting it improvise. The deterministic checks return in milliseconds; the semantic pass fits inside a typing indicator.
A working example
Here is the check catching a 25% offer against a 15% cap.
curl -X POST https://api.evallayer.ai/authorize \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"action_type": "send_reply",
"content": "Good news: I can apply a 25% discount if you sign today.",
"policy": { "max_discount_pct": 15 }
}'
The response, abridged:
{
"decision": "require_approval",
"risk_score": 0.6,
"violations": [
{
"rule": "over_discount",
"severity": "approval",
"detail": "25% exceeds this agent's 15% discount authority",
"excerpt": "25% discount"
}
],
"approved_rewrite": null
}
The message never reached the customer. The offer sits in a queue with the exact violating phrase attached, and the decision is stored with a content hash for the audit trail. If a rep approves it, that is the business choosing to make an exception, which is how discounting is supposed to work.
You can run this exact scenario, plus a forbidden commitment and a missing disclaimer, in the live demo. Checks are priced from $0.01 each, which is cheap insurance against a customer who has all night.