Escalation
Some decisions are too important for rules alone. Escalation pauses the agent and waits for a human to approve or deny the action.
When Escalation Happens
A guard rule returns "escalate" when the tool call matches an escalation condition -- typically high-value transactions, irreversible operations, or edge cases where automated rules can't confidently decide.
result = guard(
tool_name="wire_transfer",
tool_input={"amount": 50000, "destination": "external-bank"},
)
# result.action == "escalate"
# result.escalation_id == "esc_abc123"Configure escalation rules in your Staso dashboard. Example: "Escalate any refund over $1,000" or "Escalate database deletions in production."
Two Modes
Fire-and-Forget (Default)
Get the escalation ID back immediately. Handle the pending state in your application.
result = guard(
tool_name="wire_transfer",
tool_input={"amount": 50000},
)
if result.action == "escalate":
# Notify the user, queue the action, or return a pending status
return f"This transfer requires approval. Reference: {result.escalation_id}"Use this when your agent runs in a request-response model (API endpoints, chatbots) where you can't block the thread.
Polling (Synchronous Wait)
Block until a human approves or denies. The SDK polls the escalation status automatically.
result = guard(
tool_name="wire_transfer",
tool_input={"amount": 50000},
wait_for_escalation=True, # enable polling
escalation_poll_interval=3.0, # check every 3 seconds (default)
escalation_timeout=300.0, # give up after 5 minutes (default)
)
# After a human responds:
# result.action == "allow" (approved)
# result.action == "block" (denied or timeout)Use this for background workers, batch jobs, or agents where blocking is acceptable.
Polling Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
wait_for_escalation | bool | False | Enable synchronous polling |
escalation_poll_interval | float | 3.0 | Seconds between status checks |
escalation_timeout | float | 300.0 | Max seconds to wait before timing out |
Timeout Behavior
If the human doesn't respond within escalation_timeout:
# result.action == "block"
# result.reason == "Escalation poll timeout"Timeouts are treated as denials. The tool does not execute.
Resolution States
| Status | Result Action | Meaning |
|---|---|---|
approved | allow | Human approved -- safe to execute |
denied | block | Human denied -- do not execute |
timeout | block | No response within the timeout window |
Example: Refund with Escalation
import staso as st
from staso import guard
st.init(api_key="ak_...", agent_name="refund-agent")
@st.tool(name="process_refund")
def process_refund(customer_id: str, amount: float) -> str:
result = guard(
tool_name="process_refund",
tool_input={"customer_id": customer_id, "amount": amount},
wait_for_escalation=True,
escalation_timeout=120.0, # 2 minutes for manager to respond
)
if result.action == "block":
return f"Refund denied: {result.reason}"
if result.action == "allow":
do_refund(customer_id, amount)
return f"Refund of ${amount} processed"
# "modify" -- amount was adjusted
adjusted = result.modified_input["amount"]
do_refund(customer_id, adjusted)
return f"Refund of ${adjusted} processed (adjusted from ${amount})"
st.shutdown()Dashboard
Escalation events show up on your Staso dashboard with:
- The tool name and input that triggered escalation
- The rule that caused the escalation
- The resolution (approved, denied, or timed out)
- Time to resolution
Next
- Guard overview -- what guards are and how they work
- Integration guide -- add guards to your agent
- Rules and actions -- all action types and the GuardResult API