DEV Community

Cover image for An AI Phone Agent Needs a Routing Contract, Not an Intent Label
isabelle dubuis
isabelle dubuis

Posted on Originally published at vocalis-ai.org

An AI Phone Agent Needs a Routing Contract, Not an Intent Label

An AI Phone Agent Needs a Routing Contract, Not an Intent Label

An AI phone agent can identify why somebody is calling and still send that person to the wrong place.

That distinction matters. Intent classification answers a linguistic question: what does this caller appear to want? Routing answers an operational question: what is the safest useful next destination, under the conditions that exist right now? A model may correctly hear “I need to change my address” while missing that the caller has not passed identity verification. It may recognise “billing problem” while ignoring that the billing queue is closed. It may detect “cancel my order” while using a tool whose permissions are broader than the agent's mandate. It may transfer a caller who uses text relay into an audio-only callback process.

The result is not merely an inaccurate label. It is a broken service decision.

This article adapts the operating context described in Vocalis AI's overview of an AI phone agent, then takes a different architectural angle: the phone agent should not own routing as an improvised conversational act. It should submit a proposed route to a deterministic routing contract. That contract evaluates eligibility, permissions, queue state, accessibility, identity, transfer certainty and recovery before any destination is selected.

The design question is therefore:

Can the system prove that a destination is admissible now, and can it recover without abandoning the caller if that destination fails?

If the answer is no, the agent should not route there—even when its intent prediction is excellent.

The decision in one paragraph

Treat the language model as a proposer, not the routing authority. Let it extract a structured call state: probable intent, confidence, entities, requested action, caller preference and unresolved ambiguity. Pass that state to a small policy engine that has current queue data and explicit rules. The engine returns one of four outcomes: route, clarify, offer an asynchronous alternative, or escalate to a controlled human fallback. It must also attach reason codes, a transfer protocol and a recovery path. This separation makes routing testable and prevents a plausible sentence from becoming an unbounded business action.

Why intent accuracy is the wrong acceptance metric

An intent classifier is usually evaluated against labelled utterances. If 94 of 100 test calls receive the expected category, the team may celebrate a 94% score. That number is useful, but it does not answer the operational questions that determine whether callers succeed:

  • Was the chosen destination open and staffed?
  • Was the destination permitted to receive the disclosed data?
  • Did the caller meet the identity threshold for the requested action?
  • Could the destination accommodate the caller's communication method?
  • Did the receiving person or system acknowledge the transfer?
  • If the transfer failed, did the caller retain context and a recovery option?
  • Did the system distinguish advice, information, and a binding transaction?

Imagine 1,000 calls in which the classifier is correct 95% of the time. If 10% of the correctly classified calls are sent to a closed queue, 5% lose context during transfer, and 3% require an accessibility path the destination cannot support, the headline classification score conceals a substantial service failure. These rates should not simply be added because the groups can overlap. That is precisely the point: a routing decision is a conjunction of constraints, not a single probability.

A useful abstraction is:

[
\text{Admissible}(d)=I_d \land P_d \land Q_d \land H_d \land A_d \land V_d \land T_d
]

For destination (d):

  • (I_d): intent and scope fit;
  • (P_d): tool and data permissions are sufficient but not excessive;
  • (Q_d): current queue capacity is acceptable;
  • (H_d): service hours and calendar constraints permit the route;
  • (A_d): accessibility and language needs can be met;
  • (V_d): identity or verification level is sufficient;
  • (T_d): transfer acknowledgement and recovery are available.

If any mandatory term is false, the route is inadmissible. Confidence does not override policy. A score of 0.99 for intent cannot turn a closed queue into an open one or grant permission that the workflow does not have.

Build the routing contract before the prompt

A routing contract is a versioned specification for what each destination accepts. It is deliberately more boring than a prompt. Boring is useful here: operations teams can inspect it, security teams can restrict it, and test suites can exercise it without waiting for a model to produce the same wording twice.

The minimum contract should describe the following fields.

Contract field Question it answers Example evidence Failure response
Scope What requests may enter this route? intent allow-list, excluded actions clarify or human triage
Identity level How much verification is required? none, account match, strong verification verify before transfer
Data class What information may cross the boundary? public, account, financial, sensitive redact or choose safer route
Tool permission What may the receiver read or change? read order, reschedule, refund limit deny action, do not widen access
Availability Is the route operating now? hours, holidays, incident flag callback slot or alternate queue
Capacity Can it accept another call? wait estimate, overflow threshold offer queue choice or callback
Accessibility Can it support the caller's channel? relay, TTY, language, slower pacing accessible destination
Transfer protocol How is context handed over? warm transfer, acknowledgement token retain caller and recover
Completion proof What confirms success? receiver accepted, case ID created retry or escalate
Retention What is stored, where, and for how long? transcript policy, audit event minimize or suppress data

The contract also needs ownership. “Billing queue” is not an owner. A named operational role must approve its scope, availability rules, escalation path and changes. Otherwise routing policy drifts as teams add destinations, tools and exceptions.

The model proposes a call state, not a destination

The model's output should be a constrained object rather than a natural-language instruction such as “transfer to billing.” A useful call state might include:

{
  "intent": "billing.dispute",
  "intentConfidence": 0.86,
  "requestedAction": "explain_charge",
  "identityLevel": "account_match",
  "dataClasses": ["account", "billing"],
  "language": "en",
  "accessibility": ["text_relay"],
  "urgency": "normal",
  "ambiguities": ["merchant_name_unclear"],
  "callerPreference": "human"
}
Enter fullscreen mode Exit fullscreen mode

That object is still untrusted. Speech recognition may be wrong, the caller may change their mind, and a hostile instruction may appear in a retrieved record. The router validates the schema, normalises values, rejects unsupported fields and then applies policy. No free-form model text should become a queue name, tool identifier, telephone number or permission grant.

This boundary responds to a broader agent risk. The OWASP Top 10 for Large Language Model Applications project includes excessive agency among the security risks it teaches teams to examine: excessive functionality, permissions or autonomy can turn unexpected or manipulated model output into consequential action. OWASP is a security community source, not evidence that any particular call-centre product is unsafe. The relevant design lesson is narrower: routing authority should be smaller than conversational capability.

Separate four types of decision

Phone systems often compress distinct decisions into one phrase—“handle the call.” A stronger design separates them.

1. Understanding

What does the caller appear to mean? This is where speech recognition, entity extraction and intent classification help. Ambiguity belongs in the output, not behind a confident label.

2. Eligibility

Which destinations are allowed to receive this request and these data? This is deterministic policy. A sales queue may discuss public plans but not see an existing customer's payment history. A scheduling workflow may offer times but not provide professional advice.

3. Selection

Among eligible destinations, which one is best now? This stage may consider wait time, language, caller preference, staff skill and workload. Optimisation is permitted only inside the admissible set.

4. Commitment

Did the receiving destination accept responsibility? A dial attempt is not a successful transfer. The system needs an acknowledgement: an agent answered, a callback was booked, a case was created, or a transaction returned a durable identifier.

Separating these decisions prevents a common failure: using an optimisation score to excuse an eligibility violation. The fastest queue is irrelevant if it cannot lawfully or operationally handle the case.

Accessibility is a routing input, not a post-call accommodation

Accessibility requirements can change the admissible destination. They should be captured early, with caller control, and passed as minimal structured preferences rather than inferred medical details.

Ofcom's official guide explains that text relay enables people with hearing or speech difficulties to communicate by combining typed text, voice and a relay assistant. It also notes the pauses and turn-taking involved in relay calls and describes provider obligations in the United Kingdom (Ofcom text relay guide). This is a UK regulatory example, not a universal rule for every jurisdiction. Operationally, it demonstrates why “telephone available” is not a sufficient capability flag. A destination may answer voice calls yet fail if its timeout, interrupt handling or callback process assumes rapid audio turn-taking.

The contract can represent accessibility without over-collecting:

  • supportsTextRelay: true;
  • allowsExtendedSilence: true;
  • callbackChannels: ["voice", "relay"];
  • languages: ["en", "fr"];
  • humanRequested: true.

Do not have the model diagnose why a preference exists. The router needs the operational requirement, not an inferred condition.

Capacity must be current, bounded and honest

Static business hours are necessary but insufficient. A queue can be open and unavailable because of an incident, a sudden backlog or a missing specialist. Capacity data should therefore carry both a value and freshness metadata:

{
  "queue": "billing-specialist",
  "status": "open",
  "estimatedWaitSeconds": 740,
  "acceptingTransfers": true,
  "observedAt": "2026-10-20T07:45:00+02:00",
  "ttlSeconds": 30
}
Enter fullscreen mode Exit fullscreen mode

If the observation is older than its time-to-live, the router must treat capacity as unknown, not silently reuse an optimistic value. Unknown may permit a cautious attempt when recovery is strong, or require a callback offer when a failed transfer would abandon the caller.

Honesty also matters in the conversation. The system should not promise “someone will answer in five minutes” from a volatile estimate. It can say that the current estimate is approximately twelve minutes and offer alternatives. The caller's selection becomes a routing constraint, not decorative feedback.

Identity gates belong before privileged routes

The requested action determines the necessary verification level. General opening hours may require none. Reading an account balance may require an account match. Changing payment details may require stronger verification and perhaps a human-controlled step.

The routing contract should compare identityLevel with requiredIdentityLevel before disclosing the destination's context or invoking a tool. The system should not transfer sensitive context first and ask the receiving queue to repair the verification gap later.

A simple ordered scale is often enough:

  1. anonymous — public information only;
  2. contact_match — low-risk personalised service;
  3. account_match — account-specific information;
  4. strong_verified — high-impact change;
  5. human_reviewed — exceptional or regulated decision.

The names are implementation choices, not universal assurance standards. What matters is that actions and routes declare a minimum, upgrades occur through an approved workflow, and a model cannot claim that an identity check happened.

Transfer certainty changes the definition of success

A cold transfer that disconnects after forty seconds is not a completed route. The original agent should retain responsibility until the receiver acknowledges the handoff or the caller explicitly accepts an asynchronous alternative.

Use a two-phase transfer:

  1. Prepare. Create a minimal handoff packet and request capacity from the destination.
  2. Commit. Connect only after the destination returns an acceptance token; otherwise return to recovery.

The handoff packet should contain the minimum useful context: verified caller reference, approved intent, completed checks, caller preference and a concise summary. It should not include an unrestricted transcript by default. The destination confirms what it received, and the system records a correlation ID.

flowchart TD
    A[Call state proposed] --> B{Schema valid?}
    B -- No --> C[Clarify or human triage]
    B -- Yes --> D[Enumerate contract destinations]
    D --> E{Scope and permission fit?}
    E -- No --> C
    E -- Yes --> F{Identity sufficient?}
    F -- No --> G[Approved verification step]
    G --> F
    F -- Yes --> H{Hours capacity accessibility valid?}
    H -- No --> I[Offer admissible alternative or callback]
    H -- Yes --> J[Prepare minimal handoff]
    J --> K{Receiver acknowledges?}
    K -- Yes --> L[Commit transfer and record proof]
    K -- No --> M[Retain caller context]
    M --> I

This flow deliberately includes refusals. A router that always produces a destination is not robust; it is hiding uncertainty.

An executable policy core

The following JavaScript example is intentionally small. It shows the decisive boundary: the model supplies a proposed state, while ordinary code determines whether a destination is admissible. Save it as router.mjs and run it with a recent Node.js version.

const rank = {
  anonymous: 0,
  contact_match: 1,
  account_match: 2,
  strong_verified: 3,
  human_reviewed: 4,
};

const destinations = [
  {
    id: "billing-general",
    intents: ["billing.question", "billing.dispute"],
    actions: ["explain_charge"],
    requiredIdentity: "account_match",
    dataClasses: ["account", "billing"],
    accessibility: ["voice", "text_relay"],
    acceptingTransfers: true,
    capacityObservedAt: Date.now(),
    capacityTtlMs: 30_000,
    waitSeconds: 420,
    permissions: ["billing:read"],
  },
  {
    id: "billing-callback",
    intents: ["billing.question", "billing.dispute"],
    actions: ["explain_charge"],
    requiredIdentity: "account_match",
    dataClasses: ["account", "billing"],
    accessibility: ["voice"],
    acceptingTransfers: false,
    callbackAvailable: true,
    capacityObservedAt: Date.now(),
    capacityTtlMs: 60_000,
    waitSeconds: 0,
    permissions: ["billing:read"],
  },
];

function subset(required, available) {
  return required.every((item) => available.includes(item));
}

function evaluate(call, destination, now = Date.now()) {
  const reasons = [];

  if (!destination.intents.includes(call.intent)) reasons.push("INTENT_OUT_OF_SCOPE");
  if (!destination.actions.includes(call.requestedAction)) reasons.push("ACTION_OUT_OF_SCOPE");
  if (rank[call.identityLevel] < rank[destination.requiredIdentity]) reasons.push("IDENTITY_TOO_LOW");
  if (!subset(call.dataClasses, destination.dataClasses)) reasons.push("DATA_BOUNDARY_MISMATCH");
  if (!subset(call.accessibility, destination.accessibility)) reasons.push("ACCESSIBILITY_UNSUPPORTED");
  if (now - destination.capacityObservedAt > destination.capacityTtlMs) reasons.push("CAPACITY_STALE");
  if (!destination.acceptingTransfers && !destination.callbackAvailable) reasons.push("UNAVAILABLE");

  return { id: destination.id, admissible: reasons.length === 0, reasons };
}

function route(call) {
  if (call.intentConfidence < 0.75 || call.ambiguities.length > 1) {
    return { outcome: "clarify", reason: "LOW_CONFIDENCE_OR_AMBIGUITY" };
  }

  const evaluations = destinations.map((d) => ({
    ...evaluate(call, d),
    destination: d,
  }));

  const admissible = evaluations
    .filter((item) => item.admissible)
    .sort((a, b) => a.destination.waitSeconds - b.destination.waitSeconds);

  if (admissible.length === 0) {
    return {
      outcome: "human_triage",
      reason: "NO_ADMISSIBLE_DESTINATION",
      evidence: evaluations.map(({ id, reasons }) => ({ id, reasons })),
    };
  }

  const selected = admissible[0].destination;
  return {
    outcome: selected.acceptingTransfers ? "prepare_transfer" : "offer_callback",
    destinationId: selected.id,
    requiredPermissions: selected.permissions,
  };
}

const call = {
  intent: "billing.dispute",
  intentConfidence: 0.86,
  requestedAction: "explain_charge",
  identityLevel: "account_match",
  dataClasses: ["account", "billing"],
  accessibility: ["text_relay"],
  ambiguities: ["merchant_name_unclear"],
};

console.log(JSON.stringify(route(call), null, 2));
Enter fullscreen mode Exit fullscreen mode

The first destination is admissible because it supports text relay. The callback destination is excluded because it supports voice only. The example does not place a real call, authenticate a person or prove legal compliance. It demonstrates a policy seam that can be unit-tested. Production code also needs signed configuration, concurrency control, time zones, holiday calendars, incident state, privacy controls and a real transfer protocol.

Test the contract with counterexamples, not just happy paths

An acceptance suite should include calls that must not route. Teams often create dozens of friendly examples for each intent and only a few adversarial or operational cases. Reverse that imbalance for high-impact routes.

Test Expected result Evidence to retain
Correct intent, insufficient identity verification step, no disclosure policy reason code
Correct intent, queue closed callback or alternate admissible route hours snapshot
Correct intent, stale capacity treat capacity as unknown observation timestamp
Correct intent, unsupported relay need accessible route or human triage capability match
Ambiguous intent, high-impact action clarify before tool use ambiguity fields
Prompt injection in account note ignore instruction, preserve data boundary input provenance
Receiver does not acknowledge keep caller, start recovery transfer timeout event
Caller changes preference recompute route updated consent/preference
No destination admissible honest refusal plus next step full reason-code set
Duplicate transfer request one committed handoff idempotency key

Tests should run against policy versions, not only model versions. A new queue, permission or holiday rule can break routing even when the model has not changed.

Measure outcomes that expose routing quality

Containment—the proportion of calls completed without a human—is tempting because it appears efficient. It can also reward the wrong behaviour. A system may increase containment by preventing callers from reaching the specialist they need.

Use a balanced scorecard:

  • admissible-route rate: share of attempted routes that met every mandatory contract condition;
  • first useful destination: share reaching a destination able to resolve the request without another transfer;
  • transfer acknowledgement: share with an explicit receiver commitment;
  • recovery completion: share of failed transfers that produce an accepted alternative;
  • accessibility match: share whose stated channel requirements were supported;
  • permission denials: count and rate of prevented over-privileged actions;
  • repeat-contact window: callers returning for the same unresolved need;
  • human override rate: how often staff correct the proposed route, segmented by reason;
  • caller effort: clarifications, transfers and repeated explanations;
  • policy unknowns: decisions made with stale or missing operational data.

Segment metrics by intent, destination, language, accessibility path and time period. Averages can hide a small route that fails nearly every evening. Also review samples manually. Logs show what the software recorded; they do not automatically prove that the caller's problem was solved.

Governance is part of the architecture

Routing spans model behaviour, contact-centre operations, identity, security, privacy and workforce design. It cannot be owned exclusively by the team that writes prompts.

EY's description of architecting an agentic workforce frames agent deployment as a combination of technical architecture, governance and operating-model change (EY, “Architecting an agentic workforce”). That is a consulting perspective, not independent proof of a particular return on investment. It is useful here because a routing contract fails if organisational ownership is absent: somebody must maintain destinations, approve permissions, respond to incidents and review exceptions.

A practical ownership map is:

  • contact-centre operations owns hours, capacity and transfer recovery;
  • service owners define scope and completion proof;
  • identity and security teams approve verification and permissions;
  • accessibility owners validate channel capability with real users;
  • privacy owners approve data classes, retention and handoff content;
  • model owners maintain extraction quality and uncertainty thresholds;
  • an incident owner can disable a route without editing prompts.

Changes should be reviewed like code. A contract update needs a diff, approver, effective date, rollback version and tests. Emergency disablement must be fast, but restoration should still require evidence.

Common failure modes and their repairs

The destination is generated as text

Failure: the model invents a queue name or telephone number.

Repair: destinations are immutable identifiers from an allow-listed contract registry. The model never supplies transport coordinates.

Confidence becomes permission

Failure: a high intent score bypasses identity or scope restrictions.

Repair: confidence controls whether to clarify; it never raises authority.

Queue data has no freshness limit

Failure: yesterday's “open” status routes today's callers into silence.

Repair: every operational observation has a timestamp and TTL. Stale means unknown.

The transfer is counted when dialling starts

Failure: dashboards report success even when nobody accepts the call.

Repair: measure prepare, acknowledge and commit as separate events. Responsibility remains with the origin until commit.

Accessibility is inferred from voice

Failure: the model guesses a condition or overlooks a relay workflow.

Repair: ask for operational preferences when relevant, minimise the data, and match declared capabilities.

The fallback queue receives everything

Failure: one human queue becomes an unbounded safety valve, creating long waits and hidden risk.

Repair: give fallback its own contract, capacity threshold and overflow plan. “Human” is not the same as “unlimited.”

Full transcripts cross every boundary

Failure: receivers gain irrelevant personal data and prompt-injection content.

Repair: hand over a structured minimum, preserve provenance, and retrieve details only through authorised tools.

Policy has no refusal experience

Failure: when no route is admissible, the agent loops or fabricates certainty.

Repair: design refusal language and alternatives in advance: explain the limitation, preserve context, offer a controlled callback or provide verified public information.

A staged implementation plan

Stage 1: Observe

Run the proposed router in shadow mode. It records what it would have selected without changing live calls. Compare its eligibility decisions with current routes and investigate disagreements. Do not interpret agreement as proof; both systems can be wrong.

Stage 2: Recommend

Show the admissible set and reason codes to human agents. Require them to select the destination and capture override reasons. This reveals missing contract fields and operational exceptions.

Stage 3: Automate low-impact routes

Enable routes involving public information or reversible administrative actions, with strong transfer recovery. Keep narrow limits and review samples.

Stage 4: Expand by evidence

Add routes only when their failure modes, identity requirements, permissions, accessibility capabilities and recovery tests pass. Expansion should follow demonstrated control, not pressure to raise containment.

Stage 5: Exercise failure

Simulate closed queues, stale capacity, rejected transfers, tool outages and malformed model output. Confirm that the caller receives an honest next step and that incident controls can disable a destination quickly.

The proof pack for each release

A routing release should create a compact evidence bundle:

Evidence Purpose
policy version and signed diff proves what changed
destination owner approvals establishes accountability
scope and permission matrix prevents authority drift
accessibility validation proves supported paths were exercised
identity/action matrix links verification to impact
counterexample test results demonstrates refusal behaviour
transfer recovery exercise verifies failure does not abandon callers
sampled-call review checks log claims against experience
metric baseline and alert thresholds makes degradation visible
rollback and route-disable drill proves reversibility

The pack should avoid recording unnecessary caller data. Evidence of control is not an excuse to retain complete conversations indefinitely.

Release checklist

Before enabling an AI phone route, confirm:

  • [ ] The model emits a validated call-state schema, not a destination address.
  • [ ] Every destination has a named operational owner.
  • [ ] Scope, excluded actions and data classes are explicit.
  • [ ] Identity requirements are tied to requested actions.
  • [ ] Tool permissions are least-privilege and cannot be expanded by model text.
  • [ ] Hours, incidents and capacity include freshness limits.
  • [ ] Language and accessibility capabilities are represented as routing constraints.
  • [ ] Caller preference can change the selected route.
  • [ ] Handoff context is minimal, structured and provenance-aware.
  • [ ] The receiver must acknowledge before transfer commit.
  • [ ] Failed transfer recovery retains the caller and context.
  • [ ] No-admissible-route behaviour is honest and useful.
  • [ ] Counterexamples cover ambiguity, stale data and permission denial.
  • [ ] Metrics include resolution, recovery and caller effort—not containment alone.
  • [ ] Policies are versioned, reviewable and reversible.
  • [ ] A human can disable a problematic route without changing the prompt.

What this architecture cannot guarantee

A routing contract does not make speech recognition infallible. It does not prove that human staff will resolve the problem, that a queue estimate will remain accurate, or that a caller will accept the proposed alternative. A rules engine can also encode bad policy consistently. Accessibility flags do not replace testing with people who use the relevant services. Audit events can be incomplete or misleading if the implementation records intent rather than confirmed outcomes.

There is also a real trade-off. More gates can increase clarification and transfers. For low-impact requests, excessive ceremony harms callers without reducing meaningful risk. The answer is not to remove the contract; it is to calibrate requirements by action impact, make low-risk routes simple and keep high-impact routes appropriately bounded.

Finally, neither an OWASP risk page, an Ofcom guide nor a consulting architecture article validates a vendor implementation. They inform discrete parts of the design. The organisation still has to test its own routes, permissions, data flows, jurisdictions and caller experience.

The architectural conclusion

An intent label is a hypothesis about language. A route is a commitment by an operating system.

The reliable design keeps those facts separate. The model proposes a structured interpretation. A versioned contract decides which destinations are admissible. Current operational data chooses within that set. A two-phase transfer proves that somebody accepted responsibility. Recovery keeps the caller from falling through the gap.

That design may occasionally reduce automation and increase clarification. It is still the better system. The objective is not to make the phone agent sound certain or to maximise containment. The objective is to make every route explainable, authorised, current, accessible and recoverable—and to refuse the wrong queue before the caller pays for the mistake.

Top comments (1)

Collapse
 
arcwrightai profile image
arcwrightai •

The trust question is the one nobody wants to answer. Capability is easy to demo, reliability is not.