OPA policies
See OPA policies → for the conceptual model: the embedded policy engine, deny-overrides, and why an in-process policy engine adds no network hop. This page is the field-level reference: the Rego package each policy type declares, the policy editor, the input document a policy is evaluated against, and the two surface-level blocks (OPA Policy, VP Evidence) that turn evaluation on for a surface.
Evaluation layers
You create policies of three types, one per tab on the Policies page: Trust Fabric, LLM Policies, and IDE Policies. Each type’s Rego must declare a fixed package:
| Policy type | Rego package | Query path | Evaluated by |
|---|---|---|---|
| Trust Fabric tab | agentstream.policy | data.agentstream.policy.allow | Fabric traffic between appliances (gateway-to-gateway, DIDComm). It does not gate LLM Surface requests. |
| LLM Policies tab | llm.policy | data.llm.policy.allow | LLM Surfaces. |
| IDE Policies tab | ide.policy | data.ide.policy.allow | IDE Surfaces: the catalogue’s front door, and each member’s optional Model Policy. The front door is checked first. Once it passes, a member’s Model Policy, when set, is checked on that model. The member LLM Surface’s own policies still apply. |
On an LLM Surface, inbound policy runs after source authentication. VP evidence, when enabled, is verified first. The appliance-wide (global) policies for the surface type run next, then the surface’s own OPA policy. Both are deny-overrides and fail-closed, so a deny from either one blocks the request, unless the deny comes from a global policy set to Monitor only.
A policy definition’s type (Trust Fabric, LLM, or IDE) determines the package its Rego must declare, and a mismatch is rejected at write time.
A gateway can enforce more than one policy definition at once, and a surface’s own policy is combined with any global policies for its type. The set is evaluated deny-overrides: every member must independently return allow = true, and the first to deny (or error) blocks the request. An account with permission to publish policies, Administrator by default, can additionally mark one or more definitions of any type as enforced globally across every surface of that type (for Trust Fabric, every gateway connection), ANDed with each surface’s own set. Each global assignment carries its own monitor-only flag so a would-be deny is evaluated and logged, but does not block the request, while the policy’s effect is observed before it goes live.
A missing or failed-to-compile engine for an enforced gateway or surface fails closed: the request is denied rather than let through. This is recorded in policy audit evidence as a missing_engine outcome.
Rego companion reason rules
Alongside allow, a policy’s package may declare allow_reason and deny_reason rules (derived from the query path by swapping the trailing .allow) to supply a human-readable message that flows into policy audit logs and observability:
package llm.policy
default allow = false
allow if { input.source_auth.claims.role == "admin" }
allow_reason := "Admin bypass" if allow
deny_reason := "Only admin callers may use this surface" if not allowOn an allow with no allow_reason rule, reason stays absent. On a deny with no deny_reason rule, the fixed fallback "Policy denied request" is used.
A handful of Rego builtins are rejected at write time regardless of layer:
| Builtin | Rejected because |
|---|---|
numbers.range( | Can allocate an attacker-sized collection. |
http.send( | No network access from a policy. |
opa.runtime( | No runtime metadata. |
print( | No policy-authored output. |
Compiled policies also run under a fixed evaluation time limit and a fixed source-size limit, both non-configurable.
Policy definitions
A reusable Rego policy is what you create and edit on the Policies page: write it once, then reference it from as many surfaces (or a global assignment) as need the same rule, and every enforcement point picks up the exact version you save. Every save that changes the Rego is kept as a numbered version, listed in the editor’s Version history panel.
- Type has to match where you’ll enforce the policy: Trust Fabric for appliance-to-appliance traffic, LLM for LLM Surfaces, IDE for the IDE catalogue front door and its per-model policies. The Rego package has to match too, or the save is rejected. Type is locked once you save; changing it later means creating a new definition.
- Clear Enabled instead of deleting a policy you might reuse later. A disabled definition is skipped from any set that references it.
- Keep the sample input current as you edit the policy, so the Test (dry-run) panel keeps testing the case you’re actually trying to catch.
| Field | Default | Description |
|---|---|---|
| Name | — | Required. Human-readable name. |
| Description | None | What the policy does. |
| Type | — | Required. Which layer this definition may be applied to: Trust Fabric, LLM, or IDE. Its Rego must declare the matching package. Locked after creation. |
| Status | Enabled | The Enabled checkbox: whether this policy is compiled and enforced. A gateway or surface referencing a disabled definition skips it, and fails closed if that leaves its set empty. |
| Policy Content (Rego) | — | The Rego source of the current version, edited directly as text and checked for syntax as you type. |
Once a policy has been saved, the editor also shows two panels:
- Test (dry-run): runs the Rego currently in the editor, saved or not, against a sample
inputwith Run against the draft above. Nothing is enforced. The sample is saved with the policy. - Version history: every saved version, with its Version, Name, Created, Author, and Description, a current badge on the version in force, and a comparison of any version with the current one.
A save that changes the Rego body adds a new version and makes it the enforced one; a save that only changes metadata (name, description, enabled) updates the current version in place. Past versions are kept for as long as the policy exists. Bindings resolve a policy by its ID, so renaming a policy leaves everything that enforces it working.
Sample input
The sample input in the Test (dry-run) panel lets you re-run a stored request against your policy before saving an edit. A good sample:
- Is real JSON matching the input document shape below, not a partial fragment. A policy that reads
input.source_auth.claims.roleneeds a sample that actually populatessource_auth.claims, or the dry-run won’t reflect what a live request looks like. - Covers the specific caller and request shape your policy branches on. If your Rego has different rules for JWT-authenticated versus mTLS-authenticated callers, save (or keep swapping in) a sample for each path you need to check.
- Is updated whenever you add a new condition to the policy, so the dry-run keeps testing the case that condition is meant to catch, rather than a stale sample from an earlier version of the policy.
Global (appliance-wide) policy assignments
Held per policy type (Trust Fabric, LLM, IDE), enforced on every surface (or, for Trust Fabric, every gateway connection) of that type, independent of each surface’s own OPA Policy configuration and evaluated deny-overrides alongside it. It’s the right tool for an organisation-wide requirement, such as always checking a trust registry, that shouldn’t depend on each surface owner remembering to add it.
Global assignments are set on the Policies page itself: switch to the tab matching the policy’s type (Trust Fabric, LLM Policies, or IDE Policies), and each policy’s own row carries a Global switch. Making a policy global needs permission to publish policies, which defaults to Administrator; without it, the switch is disabled. Turning it on opens an Enforce globally confirmation; selecting Continue enforces that policy straight away across every surface (or gateway connection) of that type. The Monitor only checkbox then appears next to the switch.
- Before a new global policy goes live, check it with the Test (dry-run) panel. Once it is global, tick Monitor only straight away if you want to watch its decisions before it blocks traffic.
- Clear Monitor only once you’ve confirmed the policy’s decisions look right, so it actually enforces.
| Field | Default | Description |
|---|---|---|
| Global | Off | Toggle on the policy’s own row in the Policies list. Enforces this policy definition across every surface (or gateway connection) of its type. Needs permission to publish policies (Administrator by default). |
| Monitor only | Off | Shown once Global is on. When ticked, the policy is evaluated and its outcome recorded, but a would-be deny lets the request through. The row’s badge then reads Monitor instead of Global. |
Input document
Every policy layer evaluates against a single, versioned input document (schema version 1). Only the fields relevant to the current request are populated; everything else is omitted.
| Field | Populated when | Description |
|---|---|---|
| http | Always | Inbound HTTP method, path, and headers (sensitive headers containing authorization, cookie, or token substrings are stripped before injection). |
| request | IDE front door | purpose is discovery (GET /v1/models) or dispatch (POST /v1/chat/completions), letting a policy allow unauthenticated catalogue discovery while requiring auth to dispatch. forwarded_upstream_auth is true when the caller supplied a distinct upstream credential alongside the front-door token. |
| gateway | Gateway (Trust Fabric) layer | direction is inbound (self gateway) or outbound (remote gateway); source_id/target_id identify the connection points at each end of the exchange. |
| channel | Always | config_id/name identify the surface the request arrived on. variant_alias is set only when a surface’s own OPA policy evaluates traffic arriving over the Trust Fabric from another appliance. It carries the surface variant selected by the request URL (/route$alias/...). It is empty for the default variant, in Trust Fabric policies, and for requests sent directly to an LLM or IDE Surface. |
| source_auth | An authenticated caller | Exactly one of three shapes, depending on the surface’s authentication method: jwt_bearer (subject, claims), api_key (key_name), or mtls (principal, fingerprint, subject_dn, issuer_dn, sans). |
| mcp | MCP traffic | Normalised MCP request shape: method, tool_name, resource_uri, prompt_name, params. |
| extension_identity | Agent-identity extension present | The caller’s decentralized identifier and its hashed form, when the agent-identity extension resolved one. |
| payment | A payment extension is active | Whether the payment attached to this request was verified, and the response header used to communicate that back. |
| agent | Agent-card / A2A traffic | trust_verification is true or false when trust registry recognition ran, and has no value when recognition was unavailable. did is the caller’s decentralized identifier and agent_dna its agent-card payload. |
| metadata | Arbitrary extension metadata | Free-form key/value data attached by whichever extension populated it; shape varies by extension. |
| identity_binding | An LLM Surface with VP evidence enabled, when the caller presented a VP, or fabric traffic carrying a VP that verifies | The holder and issuer DIDs, a verified flag, and the VC claims, from the verifiable presentation sent with the request. On an LLM Surface, when the VP fails verification and Deny when no VP is presented is off, the binding is still present, with verified set to false and empty DIDs and claims. |
| resource | Model requests and realtime sessions, with each field populated only when relevant. Absent on IDE catalogue discovery and in a member’s Model Policy | Normalised request target: surface_type (llm or ide), model, reasoning_effort, reasoning_max_tokens, requested_models, requested_providers, tool, server_tools, requested_transforms, document_types, audio_formats, image_formats, route, modality. |
| action | A model request or a realtime session | dispatch for a request to a model, on an LLM Surface or through the IDE front door. connect when a realtime voice session opens. Absent on IDE catalogue discovery (GET /v1/models) and in a member’s Model Policy; use request.purpose there. |
| environment | Always | now is the RFC 3339 evaluation timestamp, for time-window policies. |
| schema_version | Always | Version of this input contract (1). |
Surface-level policy block
The OPA Policy block is the surface-level block, available on an LLM Surface, that connects a surface to the policies you’ve written and turns Rego enforcement on for its inbound traffic. There is no separate enabled toggle: dragging the OPA Policy element onto the canvas is what turns it on, and removing the element turns it off. When present and a policy is referenced, requests are evaluated after source authentication, with source authentication’s identity, the request’s headers, and everything else in the input document available to the Rego rule, and denied (403) on allow = false.
- Reference a single Policy Definition to enforce it on this surface. To combine more than one policy on the same surface, enforce the additional policy or policies globally instead (see Global (appliance-wide) policy assignments); a global assignment is ANDed with this surface’s own policy, deny-overrides.
- Log OPA decisions logs the full
inputJSON, including request headers and caller identity, which makes it a debugging aid rather than something to run in production.
| Field | Default | Description |
|---|---|---|
| Policy Definition | — | The stored policy definition to evaluate against, chosen by name. |
| Log OPA decisions | Off | Emit a log line for every OPA evaluation containing the full input JSON and the resulting allow/deny. A debugging aid for policy authoring: leave off in production, since input carries request headers and caller identity. |
VP evidence
The VP Evidence block governs per-surface verifiable-presentation evidence. When enabled, a VP the caller sends with the request, in a request header or a JSON body field, is verified first and its holder/issuer/claims are passed to policy as identity-binding data (input.identity_binding, above). That lets a Rego rule check input.identity_binding for a VP from a specific issuer (for example, “allow only if a VP from a known issuer is present”) without having to verify the presentation itself.
- Deny when no VP is presented denies the request outright, before the policy even runs, whenever a VP is mandatory for the surface: a belt-and-braces gate on top of whatever the policy decides.
- If a VP is only optional context that some callers provide, leave that toggle off and let your Rego decide what to do when
input.identity_bindingis absent. - VP carried in defaults to Request header; switch it to JSON body field only for callers that send the presentation there instead.
| Field | Default | Description |
|---|---|---|
| Evaluate VP evidence | Off | Whether VP evidence is evaluated for this surface. |
| Deny when no VP is presented | Off | Deny the request outright when no VP is presented: a belt-and-braces gate on top of whatever the policy decides. |
| VP carried in | Request header | Where the VP is carried on the inbound request: Request header or JSON body field. |
| Header name | X-Agent-Stream-Presentation | Header name to read when VP carried in is Request header. |
Versioning and immutability
Every save of a policy definition that changes its Rego body appends a new, immutable, monotonically numbered version. Prior versions are kept unchanged for as long as the policy exists. A metadata-only edit (name, description, enabled) updates the current version’s record without appending.
Every policy decision is recorded in audit evidence against the exact policy definition ID, resolved version, and content hash that was enforced, so a past decision can be proven against the precise policy bytes that produced it. The content hash binds both the policy’s type and its Rego body, so identical Rego stored under a different scope hashes differently.
A policy also keeps its Test (dry-run) sample input, so an operator can re-run it against an edited policy before saving.
Related
- OPA policies concept: The embedded policy engine, why in-process evaluation adds no network hop, and how policy types are enforced.
- Core configuration: Where OPA Policy and VP Evidence sit among a surface’s other optional blocks.
- Control surface access with an OPA policy: Write a policy definition and attach it to a surface’s OPA Policy block.
- Roll out an OPA policy globally: Turn on a global assignment and watch it in monitor-only mode before letting it enforce.
- Verify caller credentials with VP evidence: Gate a policy on a cryptographically verified issuer and claims via the VP Evidence block.
Glad to hear it! Please tell us how we can improve more.
Sorry to hear that. Please tell us how we can improve.
Thank you for sharing your feedback so we can improve your experience.