Skip to main content
Every governed call is evaluated in two clearly-separated phases:
  • 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.
Each policy rule carries a 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.
identity_guard is the one rule type the SDK does not enforce. It asks your connected directory whether the person a call acts for is still active, and the answer changes while your process is running — an in-process cache would enforce Friday’s directory on Monday while still reporting a green check.So it runs at the control points that hold a live database connection: the gateway and the MCP proxy. Agents that call providers directly through the SDK are not covered by it, and GET /v1/sdk/policies deliberately withholds the rule rather than shipping one that would silently do nothing. The policy editor says the same thing next to the type.
When a rule is set to 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:
The top-level 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

The request / 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.
Independently of the rename: SDK and platform versions that pre-date the two-phase split treat every rule as if it were 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; on request they silently no-op because the SDK doesn’t carry tool definitions into the request-side context yet.
Operators can always change the phase later — switching is a simple edit in the dashboard, and the SDK picks up the new phase on its next refresh without a redeploy.

What’s next

Verdicts

Allow, sanitize, and block — what each one means in detail.

Policies

Categories of rules and where they’re configured.