Designing Escalation Paths When an Agent Gets Stuck
An escalation path is a designed route from "the agent cannot safely proceed" to a human (or higher-privilege process) with enough context to finish the job.
Search across all documentation pages
An escalation path is a designed route from "the agent cannot safely proceed" to a human (or higher-privilege process) with enough context to finish the job.
Stuck means low confidence, conflicting evidence, repeated tool failure, policy ambiguity, or a required capability outside the current privilege ring.
Detect stuck states with host rules, stop guessing, package a concise escalation brief, route it to the right queue or person, pause or degrade gracefully, and resume only with a recorded human decision or handoff outcome.
from dataclasses import dataclass, field
from enum import Enum
from typing import Any
class StuckReason(str, Enum):
NO_PROGRESS = "no_progress"
LOW_CONFIDENCE = "low_confidence"
POLICY = "policy_ambiguity"
TOOL_ERRORS = "tool_errors"
NEED_HUMAN_DATA = "need_human_data"
@dataclass
class EscalationBrief:
run_id: str
reason: StuckReason
goal: str
attempts: list[str]
question: str
urgency: str = "normal" # normal | high
evidence: dict[str, Any] = field(default_factory=dict)
QUEUE: list[EscalationBrief] = []
def should_escalate(
*,
identical_tool_hits: int,
confidence: float,
consecutive_tool_errors: int,
needs_user_secret: bool,
) -> StuckReason | None:
if needs_user_secret:
return StuckReason.NEED_HUMAN_DATA
if identical_tool_hits >= 3:
return StuckReason.NO_PROGRESS
if consecutive_tool_errors >= 3:
return StuckReason.TOOL_ERRORS
if confidence < 0.45:
return StuckReason.LOW_CONFIDENCE
return None
def escalate(brief: EscalationBrief) -> dict[str, Any]:
QUEUE.append(brief)
return {
"status": "escalated",
"reason": brief.reason.value,
"queue_depth": len(QUEUE),
"user_message": (
"I need a human teammate for this step. "
f"Question: {brief.question}"
),
}
def route_queue(brief: EscalationBrief) -> str:
if brief.reason is StuckReason.POLICY:
return "compliance"
if brief.urgency == "high" or brief.reason is StuckReason.TOOL_ERRORS:
return "oncall"
if brief.reason is StuckReason.NEED_HUMAN_DATA:
return "requesting_user"
return "support_l1"
# Demo: agent detects thrash and escalates instead of guessing
reason = should_escalate(
identical_tool_hits=3,
confidence=0.9,
consecutive_tool_errors=0,
needs_user_secret=False,
)
assert reason is StuckReason.NO_PROGRESS
brief = EscalationBrief(
run_id="run-42",
reason=reason,
goal="Apply loyalty credit to account A-9",
attempts=["get_account", "get_account", "get_account"],
question="Account lookup returns 404. Correct account id?",
evidence={"account_id_tried": "A-9"},
)
print(escalate(brief))
print("route:", route_queue(brief))| Pattern | Trigger | Human job |
|---|---|---|
| Approval gate | Agent knows the action; policy requires consent | Allow / deny / edit a concrete tool call |
| Escalation | Agent should not decide alone | Provide missing judgment, data, or ownership |
| Kill switch | Run is harmful or out of control | Stop everything |
Do not force every uncertainty through an "approve send" UI. Ask a clear question when the need is information or judgment.
Useful host-side detectors:
escalate_to_human(question) (still enforce caps so it cannot spam)Prefer code thresholds over hoping the model says "I'm stuck."
Humans abandon walls of logs. Keep briefs short:
Store the brief as structured data so routing and SLAs work.
Map reason codes to queues. Compliance questions should not sit in a general engineering Slack forever.
For customer-facing agents, distinguish:
Never invent answers for identity, legal, or medical edge cases just to avoid escalation.
Two valid outcomes:
handed_off and stops tools.Hard handoff is better when the remaining work is relationship-heavy or out of tool scope.
If you use model-verbalized confidence, calibrate it - models are often overconfident.
Pair verbal confidence with independent checks (retrieval agreement, regex/business rules, second model vote on critical paths) before you trust "low confidence" alone.
| Approach | Best for | Trade-off |
|---|---|---|
| Structured escalate tool + queue (this recipe) | Product agents with ops teams | Needs staffing and SLAs |
| Always ask the end user | Consumer bots, preferences | Wrong for internal policy |
| Auto-degrade to FAQ / search only | High volume, low risk | May under-serve complex cases |
| Multi-agent specialist handoff | Clear skill splits | Still needs human path when all fail |
| Page on-call for everything | Early prototypes | Unsustainable; burns trust |
Retry transient network and rate-limit errors with caps. Escalate no-progress, policy ambiguity, and missing privileged data.
Internal escalations need authenticated staff. End-user questions need session auth. Do not accept anonymous "just approve it" messages.
Set user-visible expectations, allow partial answers that are safe, and staff queues to your SLA. Async agents should not pretend to be finished.
Yes as a first hop (specialist). If the specialist is also stuck, escalate to a human rather than cycling forever.
Honest status and the question you need. Avoid dumping internal tool errors or policy codes unless product design requires it.
Needing an R2+ capability without approval is a form of stuck/escalation. Route to an approver with a decision packet, not a free-text chat only.
Inject stuck fixtures (404 loops, low confidence, policy flags) and assert: no further side effects, brief fields present, correct queue, resume works.
Escalation rate, median time-to-first-human-response, resolution without reopen, and incidents where the agent guessed instead of escalating.
Yes when paired with a host path that escalates or asks. Bare "I don't know" with no routing is a dead end.
In a short matrix: signal → action → owner → SLA. Link it from runbooks and the agent README.
Stack versions: Pins from the category manifest (verify at build): OpenRouter (~315+ models, July 2026 pricing/fees); LangGraph 1.0+; CrewAI 1.14+; Microsoft Agent Framework 1.0; Vercel AI SDK 6; Pydantic AI (latest); LlamaIndex (latest); OpenAI Agents SDK (latest + MCP); MCP (Linux Foundation governance); A2A (HTTP+SSE+JSON-RPC 2.0); Solana
@solana/web3.js+@solana/spl-token.
Reviewed by Chris St. John·Last updated Jul 16, 2026