Refusal escalation flow

A refusal is information, not an endpoint. Sometimes the refusal is correct and the user needs a clear explanation. Sometimes the refusal is over-cautious and a human reviewer would unblock the request. The escalation flow is what turns a flat no into one of three explicit branches.

layers/escalation.py
python
from enum import Enum


class RefusalReason(str, Enum):
    pii = 'pii'
    policy = 'policy'
    uncertain = 'uncertain'


async def handle_refusal(reason: RefusalReason, envelope: RequestEnvelope) -> dict:
    if reason is RefusalReason.pii:
        # Hard refusal: never escalate raw PII to humans
        return {'status': 'refused', 'message': 'Cannot process requests with personal data.'}

    if reason is RefusalReason.policy:
        # Soft refusal: hand to a human review queue
        await review_queue.enqueue(envelope)
        return {'status': 'pending_review', 'eta_minutes': 30}

    if reason is RefusalReason.uncertain:
        # Uncertain refusal: try a safer fallback model
        return await safer_model.run(envelope)

Three reasons, three paths. PII is always a hard refusal because escalating raw PII would defeat the scrub. Policy refusals can be reviewed by a human. Uncertainty calls a smaller, more conservative model as a fallback.

Two rules keep this safe. PII never escalates: forwarding scrubbed-but-still-personal data to a queue defeats the protection. And every escalation produces a structured event with the trace ID, so the audit log shows exactly which refusals went where.

Quiz: Quiz

Loading practice…