← Blog

Approvals & policy · · 10 min read

Policy at action time: what the gate evaluates

The five inputs and three checks a runtime policy gate needs to decide whether an agent action should run, with policy-language and approval-UX considerations.

Jonathan Haas

The control plane sits between the agent's decision and the system the agent wants to act on. The question of where, exactly, that gate lives is the first architectural decision in any agent runtime. In-process gives the lowest latency and the worst isolation. Sidecar trades a few milliseconds of latency for clean isolation and a separate failure domain. Gateway centralizes the policy surface for all agents but adds a network hop. Most production deployments end up with a gateway model because the policy surface is the highest-leverage thing to centralize, and the latency hit is bounded.

Wherever it lives, when the agent picks an action, the gate decides — before the action runs — whether to allow, require approval, or deny. The decision depends on five inputs and three checks.

The five inputs#

  1. Identity. Which agent is asking. Which sub-agent in which run. Which operator launched the run. Identity is plural: the agent has an id, the run has an id, the operator has an id, and each propagates. A gate that only knows the service account is operating on the wrong principal.

  2. Resource. What the action targets. pg.write to which database; github.repos.delete to which repository; s3.put to which bucket prefix. The resource is the noun the policy reasons about. Some resources are coarse (a database); some are fine (a row in a database scoped to a tenant id). The gate's resource model has to match the granularity of the access control the underlying system supports.

  3. Arguments. What the agent intends to do with the resource. A pg.write with a row id from the customer the agent is helping yields a different decision than a pg.write with a row id from a customer in a different tenant. The gate has to read the arguments — the specific row, bucket, or repo the action targets. Arguments are the field where most cross-tenant reach incidents become detectable; without argument inspection, the gate is reduced to checking that the agent has the broad permission, which it always does.

  4. Run context. Why the run is happening. A run triggered by an inbound support ticket carries a different intent than a run started by a scheduled posture sweep. The run context is the prior on what the agent should plausibly do. Policies that don't condition on run context end up either too permissive (allowing the support agent to do whatever the posture sweep can do) or too restrictive (forcing the posture sweep to re-prove what every operator already accepts).

  5. Policy version. Which version of the policy is in force right now. The audit row will be evaluated against the same version six months later when the security team investigates. Without it, the team cannot answer "was this allowed under the policy that was active then" — only "is this allowed under the policy that is active now," which is a different question.

The three checks#

For every action, the gate runs three checks against the five inputs.

Scope check. The resource and arguments fall inside the identity's bounded scope for this run. If the agent has scope to customer A, this action is on customer A. The scope check is the one that fires on cross-tenant reach attempts; it's the highest-leverage check because the failures it catches are the ones most operators find hardest to detect downstream.

Type check. The action class is allowed at all. read from a known list of resources; write only with approval; delete only with approval-plus-second-pair-of-eyes. The type check is policy-language-friendly — it maps cleanly to RBAC and ABAC rules — and it's where most teams start. It does not, on its own, prevent cross-tenant reach.

Outcome check. The action's effect can be undone or contained inside the run. Destructive actions that escape the run boundary require approval before they execute. The outcome check is the one teams underbuild. It requires the gate to model what each action does — specifically, what state it changes and whether that change is reversible inside the run. A s3.put to a bucket the agent regularly writes to is undoable in seconds; a s3.delete-bucket is not. The gate that doesn't distinguish these treats them as the same kind of action.

Latency budget#

Microsoft's Agent OS — part of their open-source Agent Governance Toolkit — targets sub-millisecond evaluation per action (Microsoft blog). That is the bar production gates have to clear. Above 10 milliseconds per action, the agent's response time starts to feel different to the user; above 50, interactive runs become painful; above 100, the gate is a bottleneck the agent's authors will route around if they can.

Three places latency hides in a policy gate: policy compilation (the cost of turning Rego or Cedar text into an evaluator), policy evaluation (the cost of running the evaluator against the inputs), and policy I/O (the cost of fetching the identity, the policy version, and any state the policy depends on). Compilation is amortized — once per policy version. Evaluation is the hot path. I/O is the unbounded one if the policy fetches external state on every action; the discipline is to compile state into the policy at version-load time and avoid I/O on the evaluation path.

OPA Rego vs Cedar#

Two open-source policy languages dominate runtime authorization in 2026: Rego (the language used by Open Policy Agent, a CNCF-graduated project) and Cedar (the language open-sourced by AWS, used internally for IAM and Verified Permissions). The two are not quite interchangeable; they reflect different design philosophies.

Rego is general-purpose. It treats policy as a logic program, with rules that can compose freely. The expressiveness is a strength — Rego can encode any policy you can describe — and a liability — Rego policies are easy to write in a way that has unexpected evaluation cost. The OPA ecosystem is large; tooling and integrations are abundant.

Cedar is purpose-built for authorization. It is more restrictive than Rego by design — Cedar policies can be analyzed for properties like termination before evaluation. The engine can prove a policy will always terminate in a bounded number of steps.

For an agent-runtime gate, the relevant differences are: Cedar's analyzability is useful when the security team needs to prove the policy never allows a class of action; Rego's expressiveness is useful when the policy needs to incorporate complex state (e.g., the number of times this agent has performed this action in the last hour). Recent benchmarks show both within an order of magnitude of each other in raw evaluation speed, with the gap depending heavily on policy shape (Teleport benchmark).

Microsoft's Agent OS supports both, plus a YAML rule format for simpler cases. The pattern of supporting multiple policy languages — letting teams pick the right tool per use case — is becoming standard.

The same policy in both languages#

A policy that says: an agent acting on behalf of an operator can delete a repo only if the operator is in the repo-admins group and the repo is owned by that operator's organization.

In Rego:

package agent.policy

default decision = "deny"

decision = "allow" if {
  input.action == "github.repos.delete"
  input.operator.groups[_] == "repo-admins"
  input.resource.owner == input.operator.org
}

In Cedar:

permit (
  principal in Group::"repo-admins",
  action == Action::"github.repos.delete",
  resource
) when {
  resource.owner == principal.org
};

The Cedar version is shorter. The Rego version is more flexible — adding "and only between 9am and 6pm in the operator's timezone" is straightforward in Rego, awkward in Cedar.

Allow vs require-approval vs deny#

A policy gate that only allows and denies is a coarse instrument. The richer outcome that production gates emit is a three-valued decision: allow, require-approval, deny.

Allow. The action proceeds without human intervention. This is the default for actions the policy has explicit confidence in — reads of resources the agent's identity is scoped to; writes with low blast radius; tool calls that don't touch persistent state.

Require-approval. The action is held pending operator confirmation. The agent waits. The operator receives a notification (the UX choice is discussed below); on confirmation, the action proceeds; on rejection, the agent receives a denied outcome and decides what to do next. Require-approval is the right outcome for actions that are reasonable in context but consequential — writes to customer-impacting state, deletes of recoverable resources, sends to external systems. This is the mechanism that makes negative discovery lag possible: the operator sees the action before it runs, not after.

Deny. The action does not proceed. The agent receives the decision and continues with whatever its plan looks like absent the denied action. Deny is the right outcome for actions that are out-of-scope regardless of intent — a customer-support agent attempting to read another customer's data should be denied without an approval prompt.

The distinction between deny and require-approval is the one most policy designs get wrong on first pass. Teams that start with binary allow/deny tend to default-deny aggressively, which produces an agent that constantly fails. Teams that start with require-approval-on-doubt tend to flood operators with prompts that train them to click through. The right partition is policy work: which classes of action have a context in which they're reasonable, and which are out-of-scope regardless.

Approval UX: sync vs async#

When the gate returns require-approval, the agent waits. Where the operator approves is the next design question.

Synchronous. The operator is already attending to the agent's run — typing into a chat interface, watching a CLI session — and the approval happens in-stream. The experience is good; the approval is part of the work. The constraint is that the operator has to be present. Synchronous approval doesn't work for runs that outlive the operator's attention.

Asynchronous. The operator receives a notification — Slack, email, a web inbox — and approves out-of-band. The agent waits. The experience is worse, but the operator can be elsewhere, and approval doesn't block the operator's other work. Asynchronous approval scales to operators who oversee many runs.

Production agent runtimes ship both. The choice of which to use is per-policy: a policy that says "writes to the production database require approval" might run sync during a developer's interactive session and async during a scheduled posture sweep.

A useful additional control is the approval window. An action that has been waiting on an operator's approval for an hour is probably no longer the action that should run; the agent's plan has likely moved on. Policies that emit require-approval should also emit a deadline beyond which the approval auto-denies.

The MCP gateway pattern#

In 2026, the most common place to put the gate is in front of the MCP servers the agent calls. Model Context Protocol has become the dominant substrate for agent-tool integration, and the MCP gateway pattern centralizes identity verification, policy evaluation, and audit emission at a single chokepoint between agents and tools.

Red Hat's OpenShift MCP gateway, Kong's MCP tool governance, Tyk's enterprise MCP gateway, and Arcade's MCP gateway are public instances of the pattern, each making the same architectural choice: tools live behind a gateway; the agent's requests carry an identity; the gateway evaluates the policy; the gateway emits the audit row. Microsoft's Agent OS sits in the same architectural slot, with the difference that it intercepts before the MCP request is constructed rather than at the MCP boundary.

The gateway position has two advantages over per-tool enforcement. First, every tool server doesn't have to reimplement scope-checking, operator-claim verification, and audit emission. Second, the policy version that evaluated the action and the audit row that recorded it come from the same component, so they cannot disagree.

The action-time tradeoff#

Evaluating at action time costs latency on every action. A check that only runs at run start cannot see actions the agent picks later in the run. The mid-run actions are precisely the ones cross-tenant reach incidents emerge from, because the agent's mid-run decisions depend on what it observed earlier in the run — which a run-start check cannot model.

The latency cost is bounded. The visibility benefit grows with the complexity of the agent's run. For runtimes operating on systems where any action can reach data the operator shouldn't reach, action-time evaluation is the correct default.

Deixic's gate runs these five inputs and three checks. See What you can do for the action-time decision, Trust for containment.