Most AI Agents Still Run on Stateless Authorization. Few Teams Have Caught Up.
Checking if an identity has a capability isn't the same as knowing whether it should act right now — and the fix already exists, just not in most stacks yet.
Scope & limitations — read first
AI agent architecture · authorization design · policy-as-code · workflow state machines
Most AI agent authorization today is stateless. Check whether the identity has the capability, then let it call the tool. It's the same pattern API gateways have used for a decade: a role, a scope, a yes or no.
That pattern works fine for API gateways. It falls apart the moment you point it at a real business process.
Why a fixed grant isn't enough
Authorization in a real workflow isn't a permission you hold. It's a function of state — which step the process is on, what's been verified so far, what the last outcome was, how much is actually at stake in this specific decision.
The same agent can be fully trusted to classify an incoming document, need a human in the loop before it sends a denial notice, and be entirely prohibited from issuing a payment — on the same case, an hour apart. None of that is captured by "does this identity have the send-notice scope." It has the scope the whole time. Whether it should use it right now depends on everything that happened before this moment.
The two failure modes
Teams that notice the gap tend to land on one of two bad answers.
| Failure mode | What it looks like | Why it breaks |
|---|---|---|
| Over-permission | Broad tool access granted up front, prompt instructions asked to keep the agent "in line" | The prompt is not an enforcement boundary. One bad tool call, one injected instruction, one edge case the prompt didn't anticipate, and the agent does something it was never supposed to do. |
| Over-constrain | A rigid, hardcoded sequence of allowed actions per step | Real processes have exceptions. The first case that doesn't fit the sequence either breaks the workflow or forces a human to route around the system entirely, which quietly undoes the whole point of automating it. |
Both failure modes come from treating authorization as decided once, rather than evaluated continuously.
What decision-time authorization looks like
The alternative is to stop treating authorization as a role check done once at session start, and start treating it as a decision made at the point of each action — evaluated against current process state, not a static permission list.
- The check happens per action, not per session — a new decision every time the agent reaches for a tool
- The input is process state, not just identity — step, prior verifications, last outcome, amount at stake
- The same tool can be allowed, allowed-with-review, or denied depending entirely on where the case sits right now
- Every decision is traceable back to exactly why it was allowed — not just that it was
That last point matters more than it sounds like it should. "Allowed" without a reason is a liability the moment something goes wrong, because nobody can tell whether the system worked as designed or got lucky. A decision log that says which state the process was in, which policy fired, and why the action cleared is the difference between an incident review that takes an hour and one that takes a week.
The systems that get this right won't check who the agent is once. They'll check what's true about the process, every single time it reaches for a tool.
Why most teams still default to the static version
Most of the tooling built for agents so far — MCP servers, tool-calling frameworks, gateway products — inherited authorization from the API world, because that's the closest existing pattern. It's a reasonable starting point and a bad ending point. API gateways were built to answer "can this client call this endpoint," a question with no notion of business process behind it. Agents don't call endpoints. They participate in processes. The authorization model needs to know that.
This isn't uncharted territory. OWASP's guidance on LLM applications names this exact gap — excessive agency, where an agent holds more standing permission than a given moment warrants — and a handful of authorization vendors are already building runtime, per-action policy engines for agents specifically, with step-up human review as a first-class primitive rather than an afterthought. What's missing isn't the concept. It's adoption. Most teams building agents right now are still focused on getting tool calls to work reliably at all, and authorization is a config file of allowed scopes bolted on afterward. That's fine for a demo. It's not fine for an agent with write access to a system of record.
References
Reference
OWASP Top 10 for LLM Applications — LLM06: Excessive Agency. Names the static-permission gap directly and recommends authorization be enforced downstream, per action, rather than trusted to a one-time grant or the model itself.
Reference
Cerbos — Dynamic Authorization for AI Agents: Fine-Grained Permissions in MCP Servers. Argues static RBAC/scope checks are insufficient for agents and proposes attribute- and context-aware per-action decisions.
Reference
Cerbos — AI Agent Authorization & Access Control for Agentic Systems. Describes a policy decision point evaluating each agent action in context, with step-up human-in-the-loop approval for higher-stakes actions.
Reference
Permit.io — Delegating AI Permissions to Human Users with the Access Request MCP. A concrete implementation of per-step human-in-the-loop escalation inside an agent workflow.
Reference
Microsoft Security Blog — Authorization and Governance for AI Agents: Runtime Authorization Beyond Identity at Scale. A major vendor making the same case for shifting from static identity grants to runtime, decision-time authorization for agents.
Open questions
What's the right storage layer for process state that authorization decisions need to read — event log, state machine, or something else?
How much of this can be pushed into the MCP/tool-calling layer versus needing an external policy engine?
Does this converge with existing workflow engines (Temporal, Camunda) or does it need its own primitive?
Comments