Staso Docs
Guard

Actions and escalation

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

Proceed as requested. Common path. No extra work.

block

Do not execute the tool.

  • In patch_anthropic / patch_openai: staso.GuardBlocked is raised. Catch and recover.
  • In st.guard(...): return early, log result.reason, log result.rules_triggered.

modify

Guard rewrote the input. Use result.modified_input.

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

In patched integrations, the rewrite is applied to the provider's tool-call object automatically — your agent loop sees the safer input transparently.

escalate

A human must approve before the call runs.

Fire-and-forget. Return immediately with result.escalation_id. Surface it in your app. Treat escalate like block for now and don't run the tool.

Synchronous wait. Pass wait_for_escalation=True and Guard polls until the escalation resolves, returning 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
    escalation_poll_interval=3.0,    # check every 3s
)

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

Defaults: escalation_poll_interval=3.0, escalation_timeout=300.0. Tighten for chat UX, loosen for batch jobs.

Trace visibility

Every decision becomes a child span: guard:blocked:<tool>, guard:modified:<tool>, or guard:would-block:<tool> (audit-mode rules). Open any trace to see what Guard did and why.

Next