Skip to main content
Phase 7 added four built-in runtime guardrails that run inside the policy engine before a @guarded_action executes. Each guardrail is configured per-policy, raises a structured PolicyViolationError (or replays a cached value, in the case of idempotency), and emits a typed intervention so the console can render a dedicated detail panel. All four are stdlib-only by default. Moderation can opt into a Gemini-backed semantic moderator; everything else has zero new dependencies.

How guardrails are configured

Guardrail rules live on PolicyConfig (not on PolicyEngine.configure(), which is the convenience wrapper for budgets / rate-limits / evidence). You attach rules per-action via the pii_rules / moderation_rules / loop_rules dicts, or flip the matching *_default_enabled flag to apply a default rule to every action.
When the platform’s policy sync is enabled, the same per-action rules can be authored in the platform UI and pushed down to the SDK — no local configuration needed.

PII detection

Walks @guarded_action kwargs recursively and blocks the call if any string field matches a PII pattern. Detected categories: email, us_ssn, credit_card (Luhn-validated), phone_us, api_key_like, aws_access_key, private_key_block.
When a match is found the SDK raises PolicyViolationError with details.reason_code == "PII_DETECTED" and a matches array containing the field path, category, and a redacted preview (first/last 2 chars). Nothing in the matched text is logged in the clear.
Pattern checks are conservative — credit-card candidates are Luhn-validated, SSN ranges exclude reserved blocks, and api_key_like requires a known prefix or 40+ char base64. Add extra_patterns for site-specific identifiers.

Content moderation

Pluggable moderator that scans kwarg strings. The default KeywordModerator is fully offline; GeminiModerator uses gemini-2.5-flash to catch intent-level evasions (prompt injection, policy evasion, data exfiltration) that keyword matching misses.
GeminiModerator reads GEMINI_API_KEY (or GOOGLE_API_KEY) from the environment, retries up to three times on transient 429/5xx, caps input at 2,000 chars, and degrades gracefully (returns “not flagged” with a logged warning) if the key or google-genai package is missing. Strictness modes:
  • strict — every flagged result blocks
  • balanced — only flagged categories that appear in block_categories block
  • permissive — explicit category matches only
The platform also runs Gemini-powered intervention enrichment: even if the SDK uses KeywordModerator locally, blocks reaching the platform get a plain-English reason, agent_intent, and suggested_rewrite rewritten by Gemini. See LLM integrations.

Loop protection

Detects tight loops where the agent calls the same action with semantically identical arguments N times within a sliding window. Distinct from rate limits, which count calls regardless of arguments.
The detector hashes each kwargs dict (excluding the keys in arg_exclude) with SHA-256 and keeps a sliding deque per (action, arg_hash). When the deque exceeds threshold entries inside window_seconds, the SDK raises PolicyViolationError carrying a LoopDetection payload:
The break_out_hint is fed back into the LLM via self-repair feedback so the model can correct its plan rather than hammering a failed action.
Loop detection is in-process only. Distributed loops across worker processes are out of scope for this primitive — use rate limits backed by a shared store for that case.

Idempotency

Caches the result of an action keyed by (run_id, idempotency_key). A second call with the same key inside the TTL transparently returns the cached value and is recorded as an IDEMPOTENT_REPLAY intervention — the function body never runs twice.
idempotency_key accepts either a static string or a callable invoked with (*args, **kwargs). If it returns None or empty the call falls through and executes normally.
Idempotency is scoped per run by default. If the SDK isn’t inside an ExecutionContext, the cache scope falls back to a sentinel __no_run__ bucket. Pair with ExecutionContext to keep cached results from leaking across logical runs.
The cache is in-process and thread-safe. For distributed deduplication you currently still need an upstream store (Redis, the platform’s commit-repeat detector); a shared backing store is on the roadmap.

Putting it together

Every guardrail is independently toggleable: omit the rule field to disable it. The platform UI surfaces blocks under Console → Interventions with dedicated detail panels per guardrail type.

See also