Skip to main content

Policy Configuration

Manetu Policy Engine (MPE) is a policy evaluation engine that uses Rego, the policy language from Open Policy Agent (OPA). AgentVisor™ uses MPE to enforce fine-grained authorization controls, determining which HTTP endpoints, tools, and resources each agent can access based on the caller's identity.

This guide covers writing and configuring MPE PolicyDomains for AgentVisor.

PolicyDomain Basics

Policies are defined in YAML using MPE's v1beta1 schema:

apiVersion: iamlite.manetu.io/v1beta1
kind: PolicyDomain
metadata:
name: my-policies
spec:
policies:
- mrn: "mrn:iam:policy:allow-authenticated"
name: allow-authenticated
rego: |
package authz
default allow = false
allow { input.principal.sub != "" }

resource-groups:
- mrn: "mrn:iam:resource-group:allowed"
name: allowed
policy: "mrn:iam:policy:allow-authenticated"

resources:
- name: example
selector: ["mrn:agentvisor:http:example\\.com.*"]
group: "mrn:iam:resource-group:allowed"
This fragment is not a complete, working domain

It's missing the operations: and roles: sections every real domain needs — see Complete Example below for one that actually loads and grants/denies traffic. It's also missing something easy to miss: input.principal.sub != "" is true for the built-in anonymous principal too (sub: "anonymous"), not just for callers with real credentials — see Unauthenticated Requests and the Anonymous Principal for why, and how to tighten it.

MRN Format Conventions

AgentVisor uses two MRN (Manetu Resource Name) namespaces:

NamespaceFormatPurpose
iammrn:iam:<type>:<name>IAM resources: policies, resource groups, roles
agentvisormrn:agentvisor:<type>:<identifier>Runtime resources: threads, agents, HTTP endpoints

IAM namespace (mrn:iam:) — Used for policy infrastructure that defines how authorization works:

  • mrn:iam:policy:<name> — Policy definitions
  • mrn:iam:resource-group:<name> — Collections of resources sharing a policy (uses hyphen)

AgentVisor namespace (mrn:agentvisor:) — Used for actual resources being protected:

  • mrn:agentvisor:http:<host>/<path> — HTTP endpoints
  • mrn:agentvisor:thread:<id> — Conversation threads
  • mrn:agentvisor:tool:<name> — Agent tools
  • mrn:agentvisor:resourcegroup:<name> — Resource group references in policies (no hyphen)
Hyphen Convention

The iam namespace uses resource-group (hyphenated), while the agentvisor namespace uses resourcegroup (no hyphen). This reflects the origin of each schema—follow the examples in this guide for correct formatting.

Core Components

Policies

Rego rules that evaluate to allow or deny:

policies:
# Allow any authenticated principal (this also grants the anonymous
# principal, sub: "anonymous" — see the note above)
- mrn: "mrn:iam:policy:allow-authenticated"
name: allow-authenticated
rego: |
package authz
default allow = false
allow { input.principal.sub != "" }

# Deny everything
- mrn: "mrn:iam:policy:deny-all"
name: deny-all
rego: |
package authz
default allow = false

# Allow specific roles
- mrn: "mrn:iam:policy:admin-only"
name: admin-only
rego: |
package authz
default allow = false
allow { input.principal.role == "admin" }

Resource Groups

Collections of resources sharing a policy:

resource-groups:
- mrn: "mrn:iam:resource-group:llm-apis"
name: llm-apis
policy: "mrn:iam:policy:allow-authenticated"

- mrn: "mrn:iam:resource-group:blocked"
name: blocked
policy: "mrn:iam:policy:deny-all"

- mrn: "mrn:iam:resource-group:admin-resources"
name: admin-resources
policy: "mrn:iam:policy:admin-only"

Resources

Map MRN patterns to groups using regex selectors:

resources:
- name: openai
selector: ["mrn:agentvisor:http:api\\.openai\\.com.*"]
group: "mrn:iam:resource-group:llm-apis"

- name: anthropic
selector: ["mrn:agentvisor:http:api\\.anthropic\\.com.*"]
group: "mrn:iam:resource-group:llm-apis"

- name: internal-networks
selector:
- "mrn:agentvisor:http:10\\..*"
- "mrn:agentvisor:http:192\\.168\\..*"
group: "mrn:iam:resource-group:blocked"

Common Patterns

Allow LLM APIs

spec:
resources:
# OpenAI
- name: openai
selector: ["mrn:agentvisor:http:api\\.openai\\.com.*"]
group: "mrn:iam:resource-group:allowed"

# Anthropic
- name: anthropic
selector: ["mrn:agentvisor:http:api\\.anthropic\\.com.*"]
group: "mrn:iam:resource-group:allowed"

# Google AI
- name: google-ai
selector: ["mrn:agentvisor:http:generativelanguage\\.googleapis\\.com.*"]
group: "mrn:iam:resource-group:allowed"

# Hugging Face
- name: huggingface
selector: ["mrn:agentvisor:http:api-inference\\.huggingface\\.co.*"]
group: "mrn:iam:resource-group:allowed"

Block Internal Networks

spec:
resources:
# RFC1918 private networks
- name: private-10
selector: ["mrn:agentvisor:http:10\\..*"]
group: "mrn:iam:resource-group:blocked"

- name: private-172
selector: ["mrn:agentvisor:http:172\\.(1[6-9]|2[0-9]|3[0-1])\\..*"]
group: "mrn:iam:resource-group:blocked"

- name: private-192
selector: ["mrn:agentvisor:http:192\\.168\\..*"]
group: "mrn:iam:resource-group:blocked"

# Localhost
- name: localhost
selector:
- "mrn:agentvisor:http:localhost.*"
- "mrn:agentvisor:http:127\\..*"
group: "mrn:iam:resource-group:blocked"

# Cloud metadata services
- name: aws-metadata
selector: ["mrn:agentvisor:http:169\\.254\\.169\\.254.*"]
group: "mrn:iam:resource-group:blocked"

- name: gcp-metadata
selector: ["mrn:agentvisor:http:metadata\\.google\\.internal.*"]
group: "mrn:iam:resource-group:blocked"

Allow Specific Paths

spec:
resources:
# Allow only chat completions, not fine-tuning
- name: openai-chat
selector: ["mrn:agentvisor:http:api\\.openai\\.com/v1/chat.*"]
group: "mrn:iam:resource-group:allowed"

# Block fine-tuning endpoints
- name: openai-finetune
selector: ["mrn:agentvisor:http:api\\.openai\\.com/v1/fine.*"]
group: "mrn:iam:resource-group:blocked"

Multi-Tenant Policies

spec:
policies:
- mrn: "mrn:iam:policy:tenant-match"
name: tenant-match
rego: |
package authz
default allow = false

# Allow if principal's tenant matches resource tenant
allow {
input.principal.tenant == input.context.resource_tenant
}

# Admins can access all tenants
allow {
input.principal.role == "admin"
}

resource-groups:
- mrn: "mrn:iam:resource-group:tenant-resources"
name: tenant-resources
policy: "mrn:iam:policy:tenant-match"

Default Deny

Always include a catch-all deny:

spec:
resources:
# Specific allows first
- name: allowed-api
selector: ["mrn:agentvisor:http:api\\.allowed\\.com.*"]
group: "mrn:iam:resource-group:allowed"

# Catch-all deny MUST be last
- name: default-http
selector: ["mrn:agentvisor:http:.*"]
group: "mrn:iam:resource-group:blocked"

AgentVisor Core Resources

Threads, runs, state, agents, and store items are not matched by selector. The host sends these to MPE as fully-qualified resource descriptors (an MRN plus owner/group already attached — see Policy Enforcement for the phase details), so MPE never consults resources: for them. Instead, define resource-groups: entries whose MRNs match these five constants exactly (they come from pkg/authz/resource.go in the AgentVisor source and will not resolve if misspelled or given the iam namespace instead):

spec:
resource-groups:
- mrn: "mrn:agentvisor:resourcegroup:threads"
name: threads
policy: "mrn:iam:policy:allow-authenticated"

- mrn: "mrn:agentvisor:resourcegroup:runs"
name: runs
policy: "mrn:iam:policy:allow-authenticated"

- mrn: "mrn:agentvisor:resourcegroup:state"
name: state
policy: "mrn:iam:policy:allow-authenticated"

- mrn: "mrn:agentvisor:resourcegroup:agents"
name: agents
policy: "mrn:iam:policy:allow-authenticated"

- mrn: "mrn:agentvisor:resourcegroup:store"
name: store
policy: "mrn:iam:policy:allow-authenticated"
Checkpoints are not policy-evaluated

SaveCheckpoint/LoadCheckpoint deliberately skip authorization entirely — they're internal to a run whose access was already adjudicated at run creation, so there's no mrn:agentvisor:resourcegroup:checkpoint to define. See Policy Enforcement for the full list of which operations are and aren't evaluated.

Complete Example

This domain actually loads and grants/denies traffic as described — it's adapted from the working policy shipped with the langgraph/chatbot-agent template (agentvisor template create langgraph/chatbot-agent). Unlike the fragments above, it includes the two sections every real domain needs: operations: (without it, MPE hard-denies every request before resource policies ever run) and roles: (without a role matching the caller's mroles/mgroups claims, MPE denies every request regardless of what the resource policy would decide — see Policy Enforcement for why both phases exist).

apiVersion: iamlite.manetu.io/v1beta1
kind: PolicyDomain
metadata:
name: production-agent
spec:
policies:
# Operation phase - tri-state (-1 deny, 0 continue to the other
# phases, >0 grant and skip them). Runs before every other phase.
- mrn: &policy-operation "mrn:iam:policy:agentvisor-operation"
name: agentvisor-operation
rego: |
package authz
import rego.v1
default allow := -1
allow := 0 if input.principal.sub != ""

# This also grants the built-in anonymous principal (sub: "anonymous") —
# see Unauthenticated Requests and the Anonymous Principal in Policy
# Enforcement. Add `input.principal.sub != "anonymous"` to require real
# credentials.
- mrn: &policy-allow "mrn:iam:policy:allow-authenticated"
name: allow-authenticated
rego: |
package authz
import rego.v1
default allow := false
allow if input.principal.sub != ""

- mrn: &policy-deny "mrn:iam:policy:deny-all"
name: deny-all
rego: |
package authz
default allow = false

# Identity phase - the anonymous principal automatically carries the
# "mrn:agentvisor:role:anonymous" role (see pkg/authz/principal.go), so
# this role is what lets it pass. A real OIDC deployment would add a role
# here for whatever "mroles" value its identity provider issues instead —
# see the OIDC Setup guide.
roles:
- mrn: "mrn:agentvisor:role:anonymous"
name: anonymous
policy: *policy-allow

resource-groups:
# Core AgentVisor resources - MRNs must match pkg/authz/resource.go
# exactly; see AgentVisor Core Resources above for why no `resources:`
# selector is needed for these.
- mrn: "mrn:agentvisor:resourcegroup:threads"
name: threads
policy: *policy-allow

- mrn: "mrn:agentvisor:resourcegroup:runs"
name: runs
policy: *policy-allow

- mrn: "mrn:agentvisor:resourcegroup:state"
name: state
policy: *policy-allow

- mrn: "mrn:agentvisor:resourcegroup:agents"
name: agents
policy: *policy-allow

- mrn: "mrn:agentvisor:resourcegroup:store"
name: store
policy: *policy-allow

- mrn: "mrn:iam:resource-group:llm-apis"
name: llm-apis
policy: *policy-allow

- mrn: "mrn:iam:resource-group:blocked"
name: blocked
policy: *policy-deny

- mrn: "mrn:iam:resource-group:default"
name: default
default: true
policy: *policy-deny

resources:
# HTTP endpoints arrive as simple MRN strings (the host doesn't
# pre-resolve a group for them), so they're matched by selector here.

# Allowed LLM APIs
- name: openai
selector: ["mrn:agentvisor:http:api\\.openai\\.com.*"]
group: "mrn:iam:resource-group:llm-apis"

- name: anthropic
selector: ["mrn:agentvisor:http:api\\.anthropic\\.com.*"]
group: "mrn:iam:resource-group:llm-apis"

# Blocked networks
- name: internal-10
selector: ["mrn:agentvisor:http:10\\..*"]
group: "mrn:iam:resource-group:blocked"

- name: internal-172
selector: ["mrn:agentvisor:http:172\\.(1[6-9]|2[0-9]|3[0-1])\\..*"]
group: "mrn:iam:resource-group:blocked"

- name: internal-192
selector: ["mrn:agentvisor:http:192\\.168\\..*"]
group: "mrn:iam:resource-group:blocked"

- name: metadata
selector: ["mrn:agentvisor:http:169\\.254\\..*"]
group: "mrn:iam:resource-group:blocked"

# Default deny - must be last
- name: default-http
selector: ["mrn:agentvisor:http:.*"]
group: "mrn:iam:resource-group:default"

# Operation phase - a single entry routes every AgentVisor operation
# through the tri-state policy above.
operations:
- name: agentvisor-ops
selector: ["agentvisor:.*"]
policy: *policy-operation

This example grants the anonymous principal — appropriate for local development (--sandbox=none, no api.auth configured). Save it as policies/domain.yml, verify it with mpe lint -f ./policies/domain.yml, then a real request:

agentvisor serve . --sandbox=none --policy ./policies/domain.yml &
curl -X POST http://localhost:8090/threads # 201 Created

Connecting to a Remote PDP

The http provider delegates policy decisions to an external Policy Decision Point (PDP) over HTTP/HTTPS. Use this when:

  • You run a centralized or multi-tenant policy service.
  • Your PDP is not co-located on the same host as AgentVisor (i.e., it doesn't follow the MPE-Premium sidecar model).
  • You need to integrate with a third-party OPA/PDP service.

Minimal HTTPS configuration

The simplest secure setup uses a custom CA to verify the PDP's certificate:

authz:
type: http
http:
url: "https://pdp.example.com:9000"
tls:
mode: verify-full # default when https:// URL is used
ca_file: "/etc/agentvisor/pdp-ca.pem"

Equivalent environment variables:

export AGENTVISOR_AUTHZ_TYPE=http
export AGENTVISOR_AUTHZ_HTTP_URL=https://pdp.example.com:9000
export AGENTVISOR_AUTHZ_HTTP_TLS_MODE=verify-full
export AGENTVISOR_AUTHZ_HTTP_TLS_CA_FILE=/etc/agentvisor/pdp-ca.pem

Mutual TLS (mTLS)

When the PDP requires client certificate authentication:

authz:
type: http
http:
url: "https://pdp.example.com:9000"
tls:
mode: verify-full
ca_file: "/etc/agentvisor/pdp-ca.pem"
cert_file: "/etc/agentvisor/client.pem"
key_file: "/etc/agentvisor/client-key.pem"

Inline certificate data (base64-encoded PEM) is also supported via ca_data, cert_data, and key_data — useful in containerized deployments where mounting files is inconvenient.

Custom headers (API keys, bearer tokens, tenant IDs)

Use the headers list to inject arbitrary headers on every PDP request. Reference secrets from environment variables using value_env to avoid committing credentials to version control:

authz:
type: http
http:
url: "https://pdp.example.com:9000"
tls:
mode: verify-full
ca_file: "/etc/agentvisor/pdp-ca.pem"
headers:
- name: "X-Tenant-ID"
value: "acme" # static value
- name: "Authorization"
value_env: "PDP_TOKEN" # reads $PDP_TOKEN from the environment at startup

Note: The headers list can only be set via YAML configuration file, not via flat environment variables. Set value_env to an environment variable name to keep secrets out of your config files.

Development / insecure mode

For local development with self-signed certificates, set mode: require under tls: to skip all verification. Never use this in production.

authz:
type: http
http:
url: "https://localhost:9000"
tls:
mode: require # ⚠️ skips ALL certificate verification — dev/test only

TLS mode reference

ModeBehaviorUse case
verify-fullVerify cert chain + hostnameProduction (default for https://)
verify-caVerify cert chain, skip hostnameInternal services with wildcard certs or proxies
requireTLS on, skip all verificationDevelopment / self-signed without CA distribution
disableNo TLS (http:// only)Localhost / sidecar on same node

Security guidance

  • Prefer verify-full in production.
  • Use ca_file (or ca_data) to pin a specific internal CA rather than relying on system roots.
  • For bearer token or API key authentication, always use value_env instead of inline value to avoid credentials appearing in config files or logs.
  • Use mTLS (cert_file + key_file) when the PDP enforces client identity.

Testing Policies

The mpe CLI tool validates and tests PolicyDomain files before deployment. It's part of the Manetu PolicyEngine project.

Installation: See the MPE documentation for installation instructions.

Validate Syntax

mpe lint -f ./policies/domain.yml

Run with Debug Logging

AGENTVISOR_LOG_LEVEL=debug agentvisor serve ./my-agent

Test Specific Requests

# Should be allowed
curl -sX POST "http://localhost:8090/threads/$THREAD/runs?wait=30s" \
-d '{"input": {"url": "http://api.openai.com/v1/models"}}' | jq

# Should be blocked
curl -sX POST "http://localhost:8090/threads/$THREAD/runs?wait=30s" \
-d '{"input": {"url": "http://192.168.1.1/"}}' | jq

Multiple Policy Files

Combine multiple policy files. --policy is a single flag, so passing it more than once doesn't merge the values — only the last occurrence takes effect. Use a comma-separated list instead:

agentvisor serve ./my-agent \
--policy ./policies/base.yml,./policies/custom.yml

Or via environment variables:

export AGENTVISOR_AUTHZ_TYPE=embedded
export AGENTVISOR_AUTHZ_EMBEDDED_POLICY_DOMAIN_FILES=./policies/base.yml,./policies/custom.yml