Skip to content

Policy-as-Code Guardrails for Agents: From YAML to Enforced Action

Policy as code for AI agents: version guardrails in Git, test them in CI, and enforce every outbound tool call at egress. Request Agent G private beta access.

By Agent G Engineering8

A policy as code ai agent setup means your agent guardrails live in a versioned file (YAML, JSON, or Rego), get reviewed and tested in CI, and are evaluated deterministically at the network boundary on every outbound tool call. The agent cannot read, edit, or argue with the policy. It only receives an allow, deny, or escalate decision.

Why prompt-level guardrails are not policy

Most teams start by writing rules into the system prompt: never send customer data to external domains, always ask before deleting anything, only call approved APIs. That text is an input to a probabilistic model. It competes with retrieved documents, tool descriptions, user messages, and whatever a poisoned web page injected three turns ago. Any of those can win.

Policy-as-code inverts the trust model. The rules are not part of the context window at all. They are compiled artifacts evaluated by a separate process that sits on the wire between the agent and the internet. The agent still decides what it wants to do. The policy engine decides what actually happens.

That distinction matters because the interesting failures are not bad text, they are bad actions: a POST to an unapproved webhook, a DELETE against production, a package install from a registry nobody approved. Those are all HTTP requests, and HTTP requests are exactly the object a policy engine can evaluate with full context.

Guardrails as code vs the alternatives

Different layers see different things. Understanding what each layer can and cannot evaluate is the whole architecture argument.

LayerWhat it seesCan it be bypassed by the model?Versionable and testable
System prompt rulesText in contextYes, via injection or driftPartially (prompt files)
In-process guardrail libraryFunction args before the SDK callYes, if the agent has shell or raw HTTPYes, but coupled to app code
Service mesh or NetworkPolicyHost, port, IPNo, but it cannot see intentYes
Egress policy engine (Agent G)Identity, host, path, method, headers, body, tool name, argumentsNo, it is out of processYes, in Git with CI tests

Layers are complementary, not competitive. The point is that only the last row can express a rule like this agent may call the payments API, but any transfer above a threshold requires a human approval, and the destination account must match an allowlist and enforce it where the agent cannot reach around it.

Designing an action level policy llm engines cannot talk around

A useful agent policy needs five inputs available at decision time. If your design is missing any of them, you will end up writing rules that are either too coarse (block the whole domain) or unenforceable (trust the agent to self-report).

  • Subject: which agent, which workload identity, which run or session. Not a shared service account for every agent in the cluster.
  • Destination: resolved hostname, IP, port, and the actual request path, not just the domain from the config file.
  • Action: HTTP method plus the semantic operation. For MCP traffic this is the tool name from the JSON-RPC envelope.
  • Payload: the request body and its arguments, normalized. This is where destructive SQL, wire transfer amounts, and encoded secrets live.
  • Context: time of day, rate and budget counters for this agent, environment (staging vs production), and whether an approval token is already attached.

With those five inputs you can write rules that are specific enough to be safe and narrow enough to avoid blocking legitimate work. Without payload inspection in particular, your policy degrades into a domain allowlist. Domain allowlists are a good floor (see our guide to default-deny egress allowlists) but they cannot distinguish a read from a drop.

A versioned agent policy you can actually review

Here is the shape of a policy document that a platform team can put in a repository, diff in a pull request, and reason about six months later. Note that every rule declares an explicit effect, and the file ends with a default deny.

version: 2
metadata:
  owner: platform-security
  change_ticket: required

agents:
  - id: support-triage-agent
    identity: spiffe://cluster/ns/support/sa/triage
    rules:
      - name: read-tickets
        match:
          host: api.internal.example.com
          path: /v1/tickets/*
          method: [GET]
        effect: allow

      - name: post-public-reply
        match:
          host: api.internal.example.com
          path: /v1/tickets/*/replies
          method: [POST]
        body:
          deny_if_matches: [pii.ssn, pii.card, secrets.any]
        effect: allow

      - name: refunds-need-a-human
        match:
          tool: billing.refund
          args:
            amount_cents: '> 5000'
        effect: escalate
        approvers: [oncall-billing]
        timeout: 300s

      - name: no-metadata-service
        match:
          cidr: 169.254.169.254/32
        effect: deny
        severity: critical

default: deny

Three properties make this a policy rather than a config file. Effects are explicit and include escalate, not just allow and deny. Matching happens on tool names and argument values, not only hosts. And the default is deny, so a new capability requires a reviewed change instead of appearing silently.

From YAML to enforced action: the pipeline

  1. Model the actions. Enumerate the tools each agent has and the concrete HTTP calls each one makes. If you cannot list them, capture a week of egress traffic first and let observed behavior write the draft.
  2. Write the rules in the repository. One policy file per agent or per team, owned by a CODEOWNERS entry. Security reviews the diff, not a dashboard screenshot.
  3. Unit test the decisions. Every rule gets fixtures: a request that must be allowed, a request that must be denied, and a boundary case. A policy without tests is a comment.
  4. Run in shadow mode. Deploy the policy with enforcement off and log every decision it would have made. This is where you find the internal analytics host nobody documented.
  5. Diff shadow output against expectations. Tune the rules until the would-be denials are all things you genuinely want blocked. Track the false positive count as a release gate.
  6. Flip to enforce, per agent. Turn enforcement on for one agent identity at a time. Keep the previous policy version pinned so rollback is a revert, not a rewrite.
  7. Wire denials and escalations into your workflow. A deny should produce a structured log event with the rule name; an escalation should page a human with the full request in front of them. See our human-in-the-loop approval guide for the throughput tradeoffs.

The shadow mode step is the one teams skip and the one that determines whether policy-as-code survives contact with production. Agents make more outbound calls than anyone predicts, including retries, telemetry, and package fetches. Measure before you block.

Policy-as-code for MCP tool calls

Model Context Protocol traffic is where argument-level policy earns its keep. An MCP call is JSON-RPC over HTTP or a stream, so the host and port tell you almost nothing: one MCP server may expose a dozen tools ranging from harmless reads to arbitrary shell. A policy engine that only sees mcp.internal.example.com:8080 cannot express useful rules.

Matching on the method and params fields of the JSON-RPC envelope lets you allow repo.read_file while escalating repo.force_push, and lets you deny a tool whose description changed after approval. That requires parsing the protocol on the wire, which is exactly the gap covered in deep tool-argument inspection and implemented in the Agent G MCP gateway.

Failure modes to design around

  • Rules that only match hosts. You will block a whole SaaS vendor because one of its endpoints is dangerous. Match on path and method instead.
  • Shared identities. If every agent authenticates as the same service account, per-agent policy is fiction. Bind policy to a workload identity.
  • Unbounded escalation. If ten percent of calls need approval, approvers stop reading. Tier your actions so escalation is rare and meaningful.
  • Policy that lives only in the console. Untracked edits mean no review trail and no rollback. If it is not in Git, it is not policy-as-code.
  • No fail-closed decision. Define what happens when the policy engine is unreachable. For high-risk agents, that answer should be deny.

Why the enforcement point belongs on the wire

You can implement a policy as code ai agent model at several places in the stack, but only one of them sees the final, real request. In-process decorators are bypassed the moment the agent shells out to curl. Model-level filters never see the tool call at all. The egress boundary sees the bytes that are about to leave, after every framework abstraction has collapsed into one HTTP request.

Agent G evaluates versioned policy at that boundary as a drop-in proxy: identity-aware, argument-aware, default-deny, with allow, deny, and escalate effects, and an out-of-band log of every decision that doubles as audit evidence. The policy file is the contract; the wire is where it is enforced.

Frequently Asked Questions

What is policy as code for AI agents?

It is the practice of expressing agent guardrails as versioned, testable declarative files evaluated by an external engine at runtime. Instead of trusting prompt instructions, every outbound tool call is matched against rules on identity, destination, method, and arguments, then allowed, denied, or escalated for approval.

How is guardrails as code different from a guardrail library?

A library runs inside your agent process and inspects arguments before your SDK sends them. Guardrails as code at the egress boundary run outside the agent, so shell commands, raw HTTP clients, and injected instructions cannot route around them. Use both: the library for UX, the proxy for enforcement.

Can policy-as-code express an action level policy for LLM tool calls?

Yes, provided the engine parses request bodies and MCP JSON-RPC envelopes. Then rules can match tool names and argument values, for example escalating any refund above a threshold or denying SQL containing DROP. Host-only matching cannot express those conditions.

How do I keep a versioned agent policy from breaking production?

Ship it in shadow mode first, log every would-be decision, and compare against expected behavior. Add fixtures for each rule in CI, roll out per agent identity, and keep the prior version pinned so rollback is a Git revert rather than an emergency edit.

Put your guardrails in Git and enforce them on the wire

Prompt instructions are suggestions. A reviewed, tested, versioned policy evaluated outside the agent is enforcement. If you are ready to move your agent guardrails from documentation into a deterministic control plane, explore Agent G, compare it against other approaches on our alternatives page, or read how we block destructive agent actions at the egress boundary. Request access to the Agent G private beta and start writing policy your agents cannot talk around.

Agent G

Drop-in guardrails for the agentic era.

Intercept every network call your AI makes. Block destructive actions, enforce approvals, log everything.

Request access