Enterprise early access is open. Request access

Docs Tasks Write a policy that allows traffic

Write a policy that allows traffic

A fresh enforcing install denies everything. This is how you turn default-deny into a working allow list without opening the door wider than you meant to.

Before you begin#

  • A running control plane — see Install with Helm.
  • A capability that at least one healthy agent declares.

Confirm you are actually being denied by policy rather than something else:

curl -s http://127.0.0.1:8080/v1/run \
  -H 'Content-Type: application/json' \
  -d '{"tenant":"retail","capability":"refund-processing","input":{"message":"test"}}'
{"error":{"code":"policy_denied",
           "message":"Policy denied the request: missing_policy"}}

missing_policy means policy is enforcing and nothing granted an explicit allow.

Write the rules#

Rules are Helm values. The chart renders them into a ConfigMap and mounts them read-only into the policy service.

policy:
  rules:
    defaultDecision: deny
    allowedTenants:
      - retail
    allowedCapabilities:
      - refund-processing
helm upgrade agentfleet ./agentfleet \
  --namespace agentfleet-system --reuse-values \
  --set policy.rules.defaultDecision=deny \
  --set policy.rules.allowedTenants[0]=retail \
  --set policy.rules.allowedCapabilities[0]=refund-processing
helm upgrade agentfleet ./agentfleet \
  --namespace agentfleet-system --reuse-values -f my-values.yaml
Changing rules restarts the policy pod

Rules are read once at startup, so the chart puts a checksum of them on the pod template — changing rules rolls the pod and the new rules take effect. Editing the ConfigMap by hand does not: the running process keeps what it loaded.

Verify#

The allowed combination now succeeds:

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/v1/run \
  -H 'Content-Type: application/json' \
  -d '{"tenant":"retail","capability":"refund-processing","input":{"message":"test"}}'
# 200

And anything outside the list is still refused:

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/v1/run \
  -H 'Content-Type: application/json' \
  -d '{"tenant":"finance","capability":"refund-processing","input":{"message":"test"}}'
# 403
Both halves matter

A rule set that only ever returns 200 has not been shown to enforce anything. Always test the denial too.

Two behaviours that will bite you#

Allow lists only apply when the request carries the field

A request with no tenant is not matched by allowedTenants — and is not rejected by it either. It falls through to defaultDecision. This is safe while the default is deny. If you set defaultDecision: allow, a request missing the fields you filter on is allowed. Keep the default at deny.

A misspelled key fails startup

Unknown fields are rejected rather than ignored, so allowedTennants stops the policy pod with an error instead of silently leaving your restriction unenforced. Check the pod logs after a rules change.

Narrowing further#

The same rule set restricts models, tools, regions, and cost. Deny rules are evaluated before allow rules, so an explicit deny always wins:

policy:
  rules:
    defaultDecision: deny
    allowedTenants: [retail]
    allowedCapabilities: [refund-processing]
    allowedModelProviders: [anthropic, openai]
    deniedToolTypes: [database]
    maxCostUsd: 5.00

Every available key is listed in the Helm values reference.

Next#