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,
) -> GuardResultParameters
| Parameter | Type | Default | Description |
|---|---|---|---|
tool_name | str | required | Name of the tool being called. |
tool_input | dict | required | The tool's input arguments. |
context | dict | None | None | Optional context (conversation_id, agent_name, trace_id, environment, workspace_id). Merged with defaults from st.init. |
wait_for_escalation | bool | False | If True, poll until a human resolves an escalate decision. |
escalation_poll_interval | float | 3.0 | Seconds between escalation status polls. |
escalation_timeout | float | 300.0 | Max seconds to wait for an escalation to resolve. |
timeout | float | 10.0 | HTTP request timeout. Override globally with STASO_GUARD_TIMEOUT. |
fail_closed | bool | None | None | On 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=1Fail-closed is the right choice for destructive or irreversible actions. See environment variables.
GuardResult fields
Every call returns a GuardResult dataclass:
| Field | Type | Description |
|---|---|---|
action | str | One of allow, block, modify, escalate. |
reason | str | Human-readable explanation from the rule that fired. |
rule_name | str | None | Name of the first rule that triggered. |
severity | str | low, medium, high, or critical. |
results | list[GuardRuleResult] | Per-rule breakdown (rule id, name, passed, score, action, reason). |
modified_input | dict | None | Rewritten input for action == "modify". |
modifications | list[dict] | Diff of changes made to the input. |
escalation_id | str | None | Escalation handle for action == "escalate". |
latency_ms | float | Evaluation latency (includes network round-trip). |
rules_triggered | list[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
- Actions and Escalation — what each action means in practice.
- Error Handling — catching
GuardBlocked. - Environment variables
Guard Quickstart
Turn Guard on by patching your LLM SDK — tool calls are evaluated automatically, blocked calls raise GuardBlocked, modified calls are rewritten in place.
Rules and Policies
Guard ships with proprietary static rules and LLM-judge rules out of the box, plus your own custom rules bundled into policies — all manageable from the Staso dashboard.