The @guard Decorator
Wrap any function to enforce cost, step, loop, and policy guardrails in-process.
The @guard decorator is the simplest way to put an agent under SteerPlane control. It wraps the
function that runs your agent and routes every step through the guardrail pipeline — without
changing the agent logic itself.
from steerplane import guard
@guard(
agent_name="support_bot",
max_cost_usd=10.00,
max_steps=50,
denied_actions=["delete_*", "drop_*"],
enforcement="alert",
alert_threshold=0.8,
alert_timeout_sec=1800,
)
def run_support_agent():
agent.run()Parameters
| Parameter | Type | Description |
|---|---|---|
agent_name | str | Human-readable name shown in the dashboard and logs. |
max_cost_usd | float | Per-run cost ceiling in USD. Overshoot is bounded to one step. |
max_steps | int | Maximum number of execution steps before termination. |
denied_actions | list[str] | Glob patterns for actions to block (e.g. delete_*). |
enforcement | "kill" | "alert" | How to react to a breach. See enforcement modes. |
alert_threshold | float | Fraction of a limit (0–1) at which alert mode pauses for approval. |
alert_timeout_sec | int | How long to wait for human approval before terminating. |
Context-manager API
For finer control — logging individual steps, custom action names, or nested runs — use the
SteerPlane context manager instead of the decorator:
from steerplane import SteerPlane
sp = SteerPlane(agent_id="support_bot")
with sp.run(max_cost_usd=10, max_steps=50) as run:
run.log_step("search_web", tokens=1200, cost=0.004)
run.log_step("summarize", tokens=800, cost=0.002)Both APIs enforce the same guarantees: loop detection, cost ceilings, step limits, and policy rules.
Loops and policy violations always hard-terminate the run, regardless of the enforcement
setting — they are a non-overridable safety invariant.
Getting the active run
Inside a guarded function (or anywhere in its call stack), get_active_run() returns the
currently running RunManager — useful in helper functions that don't have direct access to the
run object created by sp.run(...).
from steerplane import get_active_run
def helper():
run = get_active_run()
if run:
run.log_step("helper_call", tokens=50)Exceptions reference
Every guardrail raises a subclass of SteerPlaneError when it fires:
| Exception | Raised when |
|---|---|
LoopDetectedError | A repeating action pattern is detected by the loop detector. |
CostLimitExceeded | Cumulative run cost exceeds max_cost_usd. |
StepLimitExceeded | The number of steps exceeds max_steps. |
PolicyViolationError | An action is blocked by the policy engine (deny list, missing from allow list, rate limit, or denied approval). |
RunTerminatedError | A run is forcefully terminated (e.g. manually via the CLI or dashboard). |
APIConnectionError | The SDK cannot reach the SteerPlane control-plane API (only relevant when not running fully offline). |
SteerPlaneError | Base class for all of the above — catch this to handle any guardrail violation generically. |
from steerplane import SteerPlaneError, CostLimitExceeded, LoopDetectedError
try:
run_support_agent()
except CostLimitExceeded as e:
print(f"Stopped at ${e.current_cost:.2f} (limit ${e.max_cost:.2f})")
except LoopDetectedError as e:
print(f"Loop pattern {e.pattern} in last {e.window_size} actions")
except SteerPlaneError as e:
print(f"Guardrail violation: {e}")Works offline
If the SteerPlane control plane is unreachable, the decorator keeps enforcing locally
(loop detection, cost, step, and policy rules), and alert mode automatically degrades to
kill mode. See graceful degradation.