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_*"],
)
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. "kill" is the default and the only mode the free self-hosted API implements — see enforcement modes. "alert" requires a hosted/enterprise backend. |
alert_threshold | float | Fraction of a limit (0–1) at which alert mode pauses for approval. Hosted/enterprise only. |
alert_timeout_sec | int | How long to wait for human approval before terminating. Hosted/enterprise only. |
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). If enforcement="alert" is set but no backend
implements the approval workflow, the run fails closed and terminates safely rather than
continuing unprotected. See graceful degradation.