Staso Docs
Guard

Actions and Escalation

Guard returns one of four actions — allow, block, modify, or escalate — which tells your code what to do next.

import staso as st

result = st.guard("process_refund", {"amount": 4200, "user_id": "u_42"})

match result.action:
    case "allow":
        run(tool_input=result.modified_input or original_input)
    case "modify":
        run(tool_input=result.modified_input)
    case "block":
        log(result.reason)
    case "escalate":
        queue_for_human(result.escalation_id)

allow

The call is approved — proceed as requested. This is the common path and incurs no extra work.

block

The call is denied. Do not execute the tool.

  • In integrations (patch_anthropic / patch_openai): staso.GuardBlocked is raised. You catch it and fall back.
  • In manual calls (st.guard(...)): return early, log result.reason, and decide how to recover. Log result.rules_triggered so you know which policy fired.

modify

Guard has rewritten the input. Use result.modified_input instead of the original arguments.

result = st.guard("send_email", {"to": "[email protected]", "body": "..."})
if result.action == "modify":
    send_email(**result.modified_input)  # rewritten, safer input

Inside patch_anthropic / patch_openai, the integration applies the modification to the provider's tool-call object automatically — your agent loop sees the rewritten input transparently.

escalate

A human must approve the call before it can run. You have two options:

Fire-and-forget. Return immediately with result.escalation_id, surface it in your app, and let the human resolve it in the dashboard. Your code should treat escalate like block and not run the tool.

Synchronous wait. Pass wait_for_escalation=True and Guard polls until the escalation is resolved, returning either allow (approved) or block (denied / timed out).

result = st.guard(
    "process_refund",
    {"amount": 10000},
    wait_for_escalation=True,
    escalation_timeout=300.0,        # max 5 minutes waiting
    escalation_poll_interval=3.0,    # check every 3 seconds
)

if result.action == "allow":
    run_refund(amount=10000)
else:
    log(f"escalation denied: {result.reason}")

Defaults: escalation_poll_interval=3.0 seconds, escalation_timeout=300.0 seconds. Tune them for your UX — tight loops for chat-style apps, longer timeouts for batch jobs.

Trace visibility

Every decision lands on the active trace as a child span: guard:blocked:<tool>, guard:modified:<tool>, or guard:would-block:<tool> (for audit-mode rules that fired but let the call through). Open any trace on the dashboard to see what Guard did and why.

Next