LLM Policy as Code: Version-Controlled Governance for Model and Agent Access
Ask a team "which models is your application allowed to call, and under what conditions?" and the honest answer is usually "let me check the code." The rules — which models are approved, which tools an agent may invoke, what happens when a request is too large or comes from the wrong region — are scattered across if statements in a dozen services. No one can review them in one place, no one can test a change safely, and no one can say what a rule would have done before it ships.
LLM policy as code fixes that the same way infrastructure as code fixed server configuration: move the rules out of application code and into a declarative, version-controlled language that a single control point enforces on every call.
What is LLM policy as code?
LLM policy as code is the practice of expressing your governance rules — who can call which models and tools, under which conditions — in a declarative, version-controlled format, and enforcing them centrally at a gateway on every LLM and agent (MCP) request, instead of hard-coding them in each application.
It's the same idea behind Terraform for infrastructure, Open Policy Agent for authorization, and Kubernetes admission control for clusters: the policy is a reviewable artifact, not tribal knowledge. The difference is the domain — an LLM policy matches on AI-native attributes (model, token budget, requested tools, data-residency region) that a general-purpose API gateway or IAM system can't see.
Why governance in application code fails
Rules embedded in app code have four problems, and they compound:
- You can't review them. "What's allowed?" has no single answer — you'd have to read every repo that calls a model.
- You can't test them. Changing a rule means a deploy and hoping nothing breaks. There's no way to see what a new rule would do against real traffic first.
- You can't roll them back. A bad rule is another deploy to undo, under pressure, with no record of the previous state.
- You can't audit them. There's no trail of who changed which rule, when, or what decision it produced on a given request.
Policy as code turns all four from "read the source" into "read the policy" — a diffable, testable, reversible, auditable object.
What an LLM policy looks like
A policy is a small YAML document: a set of rules, each with an id, a priority (lower number wins), a conditions block that matches request attributes, and exactly one action. Here's a model allowlist — deny anything that isn't on the approved list:
version: "1"
rules:
- id: only-approved-models
priority: 10
conditions:
model:
allowlist: [gpt-4o, gpt-4o-mini, claude-sonnet-4-5]
action: DENY
deny_message: "Model not on the approved list"
Rules evaluate in priority order. The first matching DENY returns immediately with its deny_message; a softer WARN_AGENT action collects a warning and lets the call continue — useful for nudging behavior without blocking it.
The conditions you can match on are the ones that actually matter for AI traffic:
model— an allowlist or denylist of model namesmax_tokens— cap request sizetools/ MCP server, tool, and arguments — govern what an agent can invoke, not just which model it usesdata_residency— enforce where a request may be processedtime_of_day— business-hours-only rulesbudgetutilization — tighten controls as a tenant approaches its spend cap
When the simple form isn't enough
Closed-form conditions cover the common cases, but real rules sometimes need OR, NOT, or arithmetic across fields. For those, a rule can carry a CEL (Common Expression Language) expression: instead of a conditions: block:
version: "1"
rules:
- id: gpt4o-outside-eu-or-large-requests
expression: |
request.model == "gpt-4o" &&
(context.region != "EU" || request.message_count >= 50)
action: DENY
deny_message: "gpt-4o restricted outside EU or for >= 50 messages"
CEL is the same sandboxed expression language Kubernetes admission policies, Envoy, and Cilium use — deterministic, type-checked at submit time, and with no I/O or loops. One expression replaces what would otherwise be three separate rules.
The lifecycle is what makes it safe to change
Writing rules as code is only half the value. The other half is being able to change them without fear. A governance change should move through the same stages as any other production change:
- Author — write the policy; it starts as a Draft, affecting nothing.
- Dry-run — simulate the policy against a real request and see the exact decision and timing before it touches live traffic.
- Shadow — run the candidate in production in parallel with your active policy. It records what it would have done — with divergence stats — while changing nothing. This is the step that removes the guesswork.
- Promote — activate it. Conflict detection surfaces rules that overlap or contradict, and the change hot-reloads across the fleet without a restart.
- Version & roll back — every change snapshots a version; revert to any prior one instantly.
Shadow mode is the part teams underrate. It's the difference between "we think this rule is safe" and "we watched it run against a week of real traffic and it denied exactly the three requests we expected."
Global and tenant scope, and an audit trail on every call
Policies apply at two levels: platform-wide rules that govern everyone, and per-tenant rules scoped to a single tenant. Both are evaluated on every request, and the decision — allowed, denied, or warned, and by which rule — is recorded on every LLM and MCP call. That record is what turns "we have policies" into "here's proof of what our policies did," which is exactly what an auditor asks for.
Where the policy runs
The rules are enforced at the gateway that sits in front of your model and tool calls — one control point every request already passes through. That's what makes central enforcement possible: the policy engine sees the full AI semantics of each call (model, tokens, tools, region, budget) and applies your rules before the request reaches an upstream provider or MCP server. A denied request never leaves your perimeter.
This is the core of DVARA's approach to AI governance: Policy-as-Code for LLM and agent traffic as a first-class, version-controlled surface — author it in YAML, dry-run and shadow-test it, promote with conflict detection, and roll back in one click. It ships in every DVARA install.
Want the full picture? Start with the Policy-as-Code pillar guide, or see routes and policies in the docs for the complete condition surface and CEL context.