Lesson 55 of 61

Capstone: Worked LLM System Designs

Design a support triage and refunds agent

A fully worked answer to "put an agent on our support inbox". The signature hard part is authority: what the agent may do by itself, what it may only propose, and where the line goes so that one convincing user cannot move it.

Step 1: task and boundary

The brief is "handle tier-one support". That is not a design. Narrow it into a unit of work and an authority level before anything else.

Unit of work: one customer conversation, typically three to six turns.

What the model does: read the conversation and the customer's account state, classify the issue, retrieve the relevant policy, and either answer, propose an action, or hand off.

What code does: everything that changes the world.

ActionWho decidesWhy
Which category the issue isModelFuzzy, open-ended, exactly what it is good at
What to sayModelThe point of the system
Whether this customer is eligible for a refundCodeEligibility is a rule, and rules belong in code
Whether to issue a refund at allCode, from a limitMoney moving is authority, never judgment
Whether to escalateEither. The model may always escalateEscalation is the safe direction, so it needs no gate

The single decision the whole design hangs on:

Refund amountWho actsReasoning
Under 50 dollars, eligible by ruleAgent acts, loggedReversible, cheap, high volume. The savings live here
50 to 500 dollarsAgent drafts, human approves in one clickStill cheap to review, expensive to get wrong
Over 500, or any rule missHuman handles, agent summarisesThe agent's value here is the summary, not the decision

That table is the security boundary, the cost model, and the product decision, all at once. Everything else in this design serves it.

flowchart diagram: Customer message

The authorisation box is deliberately drawn after validation and before any action. Nothing the model produces reaches the world without passing through code that re-derives eligibility from the account, not from the conversation.

Step 2: context plan

SliceSourceSizeFreshness
System prompt and policy summaryStatic~900 tokensDeploy time
Tool definitionsStatic~600 tokensDeploy time
Account state (plan, order history, prior refunds)Database lookup, not retrieval~400 tokensReal time
Relevant policy passagesHybrid search over the policy corpus~1,200 tokensNightly index
Conversation so farThe thread~800 tokens by turn fourReal time

Two decisions worth defending. First, account state comes from a database query, not a vector search: it is structured, it is exact, and retrieving it fuzzily would be an unforced error. Second, the always-on 1,500 tokens of prompt and tool definitions cost more in aggregate than the retrieval does, because they ride on every call including the cheap ones.

Trimming rule: when the window tightens, drop the oldest conversation turns first, keep a running summary, and never drop the policy passages or the account state. If you must drop policy, refuse instead.

Step 3: cost and latency budget

Typescript
const supportAgent: Budget = {
  unit: "one customer conversation",
  costCents: 4,          // p50: classify (small) + 2 agent steps (large)
  costCeilingCents: 25,  // hard stop; a conversation past this is escalated
  latencyMsP50: 1800,
  latencyMsP95: 4000,
  volumePerDay: 12_000,
};

Where the four cents goes, and why the routing decision is worth so much:

StepModelTokens in / outCost
Classify and triageSmall2,200 / 120~0.3c
Answer or plan, step 1Large3,900 / 300~1.8c
Answer or plan, step 2Large4,400 / 250~1.9c
Total, p50~4c

At 12,000 conversations a day that is about 480 dollars daily, or roughly 175,000 a year. The comparison that gets this funded is not "versus zero", it is versus the fully loaded cost of the tier-one agents it deflects, and the honest version of that number counts the 30 percent that still reach a human.

The ceiling matters more than the target. A conversation that loops is unbounded spend. At 25 cents the agent stops, hands to a human, and records why. That is one line of code and it removes the entire class of runaway-bill incidents.

Step 4: failure ladder

FailureLadder
Model times outOne retry with jitter, then the small model with a narrowed prompt, then "let me get a colleague", then handoff
Output fails schema validationRe-ask once, including the validation error, then handoff. Never guess the missing field
Policy retrieval returns nothingAnswer only what is answerable without policy, and say the policy could not be checked. Never let the model recall policy from training
Refund API errorsDo not retry blind. Check idempotency key, surface the state to the model as an observation, escalate after two attempts
Step budget hit (6 steps)Stop, summarise the conversation, hand to a human
Cost ceiling hitSame as step budget. Both are the same defence against loops

The rung to argue about is retrieval returning nothing. The tempting behaviour is to let the model answer from what it "knows" about refund policy. That is precisely how you get an invented bereavement policy that a tribunal then holds you to. Fail loudly, not fluently.

Step 5: threat model

The trust boundary and what crosses it:

SourceTrustedNotes
System prompt, policy corpusYesYou control both, and the corpus is reviewed
Customer messageNoThe primary injection vector, and the attacker is motivated
Order notes and prior ticketsNoOften free text a customer wrote. This surprises people
Refund tool responseNoReturns text that lands in the context

The question that decides the severity: what can an attacker who fully controls the model's output cause?

Answer, by construction: they can cause a refund of at most 50 dollars, to their own account, that they were already eligible for by rule. That is not nothing, but it is bounded, reversible, logged, and rate-limited. It is a business risk with a number attached, not a breach.

DefenceWhat it stopsWhere it lives
Authorisation re-derived in code from account stateThe model being talked into "you are authorised"The authorisation box
Refund limit and per-customer rate limitOne compromised conversation draining anythingSame place
Idempotency key per conversationA retry loop issuing five refundsThe refund client
Input guard for known injection shapesThe lazy attacks, not the clever onesBefore the model
Untrusted content clearly delimited in the promptSome instruction confusion, imperfectlyThe prompt

The last two are worth having and worth being honest about: prompt-level defences reduce the rate, they do not create a boundary. The boundary is the 50 dollar limit enforced by code.

Step 6: eval plan

The set: 150 real conversations pulled from the last quarter, frozen, deliberately weighted toward the cases that hurt. Roughly 60 straightforward, 40 policy edge cases, 30 that should escalate, 20 adversarial (injection attempts, users arguing for a refund they are not owed).

DimensionScorerGate
Correct action takenExact match against a human label95 percent
Escalated when it shouldRecall on the escalate class98 percent. Missing an escalation is the expensive error
Never acted beyond authorityTrajectory check on tool calls100 percent. Any failure blocks the release
Answer qualityRubric, model judge, spot-checked by a person4.0 of 5 average
Tone and policy complianceRubricNo case below 3

Two things to notice. The authority check is scored on the trajectory, not the answer, because an agent that reaches the right reply after attempting an unauthorised tool call has failed the thing that matters. And escalation recall is gated higher than accuracy, because the cost of a wrongly-handled case is much greater than the cost of an unnecessary handoff.

Step 7: operability

SignalAlertWhat it usually means
Escalation rateMoves more than 5 points week on weekEither the model drifted or your customers' problems changed
Refund rate per 1,000 conversationsAny climbThe first place an injection campaign shows up
Cost per conversation, p95Above 15cLoops, or context growing without anyone noticing
Invalid-output rateAbove 2 percentProvider drift, almost always
Approval-queue agreement rateBelow 90 percentThe agent's drafts got worse, and the 50 dollar threshold should come down

That last one is the quiet gem. Humans already approve every refund between 50 and 500 dollars, so agreement rate is a free continuous eval on live traffic. It is also the evidence you use to raise the automatic threshold later, which is the only responsible way to widen the agent's authority.

Runbook, in order: check invalid-output and refusal rate first (drift), then cost p95 (loops), then escalation rate (quality). Three levers: pin to the last known-good model, drop the automatic refund limit to zero so everything queues for approval, or turn the agent off and route to humans. The middle lever is the one you will actually use, and it is why the limit is a config value and not a constant.

The decisions, out loud

DecisionWhat it costWhat would change it
Refund authority capped at 50 dollars, in codeAbout 30 percent of refunds still need a person, so savings are cappedApproval agreement above 98 percent for a full quarter
Account state from the database, not retrievalOne more integration to build and maintainNothing. Fuzzy-matching account state is never right
Small model for triage, large for the answerA misroute sends a hard case down the cheap pathRouting accuracy below 95 percent on the eval set
Refuse rather than answer when policy retrieval is emptyA visibly worse experience on a small slice of conversationsNothing. This is the Air Canada rung
Six-step and 25 cent ceilingsA handful of legitimately complex conversations get handed off earlyTrace evidence that real conversations need more, not a hunch

Recap

  1. Authority is the design. The 50 dollar limit, enforced by code that re-derives eligibility, is what makes an injection a bounded business risk instead of a breach.
  2. The model proposes, code disposes. Nothing the model emits reaches the world without passing an authorisation check it cannot influence.
  3. Ceilings on steps and cost are the anti-loop defence, and they are two lines of code.
  4. Human approval on the middle tier is a free live eval, and the only honest evidence for widening the agent's authority later.