Runtime Authorization for AI Agents: Decision Contract and Tests
Put deterministic AI agent authorization in the runtime path with a typed decision contract, bounded latency, fail-closed behavior and replay-safe tests.

Runtime authorization decides whether an AI agent may perform one exact action using current identity, authority, policy and business state. It runs after the agent has proposed structured arguments and before the side effect begins. Putting the check only in the prompt, planning layer or admin console leaves the execution boundary unprotected.
The AI agent authorization guide covers the complete architecture. This article specifies the live decision contract, latency path and failure tests.
Define a small decision contract
The policy service should not receive raw conversation history. It should receive verified and normalized facts.
type RuntimeDecisionInput = {
principal: { id: string; tenantId: string; status: 'active' | 'blocked' };
agent: { id: string; version: string };
authority: { grantId: string; grantDigest: string };
request: { action: string; resource: string; canonicalDigest: string };
context: { now: string; riskClass: string; stateVersion: string };
};
type RuntimeDecision = {
effect: 'allow' | 'deny' | 'approval_required';
reasonCodes: string[];
inputDigest: string;
policyDigest: string;
obligations: string[];
expiresAt: string;
};
The enforcement point must compare the returned inputDigest with the request it will dispatch. This catches a time-of-check to time-of-use change. It must also reject expired decisions and unknown obligations. An allow with an unsupported obligation is not an allow.
Keep the model outside the allow path
The model may classify purpose, extract candidate fields or assign a review score. Convert those outputs into typed inputs and verify them against systems of record. A confidence score may route a request for review. It must not override a failed destination, mandate, approval or tenant check.
agent proposal
-> schema validation
-> identity and audience validation
-> authority resolution
-> current business-state lookup
-> deterministic policy decision
-> atomic replay and usage reservation
-> exact-request comparison
-> protected operation
-> evidence record
OWASP's agentic threats and mitigations describes risks such as tool misuse, identity abuse and manipulated context. A runtime boundary limits the effect of a bad plan because the protected action still needs to satisfy external rules.
Budget latency by dependency
Fast authorization is not achieved by skipping checks. Package slow-changing trust state for local verification, use versioned policy bundles, and keep fresh mutable checks narrow.
| Dependency | Typical strategy | Failure behavior for high-risk writes |
|---|---|---|
| keys and issuer metadata | signed cache with expiry | deny if beyond allowed staleness |
| policy | local versioned bundle | deny if required version is absent |
| revocation | bounded-freshness cache | deny when freshness cannot be established |
| replay and usage | atomic low-latency store | deny or hold when unavailable |
| business state | authoritative service | deny or require review when unknown |
| evidence sink | durable local queue | allow only if policy permits deferred delivery |
Do not hide dependency failures behind a generic policy denial. Return stable internal reason codes while keeping external error detail minimal. Operators need to distinguish POLICY_BUNDLE_MISSING from ACTION_NOT_ALLOWED.
Run failure fixtures before production
Test the boundary with stale, conflicting and concurrent inputs:
- Revoke a grant between resolution and dispatch. The final freshness check must stop the action.
- Send the same nonce from two workers. One atomic claim may succeed.
- Return
allowfor a different request digest. The enforcement point must reject it. - Remove the policy bundle during a sensitive write. The path must not fall through.
- Change a human-approved amount after approval. The decision must return
approval_requiredagain. - Bypass the gateway through an internal route. Network and service identity controls must block it.
See AI agent permissions for the grant model and fail-open versus fail-closed behavior for outage classes. RFC 9700 supplies OAuth security practices, but transaction semantics remain an application concern.
OATI's developer-preview components support signed objects, deterministic evaluation examples and conformance fixtures. The public status does not claim completed independent review or customer production fleet operations. Inspect the OATI developer path before adapting the contract.
Runtime design questions
Should policy evaluation be local or remote?
Local evaluation reduces network latency and keeps decisions available during a control-plane interruption. It needs signed, versioned policy distribution and explicit freshness rules. Remote evaluation simplifies central updates but places a service and network call in every protected action. A hybrid design commonly evaluates a local bundle while querying narrow mutable state such as revocation, remaining uses or current supplier status. Measure the whole decision path under failure, not only the policy function.
When should the system reserve authority?
Reserve replay, usage and limited budget after the request is fully validated and before the external side effect. The reservation must bind the same request digest returned by policy. Define what releases a reservation: a pre-dispatch failure may release it, while a provider timeout usually leaves it held until reconciliation. Record each transition. Otherwise operators will be forced to choose between duplicate execution and permanently stranded capacity.
Can a cached allow decision be reused?
Only under an explicit rule that proves the request, authority, policy and relevant state are unchanged and the decision has not expired. Caching by tool name or agent ID is unsafe because two calls can carry different amounts, destinations or records. Material writes usually benefit more from locally cached policy inputs than from cached allow results. If results are cached, include the complete input digest and remaining obligations in the key.
What should the caller learn from a denial?
Give internal operators stable reason codes and enough correlation data to diagnose the control. Give an untrusted caller only the detail needed to recover safely. Revealing every permitted counterparty or exact remaining budget can help an attacker probe policy. Separate public error text from the protected decision event, and never include secrets or complete authority objects in either.
How is a policy release made safe?
Test the new bundle against saved allow, deny, approval and failure fixtures before signing it. Compare decisions with the previous bundle and require review for changed material outcomes. Roll out by tenant or action class, watch denial and dependency signals, and preserve the earlier bundle for receipt verification and incident reconstruction. A rollback should restore policy behavior without changing the recorded digest on earlier decisions.
One last acceptance test is easy to miss: send a request that policy allows, then change a protected field inside the adapter before the upstream call. The adapter must recompute or compare the request digest and stop. This proves runtime authorization protects the operation that will execute, not merely the object that reached the policy service.