You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
| `CONTROL_PLANE_PORT` | No | `4001` | Control plane listen port |
70
70
| `ANTHROPIC_API_KEY` | No | — | Enables LLM gap analysis (`GET /agentspec/gap`) |
71
71
| `AUDIT_RING_SIZE` | No | `1000` | Max audit ring entries retained in memory |
72
-
| `OPA_URL` | No | — | OPA base URL (e.g. `http://localhost:8181`). When set, `/gap` calls OPA for behavioral violations AND the proxy evaluates every request. Fails-open if OPA is unreachable. |
73
-
| `OPA_PROXY_MODE` | No | `track` | Per-request OPA mode on the proxy (port 4000). `track` — record violations in the audit ring and add `X-AgentSpec-OPA-Violations` header, but forward the request. `enforce` — block with `403 PolicyViolation` before forwarding. `off` — disable proxy OPA checks entirely. |
72
+
| `OPA_URL` | No | — | OPA base URL (e.g. `http://localhost:8181`). When set, `/gap` calls OPA for behavioral violations AND the proxy evaluates agent response headers. Fails-open if OPA is unreachable. |
73
+
| `OPA_PROXY_MODE` | No | `track` | HeaderReporting OPA mode on the proxy (port 4000). `track` — record violations in audit ring + `X-AgentSpec-OPA-Violations` header, never blocks. `enforce` — replace agent response with `403 PolicyViolation` when OPA denies. `off` — disable proxy OPA checks entirely. OPA is only called when the agent sets `X-AgentSpec-*` response headers (sdk-langgraph `AgentSpecMiddleware`). |
74
74
75
75
`UPSTREAM_URL`and `MANIFEST_PATH` must be set correctly. The sidecar will fail to start if `UPSTREAM_URL` is not a valid `http://` or `https://` URL, or if port values are non-integer.
When `OPA_URL` is set, the proxy reads these headers from the incoming request to populate the OPA input document. Set them from your agent code (or `GuardrailMiddleware`) to give OPA the full runtime context it needs to enforce policies accurately.
83
+
OPA now evaluates **real agent behavior** — not honor-system client headers. There are two reporting paths:
84
84
85
-
| Header | Example | Description |
86
-
|--------|---------|-------------|
87
-
| `X-AgentSpec-Guardrails-Invoked` | `pii-detector,toxicity-filter` | Comma-separated list of guardrail types actually run on this request |
88
-
| `X-AgentSpec-Tools-Called` | `plan-workout,log-session` | Comma-separated list of tools invoked |
89
-
| `X-AgentSpec-User-Confirmed` | `true` | Set to `true` if the user explicitly confirmed a destructive action |
When these headers are absent, the proxy uses worst-case defaults (`guardrails_invoked: []`, `tools_called: []`). In `track` mode this records a violation. In `enforce` mode, any declared guardrail will cause a 403.
87
+
The `agentspec-langgraph` `AgentSpecMiddleware` sets internal headers on the agent's HTTP **response** after processing:
92
88
93
-
The proxy sets `X-AgentSpec-OPA-Violations` on every response where violations fired (regardless of mode), so clients and upstream tooling can observe policy gaps.
| `X-AgentSpec-Guardrails-Invoked` | Comma-separated guardrail types that actually ran |
92
+
| `X-AgentSpec-Tools-Called` | Comma-separated tool names that were called |
93
+
| `X-AgentSpec-User-Confirmed` | `true` if user confirmed a destructive action |
94
94
95
-
In `enforce` mode, the sidecar returns a structured error **before** forwarding to the upstream agent:
95
+
The sidecar proxy reads these in its `onResponse` callback and **strips them before forwarding to the client**. Clients never see these headers. OPA is only called when at least one behavioral header is present.
96
+
97
+
The proxy sets `X-AgentSpec-OPA-Violations` on every response where violations fired (regardless of mode), so clients can observe policy gaps.
98
+
99
+
In `enforce` mode, when OPA denies based on agent response headers:
{"error":"PolicyViolation","blocked":true,"violations":["pii_detector_not_invoked"],"message":"Request blocked by OPA policy: pii_detector_not_invoked"}
103
107
```
104
108
105
-
When OPA is unreachable the proxy **fails open** (forwards the request with a warning log) regardless of mode. Set `OPA_PROXY_MODE=off` to silence OPA calls entirely while keeping `OPA_URL` set for `/gap`.
109
+
> **Note:** Unlike the old implementation, `enforce` mode evaluates the agent's response headers. The upstream agent **always** processes the request. Only the client-visible response is blocked (replaced with 403).
The agent pushes behavioral events after each request via `POST /agentspec/events` on the control plane (port 4001). EventPush always records regardless of `OPA_PROXY_MODE`.
114
+
115
+
```bash
116
+
curl -X POST http://localhost:4001/agentspec/events \
# 202 {"requestId":"...","found":false} ← race (no retry needed)
128
+
```
129
+
130
+
When OPA is unreachable the proxy **fails open** (forwards the request) regardless of mode. Set `OPA_PROXY_MODE=off` to silence HeaderReporting OPA calls entirely while keeping `OPA_URL` set for `/gap` and EventPush.
Copy file name to clipboardExpand all lines: docs/concepts/opa.md
+64-22Lines changed: 64 additions & 22 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -131,15 +131,61 @@ The `agentspec-langgraph` Python package provides this for LangGraph agents. It
131
131
132
132
See [LangGraph Runtime Instrumentation](../adapters/langgraph.md#runtime-behavioral-instrumentation) for the full integration guide.
133
133
134
-
## Per-request proxy enforcement
134
+
## Behavioral observation pipeline
135
135
136
-
The sidecar proxy (port 4000) evaluates OPA on **every request** when `OPA_URL` is set. The mode is controlled by the `OPA_PROXY_MODE` env var:
136
+
OPA needs to know what the agent *actually did* — which guardrails fired, which tools were called. This data comes from the `agentspec-langgraph` sub-SDK via one of two reporting paths:
137
137
138
-
| Mode | Behaviour |
139
-
|------|-----------|
140
-
|`track` (default) | Record violations in the audit ring; add `X-AgentSpec-OPA-Violations` response header; forward the request. Safe for initial rollout — never blocks traffic. |
141
-
|`enforce`| Block with `403 PolicyViolation`**before forwarding to the upstream agent**. Use after validating policies in `track` mode. |
142
-
|`off`| Skip proxy OPA checks entirely. `/gap` still calls OPA if `OPA_URL` is set. |
138
+
### HeaderReporting — Agent response headers
139
+
140
+
`AgentSpecMiddleware` (FastAPI/Starlette) sets internal headers on the agent's HTTP response after each request completes:
The sidecar proxy (port 4000) evaluates OPA on agent response headers when `OPA_URL` is set. The mode is controlled by the `OPA_PROXY_MODE` env var:
181
+
182
+
| Mode | Trigger | Behaviour |
183
+
|------|---------|-----------|
184
+
|`track` (default) | Agent response headers present | Record violations in the audit ring; add `X-AgentSpec-OPA-Violations` response header; forward the response to client. Safe for initial rollout — never blocks. |
185
+
|`enforce`| Agent response headers present | If OPA denies: sidecar replaces agent response with `403 PolicyViolation`. Agent always processes the request; only the client-visible response is blocked. |
186
+
|`off`| — | Skip proxy OPA checks entirely. `/gap` still calls OPA if `OPA_URL` is set. |
187
+
188
+
> **Note:** If the agent does not set `X-AgentSpec-*` response headers (e.g. not using sdk-langgraph), OPA is not called and the request passes through regardless of mode. Use EventPush (`SidecarClient`) for agents that cannot use middleware.
143
189
144
190
Configure globally (docker-compose or Helm):
145
191
@@ -163,7 +209,7 @@ Override per-pod with annotation: `agentspec.io/opa-proxy-mode: enforce`.
163
209
164
210
### 403 PolicyViolation response
165
211
166
-
When `enforce` mode blocks a request, the sidecar returns before the upstream agent ever sees the request:
212
+
When `enforce` mode blocks a request based on agent response headers, the sidecar replaces the upstream response with a 403:
The sidecar builds the OPA input from **request headers**. It does not observe what the agent actually executed. OPA knows `pii-detector` was invoked only because the caller said so via a header:
If a header is **absent**, the field defaults to empty. With `pii-detector` declared in `agent.yaml` and `guardrails_invoked: []`, OPA fires `pii_detector_not_invoked` immediately — because the caller did not declare that the guardrail ran.
229
+
### Enforcement model
194
230
195
-
This is **declaration-based enforcement**, not execution-verified enforcement. A caller that sets the header without actually running the guardrail passes OPA. To close that gap, use a framework sub-SDK (`agentspec-langgraph` etc.) that sets these headers automatically from real guardrail invocations inside the agent's execution path.
231
+
| Path | Mechanism | Real-time blocking |
232
+
|------|-----------|-------------------|
233
+
| `off` | No OPA calls | — |
234
+
| `track` (HeaderReporting) | Record violations in audit ring + `X-AgentSpec-OPA-Violations` header | Never blocks |
235
+
| `enforce` (HeaderReporting) | OPA evaluates agent response headers; if deny → 403 to client | ✅ Yes (client-side) |
236
+
| EventPush | OPA evaluates pushed events retroactively; updates audit ring | ❌ No (observation) |
OPA evaluates an input document on every request. That document needs live runtime data — which guardrails were invoked, how many tokens were used, which tools were called. The sidecar builds a partial input from the manifest and probe data; for full behavioral coverage you also need a **framework sub-SDK** that intercepts the agent's execution path and sets the headers automatically.
241
+
OPA evaluates an input document on every request. That document needs live runtime data — which guardrails were invoked, how many tokens were used, which tools were called. The sidecar builds a partial input from the manifest and probe data; for full behavioral coverage you also need a **framework sub-SDK** that intercepts the agent's execution path.
200
242
201
-
The `agentspec-langgraph` Python package provides this for LangGraph agents. It intercepts tool calls, LLM calls, and guardrail invocations and sets `X-AgentSpec-*` headers on outgoing requests so that OPA receives ground truth rather than self-reported data.
243
+
The `agentspec-langgraph` Python package provides this for LangGraph agents. It intercepts tool calls, LLM calls, and guardrail invocations and reports them via HeaderReporting (response headers) or EventPush (out-of-band event push).
202
244
203
245
See [LangGraph Runtime Instrumentation](../adapters/langgraph.md#runtime-behavioral-instrumentation) for the full integration guide.
0 commit comments