An HVAC company with eleven techs and one overworked office manager learned about this failure mode from a screenshot. A customer posted it in a Google review: two side-by-side chat transcripts. In the first, dated Saturday, a human support person wrote "We've approved a $180 partial refund for the compressor issue — you'll see it in 5–7 business days." In the second, dated Monday, the same company's AI assistant, answering "any update on my refund?", replied warmly: "Thanks for your patience! At this time, refunds are not available for installed equipment per our service policy."
Same company. Same case. Two answers, 48 hours apart, and one of them called the other a liar in front of a star rating.
The owner assumed the AI had a bug in its refund logic. It didn't. The AI's refund logic was perfectly consistent with the policy document it was trained on. What the AI never saw was the resolution — the one decision that mattered more than the entire policy, made by a human, stored in a ticketing system the agent didn't read. When I pulled the logs, the pattern was not one review. In 90 days the AI handled 2,340 conversations that touched previously-escalated cases, and in 89 of them — 3.8% — it contradicted, re-litigated, or simply didn't know about the human's resolution. Eleven of those customers asked for the human again. Three left reviews. One demanded a chargeback, citing the screenshot.
This is the failure mode nobody tests for, because every test suite checks the AI against policy. Almost none check the AI against recent human decisions.
The handoff is a one-way door
Look at how most AI-to-human handoffs are built. The AI detects it can't resolve the issue, posts a summary into a ticket, and goes quiet. The human works the ticket, resolves it, closes it. Then the customer comes back — because customers always come back — and the conversation routes to the AI again, which greets the ticket like a stranger.
The information needed to not embarrass yourself exists in exactly one place: the case record. The agent reads policy, FAQs, and order history. It does not read case_state, resolution_id, or the resolution note. So when a customer says "update on my refund," the agent runs the same policy-grounded reasoning it always runs and confidently generates the pre-resolution answer — the one that was true on Friday and became false on Saturday at 2 PM.
Here's the part that makes this expensive rather than merely awkward. A human would never make this mistake, because the human would check the ticket. The receptionist who approved the refund is the same receptionist who answers the phone Monday. The entire value of a small support team is that decisions are stored in one person's memory. When you front that team with an agent that doesn't read the case file, you haven't added a layer — you've added an amnesia filter that re-litigates settled decisions with perfect confidence.
And it's worse than silence. The AI doesn't say "let me check the status of your case." It says "refunds are not available." It produces a counter-decision — fresh, articulate, documented, and stamped with your logo. The customer doesn't see an out-of-date bot. They see a company arguing with itself, and they keep the screenshot.
Step 1: Close the loop — resolution writeback
The fix starts with refusing to let a human resolution be a private event. Every escalation that ends in a decision must write back a structured record the agent can consume:
{
"resolution_writeback": {
"required_on": "escalated_case_close",
"schema": {
"case_id": "string",
"resolution_id": "string",
"case_state": "resolved | closed | pending_customer | pending_vendor",
"decision_type": "refund_approved | fee_waived | replacement | denial | policy_exception",
"decision_value": { "amount": 180, "currency": "USD" },
"decision_owner": "human",
"effective_at": "2026-11-01T14:02:00Z",
"expires_at": null,
"customer_facing_summary": "Approved $180 partial refund for compressor issue; arrives in 5-7 business days.",
"valid_until_changed": true
},
"on_missing_writeback": "case_state_stays_escalated_and_ai_defers_to_human"
}
}
Two fields carry most of the weight.
customer_facing_summary. Not the internal note — the sentence the human would actually want the customer to hear. When the agent is asked "any update?", it quotes this verbatim instead of generating policy-flavored prose. The human's words become the source of truth, not the policy doc. The single most effective line I've added to any support agent is: if a resolution record exists, your answer is the summary, plus logistics. Do not explain the policy.
valid_until_changed: true. The decision stands until a human changes it — not until the AI finds a policy clause that conflicts with it. Policy exceptions are, by definition, cases where the policy doesn't apply. The agent ranking a policy paragraph above a recorded exception is the entire bug.
Step 2: The contradiction gate
Writeback fixes the data. The gate fixes the behavior. Before the agent answers any question that touches an escalated or previously-escalated case, it must run one check:
{
"contradiction_gate": {
"trigger": "conversation_references_case_with_case_state != 'open'",
"steps": [
"resolve_case_id_from_customer_context",
"fetch_case_state_and_resolution_id",
"if_resolution_exists: answer_must_be_consistent_with_decision_value",
"if_state_is_escalated_or_pending: say_handled_by_team_no_new_decisions",
"if_no_case_record_found: say_let_me_check_never_infer_status"
],
"forbidden": [
"restate_policy_contradicting_decision_value",
"re_litigate_resolved_decision",
"offer_alternative_to_approved_resolution",
"guess_case_status_without_record"
],
"on_conflict": "acknowledge_decision_and_flag_for_human",
"customer_facing_rule": "never_describe_decision_or_status_not_in_record"
}
}
The line that earns its keep is if_no_case_record_found: say_let_me_check. The 89 contradictions in the audit split into two kinds: 31 were cases with a resolution the agent didn't read, and 58 were cases the agent couldn't find at all — so it invented a status from the order history. "I don't see anything pending" is a factual claim about a database the agent only half-searches. It must be replaced with "let me check with the team," which is what a human says when the answer isn't in front of them.
And forbid alternative offers matters more than it looks. In one audited case, a human had approved a replacement unit; the AI, unaware, helpfully offered a 10% discount code instead. The customer took the discount and the replacement. Your agent didn't just contradict the resolution — it created a second, unauthorized one.
Step 3: Make re-litigation visible
Gates prevent the next contradiction. They don't find the existing ones, and every deployment has them. Run one query: for every conversation within N days after a case close, diff the agent's customer-facing statements against the decision_value in the resolution record. Flag any negative claim ("refund is not possible," "we don't waive fees") that conflicts with a recorded positive decision.
That's how we found the HVAC company's 89. Sorted by customer sentiment, 11 were still actionable — customers who could be called by a human before they wrote anything public. The contradiction list is not a report; it's a callback queue.
Then keep it running daily. A contradiction rate above zero is normal in week one and a defect in week four.
Step 4: Decommissioned answers need tombstones
Some contradictions come from the other direction: policy changed, and the agent kept serving the old answer. If you change a policy that affects open or recent cases, write a tombstone — a dated entry the agent must check before answering anything in the affected category, stating what changed, which case IDs it applies to, and what to say instead. Tombstones expire. Untombstoned policy changes become contradictions with a 2–6 week delay, which is exactly long enough for the customers who got the old answer to come back.
Step 5: The Monday audit (five checks, 15 minutes)
| # | Check | Method | Healthy threshold |
|---|---|---|---|
| 1 | Writeback completeness | Closed escalations missing a resolution record | 0 |
| 2 | Contradiction rate | Agent statements conflicting with decision_value
|
0 |
| 3 | Invented statuses | Status claims with no case record backing them | 0 |
| 4 | Double resolutions | Unauthorised offers made after a human decision | 0 |
| 5 | Tombstone coverage | Policy changes in last 30 days lacking tombstones | 100% |
Check 3 is the one that catches the quiet variant. An agent that says "there's nothing pending" when it simply failed to find the ticket isn't contradicting anyone yet — it's manufacturing a fact, and the contradiction arrives in the next turn.
The numbers, for the internal pitch
- Contradiction rate without writeback: 2–5% of post-escalation conversations. The HVAC company was 3.8%. In a 20-seat e-commerce support team I audited, it was 4.6%, concentrated in refunds and warranty — the two categories with the highest emotion and the lowest forgiveness.
- Cost per public contradiction: $400–$900 in churn, review repair, and the human hours spent re-resolving a case that was already resolved. The screenshot review cost the company an estimated 9 customers who mentioned it in pre-sale chats.
- Resolution writeback adds one field to your close-ticket flow and 15 seconds per ticket. Agents adopting it report zero change in handle time; the summary field replaces half the internal note they were writing anyway.
- After the gate, the contradiction rate went to zero in the first month and stayed there, because the agent became structurally incapable of answering a case question without reading the case.
The HVAC company's fix was a writeback schema, a contradiction gate, and a daily diff query. The review is still up. The screenshot outlives every patch you ship — which is why this gets fixed before the next Saturday approval, not after the next Monday transcript.
If you want the working version of this — resolution-writeback schemas, contradiction-gate configs, the diff query, and the audit checklist I run on every deployment — it's here: https://whop.com/agentfast-04b6/
Top comments (0)