# Human-in-the-Loop Escalation

> Escalation is the deterministic handoff path when policy thresholds, low confidence, or repeated failure conditions are hit. The exam pattern: prompt-only escalation policy is wrong; the correct architecture uses hooks or hard gates. Full content in SCRUM-21 follow-up.

**Domain:** D1 · Agentic Architectures (27% of CCA-F exam)
**Canonical:** https://claudearchitectcertification.com/concepts/escalation
**Last reviewed:** 2026-05-04

## Quick stats

- **Trigger types:** 3
- **Exam domain:** D1
- **Right answer:** deterministic gate
- **Coverage tier:** B
- **Trap:** prompt-only policy

## What it is

An escalation is a deterministic transfer of control from an automated agent to a human operator, triggered by an explicitly defined condition. It is not a fallback when the agent gets stuck. It is not a heuristic like "escalate if uncertain". It is a Boolean check: Is this condition met? Yes: interrupt, gather facts, hand off. No: continue.

The most common triggers are policy exceptions (refund exceeds limit), permission failures (access denied), explicit customer request ("I want a human"), and ambiguous input (conflicting sources, missing required field). The agent's job is to recognize these signals, structure the handoff, and transfer decisively. A well-designed escalation takes 1-2 seconds. A poor one wastes 5 minutes of a human's time.

Escalation is a policy hook, not a prompt instruction. The agent does not decide via language like "I should escalate if the customer seems angry". The system enforces escalation via a PreToolUse hook that intercepts specific tool calls (e.g. process_refund) and checks: is this within policy? If any check fails, the hook blocks execution, constructs an escalation block, and exits the loop. Hooks execute before reasoning, guaranteeing compliance.

Escalation handoff has two phases: assembly and routing. In assembly, the harness gathers customer_id, order_id, transaction amount, policy limit, root cause, partial resolution status, and a one-sentence summary into a structured block (JSON or YAML, not prose). In routing, the block is sent to a queue (Slack, PagerDuty, email) and the agent stops. A human reads the block in 10 seconds and decides.

## How it works

Escalation begins with trigger detection. A PreToolUse hook wraps high-stakes tool calls (process_refund, delete_record, send_communication). Before the tool executes, the hook evaluates the trigger condition: if refund_amount > policy_limit: escalate. The condition is deterministic, no fuzzy logic, no sentiment scoring. If true, execution halts and the agent does not get to argue for the exception.

Once triggered, escalation assembles the handoff block. The harness extracts and structures: customer_id, order_id, refund_amount, policy_limit, current_balance, root_cause (what the agent discovered), partial_resolution_status (what was accomplished), and recommended_action. The block is compact: 200-500 characters, readable in 10 seconds. Verbose blocks kill efficiency.

The block is routed to a queue. This is infrastructure, not agent code. Blocks land in Slack (#escalations), PagerDuty, or an email inbox. The queue includes timestamp, SLA, assignment, and a link to the full conversation. The agent stops and waits, or for batch workflows, saves the block and continues with a degraded fallback (auto-approve up to $100, escalate the rest).

Escalation success is binary: either the human resolves the case and signals back (database flag, API call), or the task is shelved. When a manager approves a $1000 refund, this approval is recorded. A subsequent retry of the agent loop checks the approval flag, and the process_refund hook sees approval_status: "manager_approved" and allows execution. Without this round-trip, the escalation is incomplete; the agent will re-attempt indefinitely.

## Where you'll see it in production

### Policy violation with PreToolUse hook

Refund request for $1200, policy max is $500. Hook checks if amount > 500: escalate. Block goes to Slack. Manager approves or denies in 5 seconds and updates database flag refund_approval: approved_1200. Agent retries; hook sees the flag, allows execution.

### Ambiguous input requiring human sourcing

Three vendors claim different market shares. The agent cannot resolve. Escalation block: {conflict, sources, recommendation: "human sourcing decision"}. Without escalation, the agent would guess and silently produce inaccurate output.

### Permission failure (403 from infrastructure)

Operations agent tries restart_pod('prod-payment'). Tool returns 403 Forbidden. Harness detects error category. Escalation block routes to DevOps on-call. The on-call engineer either elevates permissions or performs the action themselves.

### Explicit customer request

Customer says "I'd like to speak to a manager." Structural state-machine check: if "manager" in message: escalate_immediately. Block goes to manager queue. No agent reasoning, no multi-turn negotiation. This is policy, not judgment.

## Code examples

### Deterministic escalation via PreToolUse hook

**Python:**

```python
from anthropic import Anthropic
import json

client = Anthropic()

def refund_hook(tool_name: str, tool_input: dict, facts: dict, policy: dict):
    """PreToolUse hook for process_refund. Returns {allow, escalate, block}."""
    if tool_name != "process_refund":
        return {"allow": True}

    amount = tool_input.get("amount", 0)
    if amount > policy["max_refund"]:
        return {
            "allow": False,
            "escalate": True,
            "block": json.dumps({
                "customer_id": facts["customer_id"],
                "order_id": facts["order_id"],
                "refund_amount": amount,
                "policy_limit": policy["max_refund"],
                "reason": f"Refund ${amount} exceeds limit ${policy['max_refund']}",
                "partial_status": "Order verified, reason confirmed",
                "recommended_action": "Manager approval required",
            }),
        }
    return {"allow": True}

# Run the agent with hook enforcement
def run_agent(msg: str, facts: dict, policy: dict):
    messages = [{"role": "user", "content": msg}]
    for turn in range(10):
        resp = client.messages.create(
            model="claude-opus-4-5", max_tokens=1024, messages=messages, tools=[...]
        )
        if resp.stop_reason == "end_turn":
            return {"status": "ok"}
        # Inspect tool_use blocks, run hook before execution
        for block in resp.content:
            if block.type == "tool_use":
                check = refund_hook(block.name, block.input, facts, policy)
                if check.get("escalate"):
                    return {"status": "escalated", "block": check["block"]}
        # ... append tool_result, continue ...
    return {"status": "max_iterations"}
```

> Hook intercepts process_refund BEFORE execution. Deterministic check; no model judgment. Agent never reaches the tool when policy violated.

**TypeScript:**

```typescript
import Anthropic from "@anthropic-ai/sdk";

interface RefundFacts { customer_id: string; order_id: string; }
interface RefundPolicy { max_refund: number; }

function refundHook(
  toolName: string,
  toolInput: Record<string, unknown>,
  facts: RefundFacts,
  policy: RefundPolicy,
): { allow: boolean; escalate?: boolean; block?: string } {
  if (toolName !== "process_refund") return { allow: true };

  const amount = (toolInput.amount as number) || 0;
  if (amount > policy.max_refund) {
    return {
      allow: false,
      escalate: true,
      block: JSON.stringify({
        customer_id: facts.customer_id,
        order_id: facts.order_id,
        refund_amount: amount,
        policy_limit: policy.max_refund,
        reason: `Refund $${amount} exceeds limit $${policy.max_refund}`,
        partial_status: "Order verified, reason confirmed",
        recommended_action: "Manager approval required",
      }),
    };
  }
  return { allow: true };
}

// Use the hook before each tool execution in your agent loop
```

> Same pattern in TypeScript. The hook is the deterministic gate; the agent loop calls it before every tool execution.

## Looks-right vs actually-wrong

| Looks right | Actually wrong |
|---|---|
| Prompt the agent to escalate if something seems risky. | Prompt-based escalation is unreliable. The agent sees many "risky" cases and may not escalate any. Use deterministic hooks. Policy exceptions trigger hooks, not prompts. |
| Wait until the agent finishes its task, then check if it made mistakes, then escalate. | Escalation must happen BEFORE tool execution (PreToolUse hook), not after. Catching mistakes post-execution means the refund was already processed, the email was already sent. Too late. |
| Escalate ambiguous cases by sending the full conversation transcript to a human. | Send a structured escalation block (200 chars, readable in 10s), not a transcript. Humans cannot triage 20-turn conversations efficiently. Structure the facts the human needs. |
| If the agent is uncertain, have it escalate. | Uncertainty is not a trigger. Only explicit conditions trigger escalation: policy exception, permission failure, ambiguous input, explicit request. Agent confidence is orthogonal to escalation triggers. |
| Once escalated, wait for the human to respond before continuing. | For user-blocking workflows (refund approval), yes, wait. For batch workflows, save the block and continue with fallback (auto-approve up to $100, escalate the rest). Design determines the pattern. |

## Comparison

| Escalation Type | Trigger | Handoff Structure | Human SLA | Round-trip |
| --- | --- | --- | --- | --- |
| Policy exception | PreToolUse hook: amount > limit | customer_id, amount, reason, partial_status | 30 min | Yes: approval flag → agent retry |
| Permission failure | Tool error: 403 | task, failure_reason, alternative | 1-5 min | No: human performs action |
| Ambiguous input | Agent detects conflict | conflict, sources, recommendation | 1-2 hours | No: human sourcing |
| Explicit request | Regex on user text | customer_id, request_context, current_stage | 5 min | No: transfer to queue |
| Missing field | Validation: required field null | field_name, blocking_reason, options | 15 min | Yes: collect field, retry |
| Compound (policy + missing field) | Multiple checks | All blocking conditions listed | 30 min | Yes: resolve all, retry |

## Decision tree

1. **Is this a deterministic policy rule (amount > limit, access denied)?**
   - **Yes:** Use a PreToolUse hook. Boolean condition. Hook blocks execution and exits agent.
   - **No:** Use ambiguity or explicit-request detection via state or text matching.

2. **Can the agent proceed with a degraded fallback (auto-approve up to $X)?**
   - **Yes:** Escalate only the overflow. Continue with fallback for the rest.
   - **No:** Escalate and halt. Wait for human decision before continuing.

3. **Must the human re-enable the agent (round-trip)?**
   - **Yes:** Record the human decision in a database field. Agent retries; hook sees flag and allows execution.
   - **No:** Human acts independently. Agent does not retry; task ends.

4. **Is this an explicit customer request ("I want a human")?**
   - **Yes:** Immediate escalation, no negotiation. Use keyword detection.
   - **No:** System rule: policy, permission, ambiguity. Use hook or structured check.

5. **How long can the human take to respond?**
   - **Yes:** SLA < 10 min (customer-blocking): async queue (Slack/email). User sees "escalated, response in ~5min".
   - **No:** SLA > 1 hour (batch): save block, continue with fallback.

## Exam-pattern questions

### Q1. Refund agent uses prompt-only enforcement: "escalate refunds over $500". Production failures show 5% of refunds violate the policy. Fix?

Replace with a PreToolUse hook that checks if amount > 500: escalate. Deterministic gate. Prompt-only is probabilistic; hook is 100% enforcement.

### Q2. Two vendors return contradictory market sizes. The agent picks the median and continues. Why is this wrong?

Ambiguity is an escalation trigger. Agent must surface the conflict, not silently average. Block: {conflict, sources, recommendation: "human sourcing"}. Otherwise the report is silently inaccurate.

### Q3. After PreToolUse blocks a refund, the agent retries the same call on the next loop. Why?

The escalation didn't update the database with refund_approval flag. Round-trip pattern: human approves → DB flag set → agent retries → hook sees flag → allows execution. Without the flag, indefinite re-escalation.

### Q4. User says "speak to a manager." Should the agent negotiate first or escalate immediately?

Escalate immediately. No reasoning, no multi-turn negotiation. Structural keyword check: if "manager" in message: escalate_immediately. This is policy, not judgment.

### Q5. Permission failure (403 from infrastructure) — should the agent retry or escalate?

Escalate. Permission failures are non-retryable error category 4. Block routes to the relevant on-call engineer with task description and partial status. Retrying wastes time and obscures the real fix (privilege grant).

### Q6. Difference between an error and an escalation?

Error = unhandled (agent crashes, tool fails unexpectedly). Escalation = handled (agent recognizes a designed condition, stops gracefully, submits structured block). Escalation is a designed path, not a failure.

### Q7. Should a customer's tone (anger, frustration) trigger escalation?

No. Sentiment is not a trigger. Only explicit conditions: policy exception, permission failure, ambiguous input, explicit request for human. Sentiment is orthogonal to escalation rules.

### Q8. Customer-blocking workflow vs batch workflow: how does the escalation pattern differ?

Customer-blocking: agent stops, async queue with 5-10 min SLA, user sees "escalated, response in ~5min". Batch: save block, continue with fallback (auto-approve up to $100, escalate the rest). Design determines the pattern, not the trigger.

## FAQ

### Q1. Is escalation the same as an error?

No. An error is unhandled (agent crashes). An escalation is handled (agent recognizes a condition requiring human decision, stops gracefully, submits a structured block). Escalation is a designed path, not a failure.

### Q2. Should escalation happen before or after the tool executes?

Before, via PreToolUse hook. If you check after, the refund is already processed. Too late. Hooks prevent the tool call from happening.

### Q3. Should the escalation block include the agent's reasoning or just facts?

Just facts. The block is for the human: customer_id, amount, reason, partial_status, recommended_action. If the human needs reasoning, they read the conversation (link in the block).

### Q4. What if a human approves but the agent re-escalates?

Record the approval in a database field that the hook checks. Without the flag (or if approval expires), re-escalation happens. Agents do not remember approvals; the database does.

### Q5. Is sentiment a valid escalation trigger?

No. "I'm angry" is not a policy violation or permission failure. Escalate on explicit request or system triggers (policy, permission, missing field). Sentiment is orthogonal.

### Q6. Should I escalate ambiguous cases or make a conservative assumption?

Escalate. Ambiguity is a trigger. The agent should not guess. Structured block: describe the conflict, list options, recommend a path. Guessing introduces silent errors.

### Q7. Difference between escalation and asking the agent to loop again?

Loop: agent retries the same task. Escalation: agent stops, submits to human. No agent retry. The human either fixes the condition (updates database) and the agent retries, or handles it independently.

### Q8. Can multiple escalations happen in one task?

Yes. Refund exceeds limit (escalation 1, manager approves), then customer phone is missing (escalation 2, support collects phone). Different triggers, multiple escalations. Each is logged and routed separately.

### Q9. Should escalation blocks be queued (async) or delivered synchronously?

Depends on SLA. Customer-blocking: async with 5-10 min SLA. Batch: async with 1-4 hour SLA. Agent waits in customer-blocking, continues with fallback in batch.

### Q10. What if a human never responds to an escalation?

Design timeout handling: retry after T minutes, escalate to higher tier, or fail gracefully with user message. The task should not hang indefinitely.

---

**Source:** https://claudearchitectcertification.com/concepts/escalation
**Vault sources:** ACP-T03 §4.1 escalation protocol; ACP-T05 Scenario 1
**Last reviewed:** 2026-05-04

**Evidence tiers** — 🟢 official Anthropic doc / API contract · 🟡 partial doc / inferred · 🟠 community-derived · 🔴 disputed.
