Build an AI Customer Support Agent: Full System Design, Cost Per Ticket and Failure Modes (2026)
- A support agent resolves a ticket for about 7.3 cents in models, roughly 5 cents when it escalates. The human path is the expensive one at about $1.65 to $2.20 per ticket. That is why deflection rate, not model quality, is the number the business case rests on.
- Confidence gating is the entire product. An agent that answers everything is a liability. An agent that answers 45% of tickets with a grounded citation and hands the rest to a human with a written brief is a business. Published production resolution rates for the leading vendor sit in the 42-53% band, not the 70%+ the marketing implies.
- Building in-house beats per-resolution vendor pricing at roughly 14,000 tickets a month on the arithmetic below. Under that, buy it. That answer costs me consulting revenue to write down.
Email, chat widget, in-app and API tickets collapsed into one ticket record with a stable id, tenant, channel and thread history.
~$0 · fails: duplicate threadsOne cheap model call that assigns intent, urgency, product area and a route. Deterministic rules run first and win ties.
$0.0028 · fails: silent misrouteHybrid search over public docs, internal runbooks and resolved past tickets. Past tickets are the highest-value and lowest-hygiene corpus you own.
$0.0002 · fails: stale doc winsThe loop, three budgets, the journal, and a hard rule that no irreversible tool runs without an idempotency key written first.
$0.066/ticket · fails: loopsRead tools into CRM, billing and order systems; a very small number of write tools behind approval. Per-agent allowlist, always.
1.8k tok/turn · fails: over-scoped tokenGrounding check, citation-span validation, policy check and a calibrated threshold. This is the layer that decides whether a customer ever sees the answer.
$0.0035 · fails: confident nonsenseA written brief (what the customer wants, what was checked, what was ruled out, the account facts) handed to a human with the trajectory attached.
saves ~1.5 min/ticketWhat is an AI customer support agent, architecturally?
A classifier, a retriever, a bounded agent loop with read-mostly tools, and a gate that decides whether the answer reaches a customer. Four parts. The agent loop is the smallest. Everything that makes the system safe to point at real customers lives in the classifier in front of it and the gate behind it.
That framing matters because the category is routinely mis-specified. People describe a support agent as a chatbot with your docs attached, which is a retrieval system with a text box. The thing that actually deflects tickets does more: it looks up the customer in your CRM, checks whether their invoice failed, reads the order status, decides whether its answer is grounded in something it can cite, and most of the time decides it is not sure enough and writes a handover brief instead.
Be clear on what this post is. It is a reference design: how I would architect this system and what the arithmetic says it costs, grounded in building AccioMatrix, an AI assessment and interview platform on an event-driven backbone orchestrating multiple LLMs, agents and MCP tooling, now serving 20+ enterprise clients. It is not a case study of a support agent I have shipped. Every dollar figure below is modelled arithmetic with its inputs printed, not an invoice.
The spine is the same nine-layer stack from the AI product architecture reference design. What changes in support is the weighting. Retrieval quality and the confidence gate carry almost all the product risk. The model router carries the cost lever. The tool layer carries all the blast radius, because a support agent is the one agent in your company with a plausible reason to touch a billing system.
One number frames the build. Published production resolution rates for the market-leading support agent cluster in the 42-53% band across real deployments, against a headline of 67% across Intercom's own 7,000+ customers. Plan against the lower band. A business case built on 70% deflection fails its first quarterly review.
| Component | What it does | Implementation | Cost per ticket | Primary failure mode | Skip in v1? |
|---|---|---|---|---|---|
| Ingest / normalise | Collapses email, chat, widget and API tickets into one record with stable identity and thread history | Your helpdesk webhooks plus a Postgres ticket table | ~$0 | The same customer opens three threads; the agent answers each without the others | No |
| Triage classifier | Intent, urgency, product area, route. Deterministic rules first, model second | Haiku-class model, ~2k in / 150 out, structured output | $0.0028 | Misroutes a billing dispute into the self-serve path and never escalates | No |
| Retrieval | Hybrid BM25 plus vector over docs, runbooks and resolved tickets, with recency and product-version filters | Postgres plus pgvector until you have measured a limit | $0.0002 amortised | A deprecated doc outranks the current one; the answer is fluent and wrong | No |
| Agent runtime | The loop, step and token budgets, tool validation, journal writes, no-progress detection | In-house loop over Sonnet-class model, 2-4 turns typical | $0.066 (3-turn resolve) | Oscillates between lookup tools for 40 turns on an ambiguous ticket | No |
| MCP tool gateway | Scoped read access to CRM, billing, orders; writes behind approval; per-agent allowlist | MCP servers behind a gateway, ~12 tools, ~1.8k tokens of schema per turn | In the turn cost above | A token scoped to the whole CRM lets the agent read another tenant's account | Gateway: no. MCP transport: often |
| Confidence gate | Groundedness check, citation-span validation, policy check, calibrated threshold | Cheap-model verifier plus deterministic span matching | $0.0035 | Threshold calibrated on a golden set that does not resemble live traffic | Absolutely not |
| Escalation brief | Structured handover: intent, facts checked, hypotheses ruled out, account state, suggested next action | One templated model call on the escalate path | $0.0045 | Brief is confidently wrong and the human trusts it, which is worse than no brief | No |
| Evals and traces | Deterministic CI suite, trajectory scoring, online judge on sampled traffic, cost attribution | OTel GenAI spans plus a run/step journal you own | $0.0006 (15% sample) | Golden set goes stale; suite passes while live deflection quietly drops | Deterministic: no. Judges: month two |
What does the full architecture look like?
Twelve boxes across four columns: ingest, triage and retrieval, agent and tools, and the gate that splits into answer or escalation. The load-bearing detail: the confidence gate sits between the agent and the customer, not inside the agent. A model cannot grade its own homework in the same call that produced it.
Read the diagram left to right and the product logic falls out. Tickets arrive on three channels and get normalised into one record, because an agent that cannot tell a chat message and an email are the same customer will answer both and contradict itself. A cheap classifier assigns intent and route before any expensive model runs. That single decision removes roughly 20% of tickets from the agent path, because password resets, order-status lookups and known-incident replies are deterministic flows that never needed a language model.
Retrieval draws from three corpora with very different characteristics. Public documentation is clean, structured and often out of date. Internal runbooks are accurate and full of language you must never show a customer. Resolved past tickets are the most valuable corpus and the messiest: the actual phrasing customers use, the actual resolutions that worked, and a long tail of wrong answers a previous agent gave that must not be recycled. Filtering past tickets to those with a positive CSAT and a confirmed resolution is not optional hygiene. It is the difference between a system that learns from success and one that launders old mistakes.
The MCP tool gateway is where the blast radius lives. A support agent has a legitimate reason to read a customer's subscription state, invoice history and open orders, which means it holds credentials that can read those things for every customer. Scope the token per request to the resolved tenant, validate every tool argument against a schema before dispatch, and bound every tool result at ingestion. A full invoice history serialised naively is a 40,000-token payload that blows your context window and your budget in one call. The full gateway pattern is in MCP in production.
Everything downstream of the agent is deliberately dull. The gate is a verifier plus deterministic span matching plus a threshold. The escalation path is a template. The journal is a Postgres table. Dull is the point: when a customer complains the agent told them something wrong, you answer what it read, what it called, what it cost and why it sent that, from a single SELECT.
What happens on one ticket, end to end?
Enqueue, classify, retrieve, two to three agent turns with one or two MCP tool calls, a grounding check, then either an answer with citations or an escalation brief. Roughly 9 to 14 seconds wall clock, 3 to 5 model calls, one row per step in a journal you own. Below is that sequence with tokens and dollars on every model arrow.
The ordering detail that matters most is where the CRM lookup happens. It is tempting to enrich every ticket with account context up front, because it makes the prompt richer and the demo better. Do not. Roughly 60% of tickets are answerable from documentation alone, and pre-fetching account state on all of them adds an external API call, a latency tail you do not control, and a privacy surface you have to justify, in exchange for context the agent never reads. Let the agent ask for it. The tool call is cheap. The unconditional enrichment is not.
The second detail: the idempotency key is written before the tool executes, not after. This is not academic. The small set of write tools (issue a credit, cancel a subscription, resend an invoice) are exactly the operations where a retried step becomes a customer-visible incident and a finance reconciliation. The key derives from the run id, the step index, the tool name and a canonical hash of the arguments, and the step row goes in first. The full pattern with the crash-window reconciliation branch is in the agent loop in production.
The third detail is the shape of the latency budget. Support is one of the few agentic products where the user is actively waiting, and the perceived budget is not the total. Stream a status line (checking your account, reading our billing docs) within 400 milliseconds and the 12-second run reads as attentive. Return nothing for 12 seconds and the same run reads as broken. That front-end decision changes your acceptable back-end budget by an order of magnitude, and it belongs in the architecture conversation, not the design review.
How does the agent decide when to answer and when to escalate?
A gate outside the agent that combines four independent signals: whether every factual claim maps to a retrieved span, whether a cheap verifier model agrees the answer is grounded, whether the ticket class is on the allowed-to-auto-answer list, and whether the agent used a write tool. Any one failing sends the ticket to a human.
Self-reported confidence is not one of the four, and that is deliberate. Ask a model how confident it is in the same call that produced the answer and you get a number that tracks fluency, not correctness. The signals that work are external and mostly deterministic: citation-span validation is string matching, ticket-class allowlisting is a lookup, write-tool usage is a boolean. Only the groundedness verifier is a model call, and it is a different model looking at the answer cold, without the reasoning that produced it.
The threshold is an operating decision, not a technical one, and it should be a configuration value a support leader can move. At 0.72 you get roughly 45% deflection with a low rate of wrong answers reaching customers. At 0.60 deflection rises toward 60% and so does the rate of confidently wrong answers. At 0.85 you deflect 25% and almost never embarrass yourself. Every support organisation sits at a different point on that curve. Let them choose it with the trade-off written on the slider, then re-measure monthly, because the curve moves when your docs change.
The escalation path deserves as much engineering as the answer path and usually gets none. A ticket that reaches a human queue with a brief (what the customer wants, what I checked, what I ruled out and why, their subscription state, what I would do next) is materially cheaper to handle than a raw one. Modelling that as 6 minutes down to 4.5 minutes of handle time is conservative, and at $22/hour fully loaded it is $0.55 of value for $0.0045 of model cost. That is the best ratio in the system, and it applies to 55% of tickets rather than 45%.
One warning next to the good news. A brief that is confidently wrong is worse than no brief, because a human under queue pressure trusts it. Every claim in the brief must carry the same citation discipline as the customer-facing answer, and the brief must visibly separate facts read from a system of record from inferences the agent made. Two visual styles, one rule: if it came from a tool, it is a fact; if it came from the model, it is a hypothesis.
import { z } from "zod";
export const AnswerSchema = z.object({
reply_markdown: z.string().min(1),
claims: z.array(z.object({
text: z.string(),
chunk_id: z.string(),
quoted_span: z.string().min(12),
})).min(1),
used_write_tool: z.boolean(),
ticket_class: z.string(),
});
export type Answer = z.infer<typeof AnswerSchema>;
const AUTO_ANSWERABLE = new Set([
"howto.product",
"billing.invoice_lookup",
"billing.failed_charge",
"account.settings",
"order.status",
]);
type Chunk = { id: string; text: string };
export type GateResult =
| { decision: "answer"; score: number }
| { decision: "escalate"; score: number; reason: string };
export async function gate(
answer: Answer,
chunks: Map<string, Chunk>,
verify: (a: Answer, c: Map<string, Chunk>) => Promise<number>,
threshold = 0.72,
): Promise<GateResult> {
// 1. deterministic: never auto-answer a class we have not approved
if (!AUTO_ANSWERABLE.has(answer.ticket_class)) {
return { decision: "escalate", score: 0, reason: "class_not_allowlisted" };
}
// 2. deterministic: any write tool means a human confirms the outcome
if (answer.used_write_tool) {
return { decision: "escalate", score: 0, reason: "write_tool_used" };
}
// 3. deterministic: every claim must quote a span that really exists
for (const claim of answer.claims) {
const chunk = chunks.get(claim.chunk_id);
if (!chunk) {
return { decision: "escalate", score: 0, reason: "citation_unknown_chunk" };
}
if (!normalise(chunk.text).includes(normalise(claim.quoted_span))) {
return { decision: "escalate", score: 0, reason: "citation_span_not_found" };
}
}
// 4. the only model call in the gate, on a cheap model, answer seen cold
const score = await verify(answer, chunks);
return score >= threshold
? { decision: "answer", score }
: { decision: "escalate", score, reason: "below_threshold" };
}
function normalise(s: string): string {
return s.toLowerCase().replace(/\s+/g, " ").trim();
}No model. A template and a database read. Roughly 20% of volume and it costs a fraction of a cent. Every ticket you remove here is a ticket you never have to make an agent safe for.
The 45% band. Gated at 0.72, cited, with a rating prompt attached so you get a live quality signal instead of a quarterly one.
Run the agent for the brief, never for the reply. You get the handle-time saving without the wrong-answer risk. This is the most under-used configuration in the category.
Triage routes it before any expensive model runs. Correct latency-to-human is zero and correct model spend is $0.0028.
A repeat contact is prima facie evidence the automated answer failed. Auto-answering it again is how a support incident becomes a public one.
What tools should the agent have, and how are they defined?
About twelve, of which nine are reads and three are writes behind human approval. Reads: customer lookup, subscription state, invoice history, payment method status, order status, shipment tracking, entitlement check, known-incident check, and past-ticket search. Writes: resend invoice, apply a bounded goodwill credit, and escalate with brief.
The count is a budget, not an aspiration. Every tool schema costs roughly 150 tokens on every turn, so twelve tools is about 1,800 tokens per turn, around 5,400 across a three-turn resolve, or $0.011 per ticket in pure catalogue before the agent does any work. Adding thirty more tools because they exist takes that to $0.038 per ticket and, worse, measurably degrades tool selection accuracy. If you need a large surface, defer the definitions and let the model discover them: Anthropic reports on-demand tool discovery cut context from 77,000 to 8,700 tokens in their own testing, an 85% reduction, while tool-selection accuracy rose.
The write tools need three properties the read tools do not. Each takes an explicit bound the agent cannot exceed: a goodwill credit tool that accepts an arbitrary amount will eventually issue an arbitrary amount. Each writes its idempotency key before dispatch. And each returns a receipt the human approver sees, not just a success boolean, because the person clicking approve needs to know what they are approving in the customer's terms, not the API's.
On transport: use MCP where you are integrating something you did not build, and plain typed function calls where you did. The July 2026 spec revision made this materially easier to operate. The 2026-07-28 spec removed the initialize handshake and the Mcp-Session-Id header from Streamable HTTP, so any server instance can serve any request behind an ordinary round-robin load balancer. That turns an MCP server from a stateful thing you have to route carefully into a plain HTTP workload. It also broke every client written against the old handshake, which is the general lesson about protocol dependencies in a fast-moving spec.
The tool definition below is the shape I would ship. Note three things absent from the naive version: the tenant is not a parameter the model can set, the result is bounded at the server, and the description tells the model when not to call it. That last line is worth more than most prompt engineering. A tool description that says do not call this for pre-purchase questions removes an entire class of wasted turn.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
const server = new McpServer({ name: "billing", version: "2.1.0" });
server.registerTool(
"get_subscription",
{
title: "Get subscription and recent billing state",
description:
"Returns the current plan, renewal date, payment method status and the " +
"three most recent invoices for the customer on this ticket. Call this " +
"when the customer asks about charges, renewals, plan limits or a failed " +
"payment. Do NOT call this for pre-purchase pricing questions - those are " +
"answered from documentation.",
inputSchema: {
customerId: z.string().describe("Customer id from the ticket record"),
includeInvoices: z.boolean().default(true),
},
},
async ({ customerId, includeInvoices }, extra) => {
// tenant is NOT a model-settable argument - it comes from the verified
// request context. This single line is the multi-tenant isolation story.
const tenantId = requireTenant(extra.requestInfo);
const sub = await billing.getSubscription({ tenantId, customerId });
if (!sub) {
return {
content: [{ type: "text", text: "No subscription found for that customer." }],
isError: false, // a 404 is information for the model, not an exception
};
}
const invoices = includeInvoices
? (await billing.listInvoices({ tenantId, customerId, limit: 3 }))
.map((i) => ({
id: i.id,
amount: i.amountMinor / 100,
currency: i.currency,
status: i.status,
failureCode: i.failureCode ?? null,
issuedAt: i.issuedAt,
}))
: [];
// bounded at the server. A 400-invoice customer must not be able to
// produce a 40k-token tool result.
const payload = {
plan: sub.planName,
status: sub.status,
renewsAt: sub.renewsAt,
paymentMethod: { brand: sub.pmBrand, last4: sub.pmLast4, valid: sub.pmValid },
seats: { used: sub.seatsUsed, purchased: sub.seatsPurchased },
invoices,
};
return {
content: [{ type: "text", text: JSON.stringify(payload) }],
structuredContent: payload,
};
},
);| Kind | Scope | Approval | Blast radius if wrong | |
|---|---|---|---|---|
| get_customer | read | tenant-scoped by request context | none | Wrong customer named in the reply — embarrassing, recoverable |
| get_subscription | read | tenant + customer | none | Wrong plan quoted; customer makes a decision on bad data |
| list_invoices | read, capped at 3 | tenant + customer | none | Context overflow if uncapped; this is why the cap is server-side |
| get_order_status | read | tenant + customer | none | Wrong delivery date quoted — the most common CSAT complaint |
| check_entitlement | read | tenant + customer + feature | none | Agent tells a customer they have a feature they do not |
| check_known_incidents | read | global, public status only | none | Missed incident means N duplicate tickets answered individually |
| search_past_tickets | read, CSAT-filtered | tenant only, PII-redacted | none | Leaks another customer's phrasing if redaction fails — the serious one |
| search_docs | read | product + version filtered | none | Deprecated doc outranks current; fluent wrong answer |
| resend_invoice | write, idempotent | tenant + customer | auto if same email on file | Duplicate email; annoying, not dangerous |
| apply_goodwill_credit | write, capped at $25 | tenant + customer | human approval always | Unbounded credits. The cap is in the schema, not the prompt |
| cancel_subscription | not exposed | n/a | n/a | Deliberately absent. Cancellation is a human conversation |
| escalate_with_brief | write, always allowed | tenant | none | A wrong brief that a rushed human trusts — style facts vs hypotheses |
What does one ticket actually cost?
About 7.3 cents when the agent resolves it, about 5 cents plus roughly $1.65 of human time when it escalates, against a baseline of about $2.20 for a fully human ticket. At 45% deflection over 10,000 tickets a month the blended cost is roughly $0.97 a ticket, a modelled saving of about $12,300 a month against an all-human baseline.
Work through the resolve path and the shape becomes obvious. Triage on a Haiku-class model is $0.0028. Retrieval is a rounding error at $0.0002 amortised. The three Sonnet-class agent turns are $0.0199, $0.0218 and $0.0244: $0.066 combined, or 90% of the total. The grounding verifier is $0.0035 and the sampled online judge adds $0.0006. Total $0.0732. Every number is the token count times the August 2026 list rate, and every token count is printed in the sequence diagram above.
The escalate path is cheaper in models and far more expensive in total. Triage plus two agent turns plus the brief is about $0.048, then a human spends the time. Modelled at $22/hour fully loaded and 4.5 minutes with a brief against 6 minutes without, the brief is worth about $0.55 and costs less than half a cent. That is the highest-leverage 100 lines of prompt in the system, and the part nobody demos because it has no customer-facing surface.
Now the part most build-versus-buy analyses skip. At 10,000 tickets a month, buying beats building. Intercom documents Fin at $0.99 per resolution; 4,500 resolutions is $4,455 a month, against a modelled build of about $6,580 a month once you amortise roughly nine engineer-weeks over 24 months and add three engineer-days a month of maintenance. The crossover is around 14,000 tickets a month. Below it, build is the worse financial decision and you should say so out loud.
Three reasons to build anyway, all non-financial. You need tools into systems no vendor will integrate with. You need the answer policy to be yours, versioned in your repo and auditable. Or support is your product surface rather than a cost centre, in which case the deflection curve is a product decision you cannot outsource. If none apply at your volume, buy it, instrument it properly, and revisit at 15,000 tickets. The general cost machinery (routing, caching, price snapshots) is in LLM routing and caching, cost per request.
| Line item | Model / rate | Tokens or units | Resolve path | Escalate path | Note |
|---|---|---|---|---|---|
| Triage classification | Haiku 4.5 · $1 / $5 per 1M | 2,000 in / 150 out | $0.00275 | $0.00275 | Runs on every ticket including the ones routed straight to a human |
| Hybrid retrieval | pgvector + BM25, amortised | 2 queries, 6k chunk tokens | $0.00020 | $0.00020 | Infrastructure amortised across volume, not a per-call API charge |
| Agent turn 1 | Sonnet 5 · $2 / $10 per 1M | 12.0k in (4.5k cached) / 400 out | $0.01990 | $0.01990 | Cached prefix = system + 12 tool schemas + few-shot |
| Agent turn 2 (after tool call) | Sonnet 5 | 13.2k in (4.5k cached) / 350 out | $0.02180 | $0.02180 | History plus the bounded 800-token tool result |
| Agent turn 3 (final answer) | Sonnet 5 | 14.0k in (4.5k cached) / 450 out | $0.02440 | n/a | Escalate path stops at two turns |
| Grounding + span verification | Haiku 4.5 | 3,000 in / 100 out | $0.00350 | n/a | Deterministic span check costs $0; only the verifier is a model call |
| Escalation brief | Sonnet 5 | 8.0k in / 550 out | n/a | $0.00450 | Worth ~$0.55 of saved handle time. Best ratio in the system |
| Online judge (15% sample) | Sonnet 5, sampled | 3.5k in / 200 out | $0.00060 | $0.00060 | Sampling rate is the knob; 15% is enough for a weekly trend |
| Model subtotal | — | — | $0.07315 | $0.04975 | Modelled from Aug 2026 list prices, not measured |
| Human handling | $22/hr fully loaded | 0 min vs 4.5 min | $0.00 | $1.65000 | Baseline without an AI brief is 6 min = $2.20 |
| Total per ticket | — | — | $0.073 | $1.700 | Blended at 45% deflection: $0.967 |
What breaks in production, and how would you know?
Eleven things, and the three most expensive all return a successful response. A confidently wrong answer, a stale document that outranks the current one, and a cache serving yesterday's policy all look like healthy 200s on every dashboard you own. No error rate moves. That is why a support agent needs quality instrumentation, not uptime instrumentation.
The failure-mode table below is the one I would put on the wall. Read the second column first, what the user sees, because it determines whether a failure is a ticket, an escalation or a screenshot on social media. A wrong delivery date is a CSAT hit. A leaked snippet of another customer's ticket is a breach notification, and it is a plausible outcome of an unredacted past-ticket index. That is why that row has its own hardened pipeline rather than a filter in a prompt.
Four signals catch most of it: deflection rate by ticket class, reopen rate on tickets the agent resolved, tool error rate broken out by tool, and cost per resolved ticket. Reopen rate is specific to this domain and the one most teams miss. A ticket the agent closed that the customer opens again within 72 hours is the cleanest available signal that the answer was wrong, and it needs no judge, no labelling and no eval set. Wire it on day one.
Alert on rates, never on individual tickets. An agent that gets one answer in three hundred wrong is a normal support organisation. An agent whose reopen rate moved from 6% to 14% overnight is an incident, usually caused by a documentation change or a prompt edit that broke no tests. Add a distribution alert on the confidence-gate score too: if the average gate score moves by more than a few points week over week, something upstream changed and you want to know before your deflection rate does.
The last row is the one people find hardest to accept. If you cannot answer, for a specific angry customer three weeks ago, exactly what the agent retrieved, which tools it called, what it cost and why the gate let it through, you do not have a production support agent. You have a demo with customers attached. That capability is one journal table and one query, and it is the difference between apologising with facts and apologising without them.
| Failure mode | What the user sees | Where to fix it | Detection signal | Cost of getting it wrong |
|---|---|---|---|---|
| Confidently wrong answer passes the gate | A fluent, cited reply that is factually false | Gate threshold and the citation-span validator; raise threshold and widen the class denylist | Reopen rate within 72h; thumbs-down rate; sampled judge | One CSAT hit per instance, plus a $2.20 human ticket you already paid $0.073 for |
| Deprecated doc outranks the current one | Instructions for a UI that no longer exists | Retrieval: version and recency filters at query time, not post-hoc reranking | Citation age distribution; share of citations to docs older than N months | Systemic — affects every ticket in that product area until fixed |
| Past-ticket index leaks another customer PII | A snippet naming someone else's account or email | Ingestion pipeline: PII redaction before embedding, not before display | Redaction test suite in CI; entity scan on the index, not the output | Breach notification. The one failure on this list that is not recoverable with an apology |
| Agent loops between lookup tools | Nothing for 90 seconds, then a timeout | Runtime: step budget of 4, plus a repeated-call hash detector that terminates into stalled | Step-budget-exceeded rate; distribution of turns per run | $0.30-$0.90 per stuck run and a customer who now needs two contacts |
| Tool returns an unbounded payload | Truncated or empty reply | MCP server: cap the result server-side; never trust the caller to bound it | Tool result size p99; context-overflow error rate | Blown context window, wasted turn, roughly $0.03 and one failed ticket |
| Over-scoped credential reads another tenant | Correct-looking answer about the wrong account | Tool gateway: tenant from verified request context, never a model parameter | Cross-tenant assertion in every tool test; audit log anomaly | Isolation breach. Treat as sev-1 even when the customer never notices |
| Triage misroutes a dispute into self-serve | A cheerful automated reply to a furious refund request | Triage: deterministic rules ahead of the model for regulated and emotional classes | Class distribution drift; sentiment-flagged tickets that were auto-answered | Escalation to a manager and, occasionally, a public screenshot |
| Cache serves a stale policy answer | Yesterday's refund window quoted after it changed | Cache key must include a docs-corpus version; invalidate on publish | Cache hit rate against corpus version; answers citing a superseded revision | Every cached hit is wrong until TTL expires — potentially thousands |
| Duplicate side effect on retry | Two credits applied, two invoices resent | Runtime: idempotency key persisted before the tool call, plus provider-side key | Duplicate idempotency_key insert attempts; finance reconciliation | Direct money loss plus a reconciliation task nobody owns |
| Escalation brief is confidently wrong | A human repeats the agent's mistake with authority | Brief template: cite tool-sourced facts, visually separate model hypotheses | Agent-disagreement rate — how often the human's resolution contradicts the brief | Worse than no brief. The failure that makes support teams distrust the whole system |
| Golden set goes stale | Nothing. Everything looks fine | Evals: re-mine the golden set from production traces monthly, stratified by failure class | Divergence between offline pass rate and live reopen rate | You lose the ability to tell a regression from a bad week |
- Deflection rate, broken out by ticket classAggregate deflection hides everything. A drop from 45% to 40% is usually one class collapsing, not a uniform decline.
- Reopen rate within 72 hours on agent-resolved ticketsThe single best quality signal in the domain. No judge, no labelling, no eval set. Wire it on day one.
- Confidence-gate score distribution, weeklyA shift in the distribution precedes a shift in deflection. It is your earliest warning that docs or prompts changed.
- Tool error rate and result-size p99, per toolPage above 20% error on any single tool. The p99 size catches the unbounded-payload failure before your context window does.
- Cost per resolved ticket, with a price snapshotStore cost as bigint micros with the rate card id. A vendor price change must not silently rewrite last quarter's report.
- Agent-disagreement rate on escalated ticketsHow often the human's final resolution contradicts the brief. Harder to instrument, and the only direct measure of brief quality.
What does it take to build, in engineer-weeks?
About nine engineer-weeks for a two-person team to a production-grade v1, plus three engineer-days a month of steady-state maintenance. The distribution is lopsided: the agent loop is roughly one week; the retrieval corpus, the confidence gate and evals are five. Anyone quoting this as a two-week build is quoting the demo.
Week one is ingestion and the journal: normalising three channels into one ticket record, the run and step tables, and cost attribution middleware with a price snapshot. It has no demo value and everything after it depends on it. Weeks two and three are the retrieval corpus, the phase underestimated by the widest margin. Chunking documentation is a day. Building the past-ticket pipeline with CSAT filtering, reopen exclusion and PII redaction is the rest.
Week four is the agent runtime with its budgets and journal writes. Week five is the MCP tool surface and the gateway: twelve tools, schema validation, tenant scoping and server-side result bounds. Weeks six and seven are the confidence gate and its calibration, which means building a labelled set from real tickets and measuring where the threshold sits on the deflection-versus-wrong-answer curve. Weeks eight and nine are evals, dashboards, the escalation brief and the work of shipping to a support team who did not ask for this.
Two things routinely get cut and both are mistakes. The first is the escalation brief, cut because it is invisible in a demo, despite applying to the majority of tickets and having the best cost-to-value ratio in the system. The second is threshold calibration, cut because an intuited number appears to work in testing. Then deflection is 30% instead of 45%, or wrong answers reach customers at four times the acceptable rate, and nobody can tell which because the curve was never measured.
To reach a working version faster, compress scope rather than quality: one channel, four ticket classes, six tools, a gate with a conservative threshold. That is closer to four weeks and it is a real system. The general approach to compressing a build like this is in how to build an MVP in days with AI, and the ongoing engineering is what AI product development is for.
- Week 1Ingest, journal, cost attribution
Three channels into one ticket record with thread merging. Run and step tables. Cost middleware with a price snapshot. Zero demo value, and everything else depends on it.
- Weeks 2‑3The retrieval corpus
Docs and runbooks are a day. The past-ticket pipeline (CSAT filtering, reopen exclusion, PII redaction before embedding, version and recency metadata) is the other nine. Most underestimated phase in the build.
- Week 4Agent runtime and budgets
The loop, step and token and wall-clock budgets, no-progress detection, validated tool calls, journal writes on every step. Smaller than people expect.
- Week 5MCP tool surface and gateway
Twelve tools, per-agent allowlist, tenant scoping from request context, schema validation, server-side result bounds, audit log. Two of them are writes and both need approval flows.
- Weeks 6‑7Confidence gate and calibration
Span validation, groundedness verifier, class allowlist, and the labelled set you need to actually place the threshold on the deflection-versus-wrong-answer curve. Cut this and you are guessing.
- Weeks 8‑9Evals, briefs, rollout
Deterministic CI suite, trajectory scoring, the escalation brief template, dashboards, and shipping to a support team that has to trust it. Shadow mode for two weeks before any customer sees an answer.
- Tickets handled by a human
- 10,000
- Average handle time
- 6.0 min
- Fully loaded cost per hour
- $22
- Model spend
- $0
- Monthly cost
- $22,000
- Cost per ticket
- $2.20
- Resolved by agent
- 4,500 at $0.073 = $329
- Escalated with a brief
- 5,500 at 4.5 min = $9,075
- Model cost on escalated tickets
- 5,500 at $0.050 = $274
- Infrastructure and observability
- $400
- Amortised build + maintenance
- $5,850
- Monthly cost
- $15,928 · $1.59 per ticket
What should you skip in version one?
Multi-agent orchestration, a dedicated vector database, autonomous write actions, and multilingual support. Each is a real capability and each is a month you spend before finding out whether your deflection rate clears 40%, the only question that matters in the first quarter.
Multi-agent first, because it is the most tempting. A triage agent, a research agent and a resolution agent looks right on a whiteboard and is almost always worse in practice: Anthropic's own guidance reports multi-agent implementations typically consume three to ten times the tokens of a single-agent approach for equivalent tasks. A support ticket is a short-horizon, single-domain task with a clear success criterion, the exact shape where one well-prompted agent with twelve tools wins. The decision procedure is in multi-agent versus single agent.
Skip the dedicated vector database. Postgres with pgvector handles a support corpus of a few million chunks comfortably and keeps your tenant ACLs in the same transaction as your data, which matters more here than anywhere else. Permission-aware retrieval in a multi-tenant support system is not a wrapper you can add later. The full treatment is in RAG over private documents.
Skip autonomous writes. Ship every write tool behind human approval in v1, measure how often the human approves without modification, and only then discuss automating the ones that clear 98%. The order matters: teams that ship autonomous writes first and add approval after an incident get a support organisation that no longer trusts the system, a much harder problem than the incident.
And skip multilingual until the monolingual deflection number is solid. Retrieval over a translated corpus, gate calibration per language and CSAT measurement per language are three separate projects wearing one hat. Do not skip: the journal, cost attribution, the confidence gate, PII redaction in the ticket index, and reopen-rate instrumentation. Those five are about two weeks of the nine and they are the difference between a system you can operate and one you can only apologise for.
- The run and step journal in your own Postgres, queryable with one SELECT
- Cost attribution with a price snapshot, stored as bigint micros
- The confidence gate, with the threshold calibrated on a labelled set from real tickets
- PII redaction before embedding in the past-ticket index, not before display
- Reopen-rate instrumentation, the cheapest quality signal in the domain
- Multi-agent orchestration: 3-10x tokens for equivalent tasks on Anthropic's own numbers
- A dedicated vector database: pgvector until you have hit a measured limit
- Autonomous write actions: approval first, automate what clears 98% approval-without-edit
- Multilingual: three projects wearing one hat, none until monolingual deflection is stable
Building an AI customer support agent: common questions
→How much does an AI customer support agent cost per ticket?
About 7.3 cents in model spend when the agent resolves a ticket end to end and about 5 cents when it escalates, against roughly $2.20 for a fully human ticket at $22/hour and 6 minutes of handle time. Blended at 45% deflection over 10,000 tickets a month, the all-in figure including human time and amortised build is about $1.59 per ticket. These are modelled figures from August 2026 list prices with the token counts printed, not measurements of a deployed system.
→What deflection rate should I plan for?
Plan for 40-50% and treat anything higher as upside. Independent write-ups put the production resolution rate for Intercom Fin, the most widely deployed system in the category, in the 42-53% band, against a published headline of 67% across 7,000+ customers. The gap comes from how resolution is counted and from knowledge base quality. Build the business case on the lower number, because a plan that needs 70% deflection fails its first review.
→Should I build this or buy an off-the-shelf support agent?
Buy it below roughly 14,000 tickets a month. Intercom documents Fin at $0.99 per resolution, which at 45% deflection is $4,455 a month on 10,000 tickets, against a modelled in-house cost of about $6,580 once you amortise nine engineer-weeks over 24 months and add maintenance. Build when you need tools into systems no vendor integrates with, when the answer policy must be versioned in your own repo for audit, or when support is your product surface rather than a cost centre.
→How do you stop the agent giving a confidently wrong answer?
Four independent checks outside the agent. Every factual claim must quote a span that is literally present in a retrieved chunk, which is a string-matching test rather than a model judgement. The ticket class must be on an approved auto-answer list. No write tool may have been used. And a separate cheap model, seeing the answer cold without the reasoning that produced it, must score it above a calibrated threshold. Self-reported confidence is deliberately excluded, because it tracks fluency rather than correctness.
→What tools does a support agent actually need?
About twelve: nine reads (customer, subscription, invoices, order status, entitlement, known incidents, past tickets, docs, shipment) and three writes, of which two sit behind human approval and one is escalation. Keep the count low, because every schema costs roughly 150 tokens on every turn, so twelve tools is about 1,800 tokens per turn and 5,400 across a three-turn resolve. Cancellation and large refunds should be deliberately absent: those are conversations, not API calls.
→How long does it take to build one?
About nine engineer-weeks for a two-person team to a production-grade v1, plus roughly three engineer-days a month of maintenance. The agent loop itself is about one week. The retrieval corpus is two, the confidence gate and its calibration are two, and evals plus rollout are two. A narrower version (one channel, four ticket classes, six tools, conservative threshold) is closer to four weeks and is a legitimate way to get a real deflection number before committing to the full build.
→Does the agent need access to write to the CRM or billing system?
Far less than teams assume. Resending an invoice and applying a bounded goodwill credit cover most of the legitimate write surface, and both should be capped in the tool schema rather than the prompt and gated behind human approval in version one. Cancellations, refunds above a threshold and account deletion should not be exposed at all. Measure how often a human approves a proposed write without modification, and only automate the classes that clear about 98%.