Staso Docs
Guards

Rules and Actions

Guard rules are configured in your Staso dashboard. The SDK sends tool calls to the guard API, which evaluates them against your rules and returns a decision.

Rule Evaluation Flow

Your agent calls guard(tool_name, tool_input)
    |
    v
Staso evaluates all matching rules
    |
    v
Each rule returns: passed/failed, action, reason, score
    |
    v
Final decision: the strictest action wins
    |
    v
GuardResult returned to your code

Multiple rules can match a single tool call. The strictest action wins -- if one rule says "allow" and another says "block", the result is "block".

Actions

allow

The tool call passes all rules. Execute normally.

result = guard(tool_name="search_kb", tool_input={"query": "pricing"})
# result.action == "allow"
# result.reason == ""

block

The tool call violates a rule. Do not execute.

result = guard(tool_name="drop_table", tool_input={"table": "users"})
# result.action == "block"
# result.reason == "Destructive database operations are not allowed"
# result.severity == "high"
# result.rule_name == "no_destructive_db_ops"

modify

The tool call is allowed, but the input needs adjustment. The guard returns sanitized input.

result = guard(tool_name="send_email", tool_input={"body": "SSN: 123-45-6789"})
# result.action == "modify"
# result.modified_input == {"body": "SSN: ***-**-****"}
# result.modifications == [{"field": "body", "reason": "PII redacted"}]

Always use result.modified_input when the action is modify:

if result.action == "modify":
    execute_tool(**result.modified_input)

escalate

The tool call needs human approval before proceeding.

result = guard(tool_name="wire_transfer", tool_input={"amount": 50000})
# result.action == "escalate"
# result.escalation_id == "esc_abc123"

See Escalation for the full workflow.

Severity Levels

Every guard result includes a severity:

SeverityMeaning
lowInformational. Rule triggered but impact is minimal.
mediumNotable. Review recommended.
highCritical. Action was blocked or requires immediate attention.
if result.severity == "high":
    alert_team(result.reason)

GuardResult

The complete result object returned by guard():

from staso import GuardResult

result = guard(tool_name="...", tool_input={...})
FieldTypeDescription
actionstr"allow", "block", "modify", or "escalate"
reasonstrHuman-readable explanation of the decision
rule_namestr | NoneName of the primary rule that triggered
severitystr"low", "medium", or "high"
resultslist[GuardRuleResult]Individual results from each evaluated rule
modified_inputdict | NoneSanitized tool input (only when action is "modify")
modificationslist[dict]List of modifications applied to the input
escalation_idstr | NoneEscalation identifier (only when action is "escalate")
latency_msfloatTime taken for guard evaluation in milliseconds
rules_triggeredlist[str]Names of all rules that fired

GuardRuleResult

Each rule that evaluated the tool call returns an individual result:

for rule in result.results:
    print(f"{rule.rule_name}: {'passed' if rule.passed else 'failed'} ({rule.action})")
FieldTypeDescription
rule_idstrUnique rule identifier
rule_namestrHuman-readable rule name
passedboolWhether the tool call passed this rule
scorefloat | NoneConfidence score (0.0 to 1.0) if applicable
actionstrAction this rule recommends: "allow", "block", "audit", "modify", "escalate"
reasonstrWhy this rule triggered
latency_msfloatEvaluation time for this specific rule
violationslist[dict]Detailed violation data from this rule

Audit Rules

Rules with action "audit" evaluate and record results but don't affect the final decision. Use audit rules to test new rules in production before enforcing them.

# An "allow" result can still have audit violations
if result.action == "allow" and result.results:
    audit_failures = [r for r in result.results if not r.passed]
    if audit_failures:
        log.info(f"Audit: {len(audit_failures)} rules would have triggered")

In CLI agent integrations (Claude Code, Codex), audit violations create guard:would-block spans on your dashboard.

Timeout and Error Handling

result = guard(
    tool_name="my_tool",
    tool_input={"key": "value"},
    timeout=5.0,  # seconds, default is 10.0
)

If the guard API is unreachable or times out, the result is:

# result.action == "allow"
# result.reason == "Guard check failed: ..."

Guards fail open. A guard outage never blocks your agent from operating. Failures are logged as warnings.

Next