An ALLOW Is Not a Permit: Designing Runtime Authorization for AI Agents
Runtime authorization for AI agents needs more than an ALLOW decision. Production systems need exact action identity, bounded permits, reservations, revocation, serialization, and reconciliation with the resulting business effect.
On this page
- Separate the decision point from the enforcement points
- Describe the exact action being authorized
- Authorization may use trusted runtime history
- ALLOW creates a time of check and time of use window
- A permit authorizes one execution opportunity
- Single use permits make authorization stateful
- Shared authorization budgets need reservations
- A reservation needs an unresolved state
- Permit identity and business operation identity are different
- Human approval should bind to the same action identity
- Credentials should be derived from the permit
- The protected resource remains the final enforcement boundary
- Broad revocation needs a fast path
- Roll policy through immutable versions
- Cache policy more aggressively than decisions
- Mutable authorization state needs a serialization domain
- Scale cheap and expensive authorization paths differently
- Failure handling has to preserve authorization and business state
- Keep an authorization evidence record
- Keep these invariants through implementation changes
A policy engine evaluates a refund and returns ALLOW.
The refund still should not execute from that decision alone.
For run R9831, reconciliation_agent_v12 wants to refund payment P9831 for $480 on behalf of user U1841 in tenant T42. At the moment policy is evaluated, the user has authority, the agent has the refund capability, the daily budget has capacity, and approval is present.
Execution happens later.
During that interval the delegation can be revoked, the payment can change, another worker can consume the remaining budget, approval can expire, or the same authorization can be replayed twice.
Runtime authorization therefore needs state and recovery semantics of its own.
The identity layer supplies trusted inputs such as the user, agent, workload, tenant, and delegation. The runtime authorization system decides whether one specific action may cross the protected execution boundary.
Separate the decision point from the enforcement points#
There are two useful responsibilities in the execution path.
The Policy Decision Point, or PDP, evaluates whether the requested action is authorized.
The Policy Enforcement Point, or PEP, prevents an action from reaching the protected resource without the authorization required for that action.
A production architecture can look like this:
The request PEP verifies the caller and constructs trusted authorization context. The PDP decides. The execution PEP validates and consumes the authorization that is allowed to cross into the protected resource.
These two PEP roles can live in one gateway in a smaller system. Keeping their responsibilities distinct matters more than putting them into separate processes.
The Policy Decision Point evaluates the exact action against policy and mutable authorization state. Request and execution enforcement points bracket the decision so a bounded permit, rather than a reusable ALLOW result, crosses the protected resource boundary.
Open diagram
In an MCP deployment, the request PEP can sit at the MCP gateway while the execution PEP sits at the MCP server or protected resource boundary. MRTR can carry a step up interaction such as human approval. The resulting action permit is still authorization infrastructure state, not MCP interaction state.
Describe the exact action being authorized#
A tool allowlist does not contain enough information for a consequential write.
The PDP should evaluate an action request containing the trusted identities, exact tool, resource, important arguments, environment, and relevant resource version.
ActionRequest {
request_id
user_subject
agent_principal
agent_version
workload_identity
delegation_id
tool
resource
normalized_arguments_hash
tenant
environment
resource_version
requested_at
}
For the running example the important values include:
tool = payments.refund
resource = P9831
amount = 480
The PDP may evaluate resource ownership, delegated scope, refund amount, current budget, approval state, risk flags, environment, resource version, and current policy.
Keep policy verdicts conceptually small: ALLOW, DENY, STEP_UP_REQUIRED, or REAUTHENTICATE.
Admission control and rate limiting are separate concerns.
Authorization may use trusted runtime history#
Some policies depend on events that occurred earlier in the run.
Suppose an agent reads an external document and immediately requests a sensitive transfer. The complete model transcript does not need to become policy input.
The tool gateway or context layer can classify returned content:
trust_class = UNTRUSTED_EXTERNAL
The runtime can maintain trusted facts such as whether untrusted data was observed, which tool classes were used, the cumulative value of prior writes, approval state, and risk flags.
Those facts are produced by infrastructure.
The model does not decide whether the content it just read should count as trusted.
That separation allows runtime history to influence policy without asking the model to describe its own security posture.
ALLOW creates a time of check and time of use window#
Suppose the PDP evaluates the following state:
user may refund P9831
agent may request refunds
budget has $900 remaining
approval exists
resource_version = 41
and returns ALLOW.
Execution may happen seconds later.
During those seconds, the delegation can be revoked, the resource can move to version 42, the approval can expire, the budget can be consumed in another region, or policy can change.
A reusable ALLOW result does not close that gap.
For consequential writes, convert the decision into a bounded action permit.
A permit authorizes one execution opportunity#
A permit carries the exact action identity and the conditions under which execution is allowed.
ActionPermit {
permit_id
user_subject
agent_principal
workload_identity
delegation_id
action
resource
normalized_arguments_hash
resource_version
policy_version
reservation_id
authorization_epoch
issued_at
execute_before
permitted_workload
status
}
Its normal lifecycle is:
ISSUED
|
v
CONSUMED
|
v
REPORTED
A permit can also expire or be revoked before use.
The execution PEP atomically consumes the permit before dispatching the protected action. A second execution using the same permit is rejected.
The permit also carries a short execution deadline. A permit created at 14:22 may need to be consumed before 14:24 rather than remaining useful for the whole agent run.
That bounds the amount of time in which resource state and authorization state can drift away from the conditions that produced the decision.
Single use permits make authorization stateful#
A signed token alone cannot guarantee single use if no component records consumption.
Suppose PERMIT_481 reaches two workers concurrently.
If both only verify the signature, both may execute.
Strong single use behavior needs an atomic transition:
ISSUED -> CONSUMED
This can be implemented with a conditional write, transactional update, or another serialization mechanism.
The additional state is not necessary for every read. It becomes much more valuable for money movement, production deployment, deletion, privilege changes, and similar operations where replay creates another business effect.
Shared authorization budgets need reservations#
Tenant T42 may have a daily automated refund budget of $5,000.
Two workers simultaneously request authorization for refunds of $3,000 each.
If each PDP only reads the currently committed amount before either write executes, both can see enough remaining capacity and both can be approved.
The budget check and reservation therefore need one atomic boundary.
AuthorizationReservation {
reservation_id
authorization_domain
policy
subject
resource
amount
permit_id
state
created_at
expires_at
}
The authorization path becomes:
ActionRequest
|
v
Policy Evaluation
|
v
Atomic Reservation
|
v
Permit Issued
|
v
Execution
The reservation can be implemented with a transaction, conditional write, compare and set operation, or another mechanism that prevents concurrent decisions from spending the same capacity.
A reservation needs an unresolved state#
Success and release are not enough.
Consider this sequence:
reserve $3,000
|
v
permit issued
|
v
permit consumed
|
v
payment API commits
|
v
response lost
The caller sees a timeout.
Releasing the reservation can now make already spent authorization capacity available again.
The reservation state machine should include an unresolved branch:
RESERVED
|
+----------+----------+
| |
definitely unused permit consumed
| |
v v
RELEASED EXECUTING
|
+----------+----------+
| |
outcome known outcome unknown
| |
v v
COMMITTED UNRESOLVED
|
v
reconciliation
A consumed permit with an unknown business outcome does not release the reservation.
A generic TTL must not quietly return that capacity to the available budget either. Expiration is safe when the permit was never consumed. Once execution may have reached the external system, business state must be reconciled first.
A consumed permit with an unknown external result must not release its reservation. The authorization budget remains unresolved until the business operation is reconciled.
Open diagram
Permit identity and business operation identity are different#
For the refund:
permit_id = PERMIT_481
operation_id = R9831_S14_REFUND
The permit records that execution was authorized.
The operation ID identifies the business effect across retries and recovery.
Suppose the permit is consumed and the worker crashes before receiving the result from the payment service.
Recovery should reconstruct:
permit = CONSUMED
reservation = UNRESOLVED
operation_id = R9831_S14_REFUND
Then reconcile the payment service using the business operation identity.
If the refund exists, the reservation becomes COMMITTED.
If the payment service can prove that the refund never happened, the reservation can be released. Any later execution requires a new authorization decision and a new permit.
Do not reuse the consumed permit.
Human approval should bind to the same action identity#
For P9831, policy requires approval for the $480 refund.
Store the action that the reviewer actually approved.
Approval {
approval_id
tool
resource
normalized_arguments_hash
resource_version
policy_version
approver
approved_at
expires_at
status
}
If the amount changes from $480 to $4,800, the approval no longer matches.
If the payment changes materially after approval, policy may require another review.
Where approval is intended for one action, consuming it also requires concurrency control. Two workers should not both exercise one approval.
Credentials should be derived from the permit#
Some protected services need a credential they can validate locally.
A credential broker can derive a short lived credential from the permit:
Credential {
agent = reconciliation_agent
acting_for = U1841
scope = payments.refund
resource = P9831
max_amount = 480
permit_id = PERMIT_481
decision_id = AD481
expires_at = 14:24
}
The payment service validates the credential and actual request together.
The credential does not replace permit consumption when single use execution matters.
For higher risk actions, the credential can also be bound to the workload identity or a workload held key. Possession of the token alone is then insufficient to exercise it from another process.
The protected resource remains the final enforcement boundary#
The payment service owns P9831.
It should validate enough trusted authorization context to know which agent is acting, which user delegated authority, which permit allowed execution, which resource and arguments are permitted, and whether the execution window is still valid.
The model does not enforce those constraints.
A compromised model can propose a different resource or amount. It cannot produce a valid permit for that modified action.
Authorization therefore gives a deterministic containment boundary around protected capabilities and resources even when model behavior is compromised. It does not solve misuse of information the agent was already legitimately allowed to read, which remains a separate data security problem.
Broad revocation needs a fast path#
Normal policy and identity changes can propagate through versioned snapshots and credential expiry.
A compromised agent may need immediate containment.
One useful mechanism is an authorization epoch.
authorization_domain = T42/payments
current_epoch = 184
A permit issued for that domain carries epoch 184.
A broad kill switch increments the domain:
current_epoch = 185
Once the execution PEP observes the new epoch, old permits can be rejected without enumerating them individually.
Fine grained revocation still needs state for specific delegations, credentials, or permits.
The epoch gives the platform a cheap bulk invalidation mechanism.
Roll policy through immutable versions#
Authorization decisions need reproducible semantics.
A controlled rollout can follow:
Policy Source
|
v
Validation
|
v
Compile
|
v
Snapshot v43
|
v
Shadow Evaluation
|
v
Regional Canary
|
v
Activate
Before activation, compare decisions made by the current policy and the candidate version.
If version 42 returns ALLOW and version 43 returns DENY for the same action, that delta should be visible before rollout reaches every region.
Keep the previous compiled snapshot available for rollback.
Do not mutate the meaning of an existing policy_version after decisions and permits have already been produced under it.
Cache policy more aggressively than decisions#
Compiled policy, tool bindings, and stable agent metadata are good cache candidates.
A final ALLOW is much more sensitive to mutable state.
It can become invalid when delegation, resource state, budget, approval, revocation, or policy changes.
For consequential writes, evaluate against current runtime state instead of relying on reusable cached allow decisions.
If a low risk decision is cached, the key needs to include the relevant state identities:
agent
user
delegation_version
action_hash
resource_version
policy_version
authorization_state_epoch
Caching policy evaluation machinery is normally safer than caching the final verdict.
Mutable authorization state needs a serialization domain#
Regional PDPs are useful for latency and availability, but shared mutable authorization state cannot always be updated independently in every region.
Suppose the remaining refund budget is $3,000.
us_east requests authorization for $2,000.
us_west requests authorization for $2,000 at the same time.
If each region evaluates against its own copy of the $3,000 state, both can approve.
Stable policy can be replicated broadly. Budgets, reservations, approval consumption, and other state that changes the verdict need a clear consistency owner.
One approach is a logical authorization domain:
authorization_domain = T42/refund_budget
home_partition = AUTH_27
All mutations for that budget serialize through AUTH_27, even though PDPs exist in many regions.
Another design can use a globally consistent store.
The important invariant is that two independent replicas cannot reserve the same remaining authorization capacity.
Stable policy can be evaluated regionally, but mutable authorization state such as budgets and reservations needs a clear serialization domain so two regions cannot spend the same remaining capacity.
Open diagram
Unrelated tenants and budgets can live on different partitions, so strong consistency for one authorization domain does not require serializing the whole platform.
Scale cheap and expensive authorization paths differently#
Assume 10,000 active agents generating five authorization requests per second at peak.
That produces 50,000 decisions per second.
Suppose 95 percent are low risk read operations and 5 percent are consequential writes.
The rough split becomes:
47,500 decisions / second
regional read path
2,500 decisions / second
mutable authorization path
The read path may need only compiled policy, agent metadata, delegation metadata, and tool binding, all of which can be served regionally.
The write path may need fresh resource state, a budget reservation, approval consumption, revocation state, and permit creation.
Classify the action using trusted tool metadata and policy before loading the expensive state.
That prevents every harmless lookup from paying the consistency and latency cost of financial authorization.
Failure handling has to preserve authorization and business state#
If a regional PDP fails, another PDP can evaluate using the same immutable policy snapshot.
If mutable authorization storage is unavailable, writes requiring fresh reservations, approval consumption, or current revocation state should not silently fall back to stale data.
If reservation succeeds but permit issuance fails, release the unused reservation or reclaim it through bounded recovery.
If a permit expires without being consumed, its reservation can normally be released.
If the permit was consumed and the business result is unknown, move the reservation to UNRESOLVED.
If the outcome report is delivered twice, apply the transition idempotently.
If revocation races with an action already executing, define the commit boundary. Before the protected resource accepts the operation it may still be cancellable. After irreversible commit, revocation prevents future actions but does not erase the business effect that already occurred.
Authorization state and business state are separate systems. Failure handling has to respect both.
Keep an authorization evidence record#
For consequential writes, persist enough information to reconstruct why execution was allowed.
AuthorizationEvidence {
decision_id
user_subject
agent_principal
agent_version
workload_identity
delegation_id
tool
resource
arguments_hash
policy_version
authorization_epoch
approval_id
reservation_id
permit_id
credential_id
operation_id
decision
outcome
decided_at
executed_at
}
For R9831_S14_REFUND, an incident investigator should be able to determine who initiated the work, which agent proposed the refund, which workload executed it, which delegation supplied authority, which policy version evaluated it, which budget was reserved, whether approval was required, which permit crossed the execution boundary, and which business operation finally committed.
That is the evidence needed when the question is no longer whether a token was valid, but why a production agent was allowed to change something.
Keep these invariants through implementation changes#
-
An authorization decision is not itself execution authority for a consequential write.
-
A sensitive action is authorized against the exact resource, meaningful arguments, policy version, and relevant resource state.
-
A single use permit cannot authorize two execution attempts.
-
Concurrent authorization decisions cannot consume the same budget or approval twice.
-
A consumed permit with an unknown business result does not release its reservation.
-
Permit identity and business operation identity remain separate.
-
A new execution attempt after a consumed permit requires a new authorization decision.
-
Human approval becomes invalid when the material action differs from what was reviewed.
-
The protected resource validates the authorization constraints for the resource it owns.
-
Mutable authorization state that changes a verdict has one clear serialization domain.
-
Cached authorization results cannot silently survive changes to delegation, resource state, policy, approval, or revocation.
-
Broad revocation has a path faster than ordinary policy and credential expiry.
-
Authorization failure does not rewrite a business effect that may already have committed.
-
Every consequential external action can be traced through the decision, reservation, permit, credential, approval, and business operation that produced it.
Keep reading
Continue with a guided sequence of free production engineering essays.
Find your next reading pathRelated reading
Continue this path
How Coding Agents Actually Work: Inside the Agent Harness
A technical look inside coding agent harnesses, covering context management, repository search, tools, sandboxes, subagents, durable sessions, recovery, concurrency, completion and evaluation.
Read essayDesigning Containment for Production AI Agents
Production AI agent containment needs more than a sandbox. It needs separate information, execution and effect boundaries, scoped capabilities, egress controls, quarantine, and independent runtime enforcement.
Read essayDesigning Continuous Evaluation for Production AI Agents
Production AI agent evaluation needs more than task success. It needs behavioral checks, trajectory analysis, outcome verification, failure lineage, repeated trials, statistical release gates, and production failures feeding new evaluation evidence.
Read essay