By Sagar Shankaran, Founder of CallSphere
Build robust fallback systems for chat agents that detect out-of-scope questions, provide helpful redirects, escalate to humans intelligently, and learn from failures to continuously improve coverage.
Key takeaways
No chat agent can answer every question. Even the most capable AI agent has a defined scope — it handles product questions, support tickets, or lead qualification, not all three perfectly. The quality of a production agent is measured not just by how well it handles in-scope questions, but by how gracefully it handles out-of-scope ones.
A bad fallback experience sounds like: "I'm sorry, I can't help with that." A good fallback experience redirects the user, explains what the agent can do, offers to connect them with someone who can help, and logs the gap so you can expand coverage later.
The foundation of a good fallback system is knowing how confident the agent is in its response. Use a two-pass approach — first classify the intent and confidence, then decide how to respond:
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
flowchart LR
INPUT(["User intent"])
PARSE["Parse plus<br/>classify"]
PLAN["Plan and tool<br/>selection"]
AGENT["Agent loop<br/>LLM plus tools"]
GUARD{"Guardrails<br/>and policy"}
EXEC["Execute and<br/>verify result"]
OBS[("Trace and metrics")]
OUT(["Outcome plus<br/>next action"])
INPUT --> PARSE --> PLAN --> AGENT --> GUARD
GUARD -->|Pass| EXEC --> OUT
GUARD -->|Fail| AGENT
AGENT --> OBS
style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
style OBS fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
style OUT fill:#059669,stroke:#047857,color:#fff
from pydantic import BaseModel
from enum import Enum
class Confidence(str, Enum):
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
OUT_OF_SCOPE = "out_of_scope"
class IntentClassification(BaseModel):
intent: str
confidence: Confidence
reasoning: str
async def classify_with_confidence(message: str, agent_scope: str) -> IntentClassification:
response = await openai.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": f"""Classify the user's intent and your confidence in handling it.
Agent scope: {agent_scope}
Return JSON with: intent, confidence (high/medium/low/out_of_scope), reasoning.
- high: clearly within scope, you know exactly how to help
- medium: probably within scope but may need clarification
- low: tangentially related, might be able to help partially
- out_of_scope: clearly outside what this agent handles"""},
{"role": "user", "content": message},
],
response_format={"type": "json_object"},
)
return IntentClassification.model_validate_json(
response.choices[0].message.content
)
async def route_by_confidence(
message: str,
classification: IntentClassification,
session_id: str,
) -> dict:
match classification.confidence:
case Confidence.HIGH:
return await process_normally(message, session_id)
case Confidence.MEDIUM:
return await process_with_clarification(message, classification, session_id)
case Confidence.LOW:
return await process_with_caveat(message, classification, session_id)
case Confidence.OUT_OF_SCOPE:
return await handle_out_of_scope(message, classification, session_id)
Instead of a single "I can't help" message, implement a cascade of increasingly helpful responses:
async def handle_out_of_scope(
message: str,
classification: IntentClassification,
session_id: str,
) -> dict:
# Layer 1: Acknowledge and redirect
scope_description = "I specialize in product questions, pricing, and technical support."
# Layer 2: Suggest related topics the agent CAN help with
suggestions = await find_related_topics(message)
# Layer 3: Offer human escalation
escalation_available = await check_human_availability()
response_parts = [
f"That question is outside my area of expertise. {scope_description}",
]
if suggestions:
formatted = ", ".join(suggestions[:3])
response_parts.append(f"However, I can help you with: {formatted}.")
if escalation_available:
response_parts.append(
"Would you like me to connect you with a human agent who may be able to help?"
)
else:
response_parts.append(
"Our support team is available at support@example.com for questions outside my scope."
)
# Layer 4: Log for coverage improvement
await log_fallback(session_id, message, classification)
return {
"type": "quick_replies",
"text": " ".join(response_parts),
"replies": build_fallback_replies(suggestions, escalation_available),
}
def build_fallback_replies(suggestions: list, escalation_available: bool) -> list:
replies = [{"label": s, "value": f"topic:{s}"} for s in suggestions[:3]]
if escalation_available:
replies.append({"label": "Talk to a human", "value": "escalate"})
return replies
Escalation is not just transferring the conversation. Package the context so the human agent can pick up seamlessly:
from dataclasses import dataclass
@dataclass
class EscalationPackage:
session_id: str
user_message: str
conversation_summary: str
detected_intent: str
confidence: str
suggested_department: str
user_sentiment: str
priority: str
async def escalate_to_human(session_id: str, message: str, classification: IntentClassification):
# Summarize conversation for the human agent
history = await get_conversation_history(session_id)
summary = await summarize_for_handoff(history)
# Detect sentiment and urgency
sentiment = await detect_sentiment(message)
priority = "high" if sentiment in ("frustrated", "angry") else "normal"
# Determine department
department = await route_to_department(classification.intent)
package = EscalationPackage(
session_id=session_id,
user_message=message,
conversation_summary=summary,
detected_intent=classification.intent,
confidence=classification.confidence,
suggested_department=department,
user_sentiment=sentiment,
priority=priority,
)
ticket_id = await create_support_ticket(package)
return {
"type": "text",
"content": (
f"I've connected you with our {department} team. "
f"Your reference number is {ticket_id}. "
"A team member will be with you shortly. "
"Everything we've discussed has been shared with them so "
"you won't need to repeat yourself."
),
}
Every fallback is a data point for improvement. Build a feedback loop:
import json
from datetime import datetime
async def log_fallback(session_id: str, message: str, classification: IntentClassification):
await db.execute(
"""INSERT INTO fallback_logs (session_id, user_message, detected_intent,
confidence, reasoning, created_at)
VALUES ($1, $2, $3, $4, $5, $6)""",
session_id, message, classification.intent,
classification.confidence, classification.reasoning,
datetime.utcnow(),
)
async def get_fallback_report(days: int = 7) -> dict:
rows = await db.fetch(
"""SELECT detected_intent, COUNT(*) as count,
array_agg(DISTINCT user_message) as sample_messages
FROM fallback_logs
WHERE created_at > NOW() - INTERVAL '%s days'
GROUP BY detected_intent
ORDER BY count DESC
LIMIT 20""",
days,
)
return {
"period_days": days,
"top_gaps": [
{"intent": r["detected_intent"], "count": r["count"],
"samples": r["sample_messages"][:5]}
for r in rows
],
}
Run this report weekly. The top gaps tell you exactly what topics to add to your agent's scope next. If 40% of fallbacks are about "shipping status," that is your next feature.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
Instruct the agent explicitly in its system prompt: "If you are not confident you can answer accurately based on the available tools and knowledge, say so instead of guessing." Reinforce this with a confidence classification step before generating the final response. Test with adversarial questions that are close to but outside your agent's scope — these are where hallucination risk is highest.
For a well-scoped agent, aim for a fallback rate below 10-15% of total conversations. Higher than that means your scope definition does not match user expectations. Lower than 2-3% might mean your confidence threshold is too low and the agent is answering questions it should not be. Track the fallback rate over time and correlate it with user satisfaction scores to find your optimal threshold.
Yes, but with guardrails. Prefix the response with a transparency signal: "I'm not entirely sure about this, but..." and offer to escalate if the answer is not helpful. This serves users who have simple questions outside the core scope while still being honest about the agent's limitations. Track whether users accept or reject these low-confidence answers to calibrate your threshold over time.
#Fallback #ErrorHandling #Escalation #IntentDetection #ChatAgent #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
A data-led look at how small businesses from Lusaka to Kitwe are losing revenue to missed calls, and how CallSphere AI voice and chat agents answer every enquiry 24/7 in English, Bemba, and Nyanja.
LangChain v1 + LangGraph v1 in JS, paired with Ollama, gives you a fully local chat agent with tools, memory, and structured output. No OpenAI key required.
What happens when the model has no good answer is the most-undervalued bot design decision. The 2026 fallback patterns that work.
How to design chat-to-voice escalation that preserves context, picks the right channel, and beats the warm-transfer baseline of human agents.
When the agent fails, the handoff is the entire experience. Here are the 2026 UX patterns — confidence-based, permission-based, and the warm transcript transfer.
Learn how CallSphere's 7-agent after-hours escalation system detects emergencies, triggers call ladders, and ensures the right person responds within 60 seconds.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco