Staso Docs
Guard

Manual Guard Checks

Call st.guard(...) explicitly to evaluate a tool action from non-patched code paths.

import staso as st

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

if result.action == "block":
    raise RuntimeError(result.reason)
if result.action == "modify":
    tool_input = result.modified_input

actual_run_refund(**tool_input)

Use this when you're dispatching tools yourself — a custom agent framework, a background job, or a hook outside the Anthropic/OpenAI drop-in integrations.

Signature

st.guard(
    tool_name: str,
    tool_input: dict[str, Any],
    *,
    context: dict[str, Any] | None = None,
    wait_for_escalation: bool = False,
    escalation_poll_interval: float = 3.0,
    escalation_timeout: float = 300.0,
    timeout: float = 10.0,
    fail_closed: bool | None = None,
) -> GuardResult

Parameters

ParameterTypeDefaultDescription
tool_namestrrequiredName of the tool being called.
tool_inputdictrequiredThe tool's input arguments.
contextdict | NoneNoneOptional context (conversation_id, agent_name, trace_id, environment, workspace_id). Merged with defaults from st.init.
wait_for_escalationboolFalseIf True, poll until a human resolves an escalate decision.
escalation_poll_intervalfloat3.0Seconds between escalation status polls.
escalation_timeoutfloat300.0Max seconds to wait for an escalation to resolve.
timeoutfloat10.0HTTP request timeout. Override globally with STASO_GUARD_TIMEOUT.
fail_closedbool | NoneNoneOn transport failure, return block instead of allow. Defaults to STASO_GUARD_FAIL_CLOSED.

Fail-open vs fail-closed

By default, transport failures (network down, timeout, unreachable backend) return action="allow" — Guard does not want to brick your agents because of a flaky backend.

Opt into fail-closed behaviour two ways:

# Per call
result = st.guard("delete_user", {"id": "u_42"}, fail_closed=True)
# Process-wide
export STASO_GUARD_FAIL_CLOSED=1

Fail-closed is the right choice for destructive or irreversible actions. See environment variables.

GuardResult fields

Every call returns a GuardResult dataclass:

FieldTypeDescription
actionstrOne of allow, block, modify, escalate.
reasonstrHuman-readable explanation from the rule that fired.
rule_namestr | NoneName of the first rule that triggered.
severitystrlow, medium, high, or critical.
resultslist[GuardRuleResult]Per-rule breakdown (rule id, name, passed, score, action, reason).
modified_inputdict | NoneRewritten input for action == "modify".
modificationslist[dict]Diff of changes made to the input.
escalation_idstr | NoneEscalation handle for action == "escalate".
latency_msfloatEvaluation latency (includes network round-trip).
rules_triggeredlist[str]Names of every rule that contributed to the decision.

Plan quotas

Manual calls count toward the same monthly quotas as integration calls. Free no_plan orgs get a 403; Personal and above have the quotas listed in Rules and Policies.

Next