- Request — fires against the inbound payload (the user prompt, tool arguments) before the call is made.
- Response — fires against the outbound payload (the model’s completion, a tool result) after the call returns.
phase that selects which side it runs on.
Operators choose the phase in the dashboard when they create or edit a
rule; the SDK enforces the choice at evaluation time.
The phase names are call-relative on purpose — they read correctly
for every governed surface: model calls, tool calls, MCP calls, and
Gateway traffic. Earlier releases spelled them
pre_model /
post_model; those spellings are still accepted everywhere and
normalize to request / response (see
Backward compatibility).Before either phase runs, the SDK applies an operator pause check.
When an agent is paused from the dashboard, every call from that agent
short-circuits with a
block verdict before Phase 1 runs — no
policies are evaluated, no LLM token is spent, no prompt text is
hashed. The pause takes effect SDK-side within seconds of the
operator clicking Pause.Why two phases
Some risks live on the request — a user pasting a Social Security number, or a script accidentally leaking an API key. Some live on the response — a model emitting a tool call to delete files, or returning a connector target that’s outside your allow-list. Splitting evaluation in two lets each rule run where it can actually do its job, without the SDK having to guess. It also lets the audit trail show exactly which side fired, which is what auditors care about during review.When each phase runs
Request phase
Runs before the upstream call is made. Local deterministic checks
run first (PII detection, regex denylists, prompt-size caps, model
allow-lists). LLM-backed intent checks run after, and only on a
sanitized copy if a deterministic rule asked for one. If a rule
blocks, the provider is never called and the response phase is
skipped.
Response phase
Runs after the call returns, with the same two-phase split
as the request side: local deterministic checks first (PII
detection, regex denylists, response-size caps, model allow-lists,
tool / shell / MCP rules), LLM-backed
semantic_guard after — and
only when no local rule already blocked. If a rule blocks, the SDK
suppresses the response before it reaches your code and the LLM
judge is never consulted on that response.Naming disambiguation: “phase” in this page means the
request/response side a rule runs on. The engine internally also
splits each side into “Phase 1” (deterministic checks) and
“Phase 2” (LLM-backed checks) — that ordering is a security
contract, not an operator choice, and is unrelated to the
phase
field on a rule. Once a local rule has refused the call, the LLM
judge is not invoked — no network call, no token spend, no chance
of the prompt or response reaching an external model. Operator-set
rule priorities reorder rules within Phase 1 or Phase 2 but never
across the split.Phase × type matrix
Every rule type accepts every phase. The dashboard offers all three choices for any rule, and the engine evaluates each rule on whichever side it has meaningful signals for. The table below shows which combinations actually fire and which silently no-op.For
injection_scan, put it on both sides or the response side.
The request side catches a user typing “ignore your instructions”.
The response side catches the fetched web page, the Jira comment, or
the PDF that says it instead — text your agent read but nobody on
your team wrote. That is where real attacks arrive, and a
request-only rule misses all of them.Scoring is deterministic: compiled patterns plus two character-class
counts, no model and no network, so it runs in phase 1 on every call.
The default action is flag — the finding lands on the audit row and
the call proceeds — because nobody can predict what their own tool
results look like until they have watched a week of them.both, it runs on each side independently
and contributes one match to whichever phases fired. pii_scan set
to both will, for example, sanitize the prompt and block any PII
the model echoes back in its response.
Surface scoping (applies_to)
Orthogonal to phase, a rule can be scoped to specific call
surfaces with applies_to:
An empty / omitted
applies_to means the rule applies on every
surface — the behavior every rule had before surface scoping
existed, so existing policies are unaffected. A rule can combine
both dimensions: for example, fire on the response side of tool
calls only.
Surface scoping requires SDK ≥ 0.32.0. Older SDKs ignore the
field and apply the rule on every surface — over-application,
which is the safe direction.
Audit shape
The audit event records each phase’s decision independently:verdict is the dominant outcome (precedence
block > sanitize > allow). The two nested blocks describe what each
phase actually saw.
When the request phase blocks, the model is never called, so
response_decision is null on the audit row. The dashboard renders
that as a single request decision card, with no response card.
Backward compatibility
Therequest / response spellings replaced pre_model /
post_model in SDK 0.32.0 and the matching platform release. The
rename is fully backward compatible in both directions:
- The platform accepts the legacy spellings on every write path and normalizes them to the new names.
- SDK ≥ 0.32.0 accepts both spellings on the wire and normalizes internally.
- For fleets still running older SDKs, the platform serves the
legacy spellings on the SDK policies endpoint until the operator
flips the compatibility flag — older SDKs coerce unknown phase
values to
both, and serving them new names early would silently over-enforce. - Policy version history keeps whatever spelling was recorded at the time; the API normalizes on the way out.
both, and the
backend back-fills prompt_verdict from the legacy verdict column
so historical rows still render in the current column layout.
Choosing the right phase
A few rules of thumb:- If the rule looks at the inbound payload only (an inbound PII scan,
a prompt-size cap, an intent check on what the user asked for),
choose
request. - If the rule looks at the outbound payload only (a tool name, a shell
command, a connector target, an outbound PII scan), choose
response. - If you want the rule to enforce on both sides — for example,
scanning for PII in both the prompt and the response — choose
both. Most text-content rules support this naturally. - Tool / bash / MCP rules belong on
response; onrequestthey silently no-op because the SDK doesn’t carry tool definitions into the request-side context yet.
What’s next
Verdicts
Allow, sanitize, and block — what each one means in detail.
Policies
Categories of rules and where they’re configured.