Module Deep Dive

Guardrails

What runs at the boundaries of every agent turn — injection detection, PII masking and restoration, content safety, and the audit trail behind all of it.

Overview

Read this first — especially the part about what runs even when you configure nothing.

Guardrails are the checks that run on the way in and on the way out of every agent turn. On the way in they decide whether a request is safe to process at all and what must be hidden from the model. On the way out they decide whether a generated answer is safe to return, and put back anything that was hidden.

The most important thing to understand is that guardrails are not purely opt-in. A hardcoded global safety baseline runs on every single request — direct chat, cached turns, everything — with no configuration and no database record behind it. Configured guardrails layer on top of that baseline; they never replace it. An agent with zero guardrails attached is still screened for prompt injection.

Everything a guardrail does is recorded. Blocks, redactions and warnings all land in a dedicated violations collection and in run analytics — including the baseline's own blocks, which are given a sentinel identity precisely so they appear alongside configured rules rather than living in a separate silo. Raw sensitive values are never written to any of it.

The two boundaries

Where each layer sits relative to the model, and where PII is hidden and restored.

runs with zero config five rules six rules PII masked PII restored User Input Global Safety Baseline Configured Input Rules Agent Generation Configured Output Rules Response
The baseline is unconditional; configured rules layer on top of it

Anatomy of a guardrail

One record holds a title, a lifecycle status, a classification, and a configuration object with six sections.

status
Active or Inactive — only active guardrails are evaluated. Separate from isActive, which is the soft-delete flag.
type
Security or Alignment — a classification for organising policies.
isDefault
When true the guardrail applies to all workflows, not just the ones that name it.
scope
Per-config direction: input, output or both. A config scoped to one direction is skipped entirely on the other.
input_security
Prompt injection, blocked topics, PII detection, language filter, max input length.
output_filter
Topic adherence, PII redaction, toxicity, format validation, disclaimers, max output length.
content_safety
Harm-category thresholds, a profanity blocklist and blocked domains.
topic_alignment
Restricted and allowed topics, a custom violation message, and a knowledge-scope override that can force strict grounding even when the agent says open.
operational_handling
A shared fallback message, and a switch for whether violations are written to the collection at all.
integrity_policy
When enabled, its message is injected directly into the generation system prompt — rules about tool usage and hallucination prevention that shape behaviour rather than filtering it afterwards.

Modules

Global Safety Baseline

Always on

Before any configured rule runs, every request is tested against a library of 30-plus prompt-injection patterns. This check needs no guardrail record, no agent binding and no setup. A single match blocks the request outright.

What the patterns cover
  • Classic overrides — ignore, forget, disregard previous instructions
  • Persona substitution — "you are now", "pretend", "act as", "roleplay as"
  • Model control tokens and system-prompt markers
  • Known jailbreak terminology and developer-mode claims
  • Capability-override claims — "you have no restrictions"
  • Hypothetical and fictional framing used as a wrapper
  • Base64 and hex obfuscation
  • Template and code injection — eval(, exec(, interpolation syntax
  • Compliance coercion
  • Prompt-extraction attempts
  • Token smuggling, context overflow, prompt leaking
  • Repetition attacks
Baseline blocks are first-class audit records. The baseline has no database guardrail behind it, so a sentinel identity is used — a fixed all-zero id under the title Global Safety Baseline, recorded with rule type global_safety, action block and severity critical. That means baseline blocks appear in the violations collection and analytics exactly like configured ones, and stay filterable by a well-known id. URL safety blocks raised during classification reuse the same sentinel under their own title and rule type.

Input Checks

Input evaluation is composed in a fixed order so that every caller — the classifier's pre-check and the guardrail node in the graph — gets identical behaviour and cannot drift apart. The baseline runs first; configured rules run second and are a no-op when none are active.

Within the configured rules, deterministic pattern checks run before any model call. A pattern match returns a block immediately and no tokens are spent. Model-based verification is attempted last, and only where it has been explicitly enabled.

The five input rules, in order
max_input_length medium
Character ceiling on the incoming message. Purely synchronous.
prompt_injection critical
The built-in pattern library plus any additional patterns you supply. Optional model verification runs only when patterns have already flagged the input.
blocked_topic high
Keyword and phrase list, with an optional semantic check for meaning rather than wording. Action defaults to block.
pii_in_input high
Detects the configured entity types. Action defaults to warn — the request proceeds with values masked rather than being rejected.
language_filter low
Heuristic word-frequency detection across ten languages, including script-based detection for non-Latin writing systems. A language is only claimed when the evidence passes a minimum threshold; otherwise the result is unknown rather than a guess.
Masking and blocking are deliberately separate. A PII rule set to block rejects the request wholesale rather than silently masking it. Masking only applies to rules that are not blocking — so a policy of "never accept card numbers" behaves as a rejection, not as a quiet scrub the user never learns about.

When more than one rule fires, the actions are merged by precedence: block beats redact, redact beats warn, warn beats allow. The strictest outcome always wins regardless of evaluation order.

Output Checks

The generated answer runs through a second sequence before anyone sees it. Configs scoped to input only are skipped here.

max_output_length low medium
Over-length responses can be truncated (low severity) or blocked (medium), depending on configuration.
pii_in_output high
Redacts configured entity types from the generated text, so the model cannot leak something it inferred or retrieved.
toxicity critical
Pattern library with per-entry severity and category, plus an optional model escalation. Can block or redact, with its own fallback message.
topic_adherence medium
Keeps responses within an allowed topic list, with an optional semantic judge and a custom fallback.
format low
Validates the response against an expected shape — text, markdown, JSON or XML. Warns by default; strict mode blocks.
confidence_disclaimer low
Appends a disclaimer when the response contains configured uncertainty keywords. Required disclaimers can also be appended unconditionally.
The toxicity library

Ten pattern entries, each carrying its own severity and category rather than a single flat list. The critical tier covers extreme violence, terrorism, self-harm and other-harm instruction, weapons manufacturing, drug synthesis, and child sexual abuse material. The high tier covers sexual violence content, harassment and stalking, hate content, and self-harm method-seeking.

A configurable threshold governs how much pattern density is needed before the filter engages, and a model classifier can be escalated to only after that threshold is met — so the expensive check is never the first thing that runs.

The PII Engine

Six entity types are recognised: email, phone, ssn, credit_card, ip_address and passport. When a rule enables PII handling without naming types, all six apply.

The patterns are more careful than a naive regex set. Phone numbers are matched internationally — including one-digit area codes — and then filtered by total digit count to the E.164 range of 7 to 15, which discards short false positives the broad pattern would otherwise let through. Social security numbers exclude structurally invalid ranges, but deliberately include taxpayer identification numbers beginning with 9, which are equally sensitive. Card numbers accept spaced and dashed formatting across the major issuer families.

Overlap resolution — and the bug it fixes

The engine locates every candidate in a single pass over the original text, then resolves conflicts: on a clash the longest match wins, and an earlier start breaks ties so the scan stays deterministic and left-to-right.

This is not a theoretical nicety. Masking used to run one regex per type, each over the output of the last. The phone pattern matches any ten digits — so across a string containing an SSN followed by a card number it matched the SSN's tail plus the card's head and replaced that span. Neither the SSN nor the card pattern then matched what was left, and fragments of both went to the model unmasked. Because the phone type is evaluated before SSN and card in the default order, that was the default behaviour, not a corner case.
Restoration

Masking is only half the design. Each distinct value is allocated its own indexed placeholder — [EMAIL REDACTED] becomes [EMAIL_REDACTED_3] — so restoration is an exact lookup rather than positional guesswork. The placeholder is deliberately shaped with no spaces and in a form that matches none of the PII patterns itself, so it survives a second masking pass untouched.

One placeholder per value also means the model sees one stable identity for one real person across a turn — the same email always becomes the same token, which keeps the text coherent instead of reading as several different people.

Session scoping
Sub-agent session ids resolve to the parent's root store, so a value masked by a parent and returned by a sub-agent still restores. Keyed literally, those two never met — the placeholder shipped to the client unrestored while the sub-store sat on the raw value until it expired.
Counter sharing
Because the store is shared, the per-type counter is global to the run family — a parent and a sub-agent can never mint the same placeholder for different values.
First writer wins
A placeholder already standing for a value keeps that meaning rather than being silently repointed at a different one.
Selective restoration
An optional field allow-list controls which response fields are restored; nested objects and arrays inherit the decision made for the key enclosing them. An empty list means restore everything.
Expiry
Mappings expire on a 30-minute idle TTL, swept hourly, and can be cleared immediately after restoration. Nothing is persisted to a database.
Tool arguments
Values are also restored inside MCP tool arguments just before execution, so a downstream system receives the real value while the model never saw it.

Content Safety

Four harm categories can each be given their own block threshold: hate_speech, harassment, sexual_content and dangerous_content. Detected severity is rated none, low, medium or high, and the threshold names the minimum severity that triggers a block.

One threshold name is counterintuitive. BLOCK_NONE_AND_ABOVE does not mean "block everything" — it means the category is disabled. The three enforcing levels are BLOCK_LOW_AND_ABOVE, BLOCK_MEDIUM_AND_ABOVE and BLOCK_HIGH_AND_ABOVE.

Enforcement is two-tier and ordered for cost. A deterministic base check runs first against your own blocklist — there is no built-in word list, only terms you supply — along with exact and suffix domain blocking. The model-based harm classifier is escalated to only if the base check did not already block, so the expensive path is skipped whenever the cheap one has already decided.

The classifier prompt describes each category in neutral terms, and the source deliberately contains no explicit examples.

Fail-open versus fail-closed

This distinction matters more than any single rule, and the two model-based checks behave differently on purpose:

Fails open
The binary yes/no classifier used for injection verification, topic checks and similar. If the model is unreachable the check resolves in the permissive direction, preserving availability rather than taking the agent down with the provider.
Fails closed
The harm-severity classifier. If it errors or returns unparseable output, the caller blocks. Content safety is not allowed to degrade into permissiveness because a model call failed.

Violations & Audit

Every block, redaction and warning is written to a dedicated violations collection. Records carry the session, user and agent, the guardrail identity and title, the rule type, severity, action, direction, a detail string, and whether the request was actually blocked.

Content is never stored in full. Only a snippet capped at 300 characters is kept, enforced by the schema rather than by convention. For PII redactions the snippet that gets persisted is the masked version — the raw values never reach the audit trail. Reported redactions are categories and counts only, in the form 2×email, 1×ssn.
Designed for querying
  • Indexed by session, user, agent and guardrail
  • Compound index for session history, newest first
  • Compound index for per-guardrail history
  • Compound index for blocked events per agent
  • Unordered bulk insert so one bad record cannot drop the batch
  • Write failures are rethrown, never swallowed
In run analytics

Each run also records a per-direction summary alongside its token and cost figures — whether guardrails were evaluated at all, the outcome as passed, warn, modified, blocked or skipped, which check caused a block, and the rule types that fired with their severity and action. No content appears in analytics — only rule types.

Safety-check token spend is tracked per direction too, so the cost of running guardrails is visible separately from the cost of the answer itself.

How Guardrails Work — Flow Examples

Four diagrams. Severity labels beside each rule are the values actually recorded when that rule fires.

Input / output Data handling Model / decision Gate Result

Example 1 — The input path Input

The baseline first, then five configured rules in fixed order, then masking. Pattern checks resolve before any model call, so a blocked request costs no tokens.

injection pattern matched SEVERITY medium critical high high low restorable placeholders User message Global Safety Baseline Blocked Max input length Prompt injection Blocked topics PII detection Language filter Mask PII Masked query to model
Baseline → five rules → mask. The strictest action across all rules wins

Example 2 — The output path Output

Six rules on the way back, ending with restoration so the user sees real values that the model never held.

fallback message instead SEVERITY low / medium critical high medium low low Model output Max output length PII redaction Toxicity Blocked Topic adherence Format validation Confidence disclaimer Restore PII Response to user
Six rules, then restoration — the user sees real values the model never held

Example 3 — Overlap resolution in PII masking PII

Why candidates are collected first and resolved second. Running one regex per type over the previous result let adjacent values clip each other.

spans overlap — phone clips the SSN tail and the card head Raw text Collect all candidates phone span ssn span card span Longest wins · earliest ties Indexed placeholders Model sees masks only
One pass over the original text, then conflict resolution — so each value is masked as the entity it really is

Example 4 — Where a violation goes Audit

Three destinations, none of which ever receives a raw sensitive value.

level-filtered 300-char masked snippet rule type · severity · action Any violation Trace log Violations collection Run analytics No raw values in any of them
Baseline blocks land here too — the sentinel identity keeps them alongside configured rules

Reference Tables

Rules with the severity each records, the PII entity set, and the safety thresholds.

Rules by direction

Rule typeSeverityDefault actionModel call
Always on
global_safetycriticalblockNo
url_safetyhighblockClassifier stage
Input
max_input_lengthmediumblockNo
prompt_injectioncriticalblockOptional, after a pattern hit
blocked_topichighblockOptional semantic check
pii_in_inputhighwarnNo
language_filterlowwarnNo
Output
max_output_lengthlow truncate / medium blockconfigurableNo
pii_in_outputhighredactNo
toxicitycriticalblock or redactOptional escalation
topic_adherencemediumblockOptional semantic judge
formatlowwarn, block in strict modeNo
confidence_disclaimerlowappendNo

PII entity types

TypeDetection notes
emailStandard address form
phoneInternational and domestic forms, filtered to a 7–15 digit E.164 range to drop short false positives
ssnExcludes structurally invalid ranges; taxpayer identification numbers beginning with 9 are deliberately included
credit_cardMajor issuer families, with optional spaces or dashes between groups
ip_addressDotted-quad IPv4 with octet range validation
passportOne or two letters followed by six to nine digits

Content-safety thresholds

ThresholdEffect
BLOCK_NONE_AND_ABOVECategory disabled — nothing is blocked
BLOCK_LOW_AND_ABOVEBlocks at detected severity low, medium or high
BLOCK_MEDIUM_AND_ABOVEBlocks at detected severity medium or high
BLOCK_HIGH_AND_ABOVEBlocks only at detected severity high