Staso Docs
Guard

Guard direct tool calls

Use st.guard() when your code dispatches a tool directly instead of receiving a proposal from a patched provider response.

import staso as st

tool_input = {"record_id": "r_42"}
decision = st.guard("delete_record", tool_input)

if decision.action == "block":
    raise RuntimeError(decision.reason)

delete_record(**tool_input)

@st.tool records the function execution. It does not run this check automatically.

Launch parameters

st.guard(
    tool_name: str,
    tool_input: dict[str, Any],
    *,
    context: dict[str, Any] | None = None,
    decision_id: str | UUID | None = None,
    timeout: float = 10.0,
    fail_closed: bool | None = None,
) -> GuardResult
ParameterDefaultDescription
tool_namerequiredTool name evaluated by configured policies.
tool_inputrequiredProposed arguments.
contextNoneExplicit trace, span, session, agent, environment, or workspace identifiers.
decision_idgenerated UUIDReuse an explicit UUID only when retrying the same logical decision.
timeout10.0Guard request timeout in seconds.
fail_closedNoneUses STASO_GUARD_FAIL_CLOSED; false when unset.

Result handling

For the launch path, handle two wire outcomes:

  • allow: no configured rule blocked the proposal. Continue.
  • block: stop the action and use reason plus rules_triggered for recovery or logging.

Audit findings keep the top-level result at allow and appear in the per-rule results list as failed audit checks.

Every result also reports whether Guard made a policy decision:

FieldMeaning
decision_idUnique decision identity echoed by Guard and safe to use for correlation.
decision_sourceAuditable origin label returned by Guard, such as manual, a native provider, or the generic sdk path.
decision_availableTrue for a policy decision; False for explicit degradation.
evaluation_statusevaluated, skipped, abstained, or unavailable. Per-rule results also carry this field.
failure_kindStable failure category, or None when a decision was available.
failure_modefail_open or fail_closed, the policy applied to degradation.
retryableWhether retrying later can reasonably succeed.

Use each rule result's is_finding property to select evaluated policy matches. Skipped, abstained, and unavailable results are not findings. Non-finding rule results keep action="allow"; an aggregate unavailable decision can still block under fail-closed.

Stable failure kinds cover missing or rejected keys, inactive plans, forbidden access, rate limits, malformed responses, deadlines, backend or network outages, and unsupported server actions. Do not parse reason to classify a failure.

capacity_exhausted means the organization lacks enough remaining allowance for the request. If Python-run capacity is insufficient, Guard rejects the whole request before any rules execute and returns an unavailable decision. The SDK applies the configured fail-open or fail-closed behavior. Check the reset date and remaining capacity on Usage. See pricing and limits.

Each call generates a new decision_id, including identical checks in the same trace. If your application retries one logical check after an uncertain transport outcome, pass the same explicit UUID so backend persistence remains idempotent. Provider and CLI integrations reuse the decision identity when the same genuine tool invocation is observed again in the same trace. Distinct tool calls keep distinct identities even when their arguments are identical. Calls without a genuine invocation identifier receive fresh identities.

Automatic trace linkage

A check inside @st.agent, @st.tool, or a manual span automatically carries the active trace and span identifiers. You can add non-empty context such as environment or workspace fields:

@st.agent(name="cleanup-agent")
def cleanup(record_id: str) -> None:
    decision = st.guard(
        "delete_record",
        {"record_id": record_id},
        context={"environment": "production"},
    )
    if decision.action == "block":
        raise RuntimeError(decision.reason)
    delete_record(record_id)

Active trace, span, and session identifiers remain authoritative if a stale explicit value is supplied. Empty context values do not erase client or active defaults.

Evidence and capture

Guard requests carry payload_version=1. The context_availability map inside context describes available tool input, tool schema, conversation history, and tool output. The current tool's output is unavailable because Guard runs before execution. Available history is bounded and explicitly partial.

Supported provider adapters attach context.tool_call_evidence with the argument parsing status and the exact current-request schema, when available. Static checks use this evidence. Manual calls do not infer parsing failures from payload keys or Python annotations.

Capture settings apply before Guard transmission. Disabling tool-argument capture sends no proposed arguments to Guard; disabling schema capture sends no schema. History respects request, system-prompt, argument, and output controls. Rules that need missing evidence skip or abstain.

Fail-open and fail-closed

An unavailable decision returns allow by default so authentication, service, response, or deadline failures do not stop the host application.

Use fail-closed for an action that must stop when Guard cannot return a decision:

decision = st.guard(
    "delete_user",
    {"user_id": "u_42"},
    fail_closed=True,
)

Or set it for the process:

export STASO_GUARD_FAIL_CLOSED=true

With fail-closed enabled, an unavailable decision returns block. Check decision_available to distinguish that degradation from a policy block. Patched provider integrations surface either effective block as st.GuardBlocked.

Provider blocks

try:
    response = client.chat.completions.create(...)
except st.GuardBlocked as blocked:
    for item in blocked.blocked_tools:
        print(item.tool_name, item.result.reason)

Provider tool proposals are evaluated concurrently under one batch deadline, with four evaluations active by default. STASO_GUARD_BATCH_TIMEOUT and STASO_GUARD_MAX_CONCURRENCY configure the integration defaults. Results and blocked_tools stay in provider input order.

Next