Staso Docs
Guard

Guard Error Handling

When the Anthropic or OpenAI integration is enabled, a blocked tool call raises staso.GuardBlocked.

import staso as st
from staso.integrations import patch_anthropic
import anthropic

st.init(api_key="...", agent_name="refunds-agent")
patch_anthropic()
client = anthropic.Anthropic()

try:
    resp = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=1024,
        tools=[...],
        messages=[...],
    )
except st.GuardBlocked as e:
    for bt in e.blocked_tools:
        print(f"blocked {bt.tool_name}: {bt.result.reason}")
    # fall back or ask the user

The integrations evaluate every tool call in the batch before raising, so you see all failures, not just the first.

GuardBlocked fields

FieldTypeDescription
tool_namestrName of the first tool that was blocked. Handy for one-line error messages.
resultGuardResultThe full GuardResult for that first tool.
reasonstrShort human-readable reason. When multiple tools are blocked, this summarises all of them.
rules_triggeredlist[str]Deduplicated union of every rule name that contributed across all blocked tools.
blocked_toolslist[BlockedTool]Ordered list of every blocked tool in the batch. Iterate this for per-tool detail.

BlockedTool is a small frozen dataclass:

FieldTypeDescription
tool_namestrThe blocked tool's name.
resultGuardResultThe full GuardResult for that tool.

Patterns

Recover gracefully. Catch GuardBlocked, log the reason, and return a safe default or user-facing message. Never let it crash the outer request handler.

Fall back to a safer tool. If send_email was blocked for containing PII, try send_email_redacted. Structure your agent prompts so a fallback path exists.

Log and alert. Push e.rules_triggered and e.blocked_tools into your logger or alerting pipeline. Frequent blocks mean either a misbehaving agent or an over-eager rule — both worth knowing.

Re-ask the user. Surface e.reason back into your chat UI: "I can't do that because <reason>. Want me to try something else?"

Transport failures

If Guard itself is unreachable (network down, timeout, backend error), Guard fails open by default — the tool call is allowed and no exception is raised. The philosophy: a flaky observability backend should never brick production agents.

Override per call:

result = st.guard("delete_user", {"id": "u_42"}, fail_closed=True)

Or process-wide:

export STASO_GUARD_FAIL_CLOSED=1

With fail_closed=True, a transport failure returns action="block" with severity="high" and a reason like "Guard unavailable (fail-closed): <error>". In the patched integrations this path raises GuardBlocked, so wrap your client calls in try/except as shown above.

Next