The guardrail pipeline
The whole pipeline is reachable from a detector and a rule. This is a complete policy, and it refuses any request carrying a card number:
guardrails: detectors: - type: pii entities: [CREDIT_CARD] rules: - name: block-cards action: denyThe detector says what to look for. The rule says what to do about it,
and with no when it fires on whatever was found. Every other concept
on this page is a default that configuration took: which parts of the
request the detector read, when it ran, whether policy listened to it,
and how a span would have been rewritten. Each is named here so it can
be changed on purpose.
Guardrails process each request in five stages. They select content, run detectors, produce annotations, schedule work, and apply policy. Each stage has a separate configuration and result.
A segment is an addressable piece of content
Section titled “A segment is an addressable piece of content”The engine works on segments, not whole requests. Each segment is a
span of text at a path into the body, messages[3].content,
tool_calls[0].function.arguments, result.content. Each one is
tagged with a kind: system, user, assistant, tool_args,
tool_result, output, header, query.
Each finding carries its path. A redaction edits that value without re-serializing the request. See Passthrough.
The kind narrows a rule. “Redact this in what the user sends but not in
what the tool returns” is a kinds: list. The two read-only kinds stay
read-only the same way. Naming header or query in a redact rule is
refused when the configuration compiles.
The compiler rejects header and query in redact rules. A dynamic
select predicate cannot make those read-only values writable.
A response is inspected the same way, but only if you ask, apply_to: [output]. A streamed response is inspected against
the commit horizon, so a stream is checked without
being buffered.
At the A2A door the body is a JSON-RPC message and the paths are its
own, params.message.parts.0.text going out and
result.artifacts.0.parts.1.data.pnr coming back. The engine reads
each part’s text, a file part’s URL and filename, and structured data
leaf by leaf. Bytes carried inline in a part are left alone, because a
base64 blob is not text. A method that only names a task yields no
segments: GetTask, ListTasks, CancelTask and SubscribeToTask
carry ids and no content.
Coming back it reads what the peer produced: a message, a task’s status
message, its artifacts, its history, and each streamed event as a whole
frame. A JSON-RPC error’s message and data are read as well, because a
peer says things in a failure too. Sent parts are tagged user and
everything read back is tagged output, so a rule written with
kinds: reaches this door without naming it. See
Front a peer agent.
Detectors annotate. They never decide.
Section titled “Detectors annotate. They never decide.”Six types, and all of them produce only annotations:
| type | what it is | annotates |
|---|---|---|
pii |
103 recognizers with their checksums and context words, plus your own patterns | pii, credential |
nlp |
a token-classification checkpoint from the Hub, in this process or on a GPU box | pii |
remote |
any presidio-analyzer you already run, over its own /analyze |
pii |
classify |
a sequence classifier, an injection or content-safety checkpoint, scoring each segment | injection |
llm |
a chat model asked whether a policy written in prose applies | injection |
embed |
an embedding model and your own example sentences per topic | topic |
The category in that last column is the default. A detector can be told
to file its findings under another one. It matters because a rule
matches on it. category separates “a credit card was found” from
“this looks like a jailbreak” when both arrive as the same struct.
Detectors do not deny requests. They produce annotations. CEL rules apply those annotations and define the gateway’s decisions in one place.
Annotations are the common currency
Section titled “Annotations are the common currency”Every detector, whatever it is underneath, produces the same struct: a category, an entity type, a byte span, a score, optionally the reasoning that produced it, and the address of the segment it belongs to.
The shape extends Presidio’s RecognizerResult with two fields. A
remote detector can return it without another translation step.
Scores combine findings from multiple detectors. A recognizer’s own confidence is lifted when its context words appear beside the match, and lifted again when a second detector found the same span. So a profile that drops a detector can quietly lower the score of a finding some other detector made, and a rule declares what it depends on.
Schedule and mode are two axes, not one
Section titled “Schedule and mode are two axes, not one”They get confused because both sound like “how seriously do we take this”, and they are answers to different questions.
Schedule is when the detector runs, and it is a latency decision:
sync makes the request wait, overlap runs it alongside the upstream
call, async enforces from the next turn. Schedules is
a page of its own, because the concessions are not the same shape.
Mode is whether it enforces at all. A detector in mode: shadow
annotates and records what would have happened, and nothing is denied or
rewritten on its findings. That is how a new detector earns its way in,
on the evidence it recorded before it enforced anything.
A detector can be sync and shadow, or async and inline. Neither
combination is a mistake, and the axes are separate for that reason.
Rules are CEL over the annotations
Section titled “Rules are CEL over the annotations”A rule has a condition, an action, and, if the action is redact, an
operator saying how the span is rewritten.
There are three actions, and allow is not one of them:
denyrefuses the request with the rule’s message, and ends evaluation immediately.redactrewrites the spans itsselectpredicate picks.annotaterecords and changes nothing. A rule written to produce evidence rather than enforcement uses it.
A request is allowed when no rule acts, and that is the same
default the access rules run on. The engine
evaluates every rule rather than stopping at the first match: a deny
short-circuits, an annotate only takes effect if nothing stronger
has, and redactions accumulate. One policy can rewrite several entity
classes with different operators in a single pass.
Seven operators do the rewriting. Four are presidio-anonymizer’s, name
and behaviour alike, replace, redact, mask, hash. Three are
this gateway’s, because none of upstream’s can keep two distinct values
distinct. Presidio’s replace turns every PERSON in a prompt into
the same <PERSON>, so a request about two people arrives about one:
placeholdernumbers the spans per entity type,<PERSON_1>,<PERSON_2>, consistently within one request and meaninglessly across them.pseudonymsubstitutes a plausible stand-in derived from a key, so one value maps to one stand-in everywhere that key is in force.fpeencrypts the span into another span of the same shape, and is the only reversible one.
fpe preserves the span’s shape. The provider receives a card number
that still passes Luhn, so a
model reasoning about the request still works, and the original can be
restored on a hop that is authorized to see it. Restoration is not an
operator a rule may choose. It is an authorization decision about a
destination, so it is not spellable in a policy file.
When a rule cannot be evaluated
Section titled “When a rule cannot be evaluated”A CEL condition fails when it reads a key that is not there,
key.metadata.team on a key with no team, or when it exceeds its cost
budget over a body the client sized.
The default is fail_closed, and the second case is why. A deny rule
that a large enough argument list could switch off is not a deny rule,
and the argument list is the client’s to make large.
What this does not cover
Section titled “What this does not cover”Authorization covers which detectors a given team’s traffic runs and why that selection is gated. Measure a detector covers what a detector costs and how to measure whether it is any good. The threat model covers what is claimed about any of this under attack.
Related
Section titled “Related”- Schedules, sync, overlap and async, and what each concedes
- The commit horizon, how a stream is inspected without being buffered
- Authorization, where guardrails sit among the four gates
- PII coverage, every kind of personal data the built-in recognizers find
- Credential coverage, the API key formats the same detector finds