Documentation Index
Fetch the complete documentation index at: https://docs.xenovia.io/llms.txt
Use this file to discover all available pages before exploring further.
Policy engine
Xenovia uses Open Policy Agent (OPA) to evaluate Rego policies. Policies are attached per proxy and evaluated synchronously on every request. There are two independent policies per proxy:
- Request-stage policy — evaluated before the request is forwarded to the upstream LLM.
- Response-stage policy — evaluated after the LLM responds, before the response is returned to the agent.
Policies are fetched from the control plane and cached in Redis for 5 minutes. Compile results are cached by (proxy_id, policy_hash). Eval timeout is 200ms; compile timeout is 2s.
Decision actions
Request stage
| Action | HTTP status | Effect |
|---|
allow | — | Request forwarded to upstream |
block | 403 Forbidden | Request terminated, trace recorded |
redact | — | PII removed from messages/tools before forwarding |
Response stage
| Action | HTTP status | Effect |
|---|
allow | — | Response returned to agent |
block_response | 403 Forbidden | Response suppressed, trace recorded |
redact_response | — | PII removed from response content before returning |
For redact and redact_response, specify which fields to redact with the redact_fields set. Supported patterns:
| Field | Pattern |
|---|
email | Standard email regex |
ssn | US Social Security Number format |
Both request-stage and response-stage policies receive the same base input. The policy engine exposes input with the following fields:
{
"agent_id": "string — proxy ID",
"org_id": "string — organisation ID",
"request_type": "chat_completions | responses | embeddings | completions",
"provider": "openai | anthropic | gemini | azure | bedrock | groq | vllm",
"model": "string — model name as sent by the agent",
"stream": "boolean",
"messages": "[{ role, content }]",
"tools": "[{ type, function: { name, description, parameters } }]",
"tool_names": "[]string — extracted tool names for convenience",
"system_prompt":"string — first system message content, if present",
"request": "object — full request body (nested)",
"request_flat": "object — full request body (flattened, single depth)",
"http_headers": "object — request headers (secret values redacted)",
"intent_action":"string — allow | block | escalate (from intent plugin)",
"intent_score": "float — 0.0–1.0 semantic similarity score"
}
Response-stage additional fields
{
"stage": "response",
"output": {
"content": "string — full response text"
}
}
Writing policies
Policies are written in Rego under the package xenovia.policy. The policy engine reads the deny set and the redact_fields set.
Minimal allow-all policy
package xenovia.policy
default allow = true
package xenovia.policy
default allow = true
deny[reason] {
some tool in input.tool_names
tool == "delete_database"
reason := "delete_database is not permitted"
}
Require approval for production writes
package xenovia.policy
default allow = true
deny[reason] {
input.request_type == "chat_completions"
some tool in input.tool_names
startswith(tool, "write_")
reason := "write tools require operator approval in production"
}
Redact PII from requests
package xenovia.policy
default allow = true
redact_fields := {"email", "ssn"}
Block based on intent score
package xenovia.policy
default allow = true
deny[reason] {
input.intent_score < 0.4
reason := "request does not match declared proxy intent"
}
Response-stage: block low-confidence outputs
package xenovia.policy
default allow = true
deny[reason] {
input.stage == "response"
contains(input.output.content, "I cannot help")
reason := "model refused to respond"
}
Allowlist specific models
package xenovia.policy
default allow = true
approved_models := {"gpt-4o", "gpt-4o-mini", "claude-3-5-sonnet-20241022"}
deny[reason] {
not approved_models[input.model]
reason := sprintf("model %v is not on the approved list", [input.model])
}
Policy blocks in your application
When a request is blocked, the runtime returns 403 Forbidden. The response body follows the standard error format:
{
"error": {
"message": "request blocked by policy",
"type": "policy_violation",
"code": "policy_block"
}
}
The OpenAI SDK raises this as PermissionDeniedError. The X-Xenovia-Trace-Id header is present on blocked responses — use it to look up the full trace.
Approval workflow
For actions that require human review rather than automatic blocking, use the intent plugin’s escalate action (configured per proxy in the platform). When an escalation fires:
- The request is blocked with
403.
- An async notification is sent to the control plane with the full request context.
- Operators review the trace — including the session, turn number, messages, tools, and intent score — and approve or deny.
The escalation reason is logged server-side only and is not returned to the agent. A bounded pool of 32 goroutines handles escalation notifications to prevent backpressure during traffic spikes.