
TECHNICAL GUIDE
Permission Models for Autonomous Systems
Reach and initiative are different questions. A table separating safe from idempotent from reversible, what MCP's tool annotations do and don't let you enforce, and how a checkpoint became a protocol message.
TL;DR
Whether an agent can reach a system and how much initiative it gets once it is there are two different questions, and most permission models only ever answer the first one.
The protocols have started answering the second. MCP's tool annotations carry readOnlyHint,
destructiveHint, idempotentHint and openWorldHint, and the defaults are pessimistic on purpose:
destructiveHint and openWorldHint both default to true. An unannotated tool is, by the spec's own
defaults, destructive and open-world.
The common fix is a checkpoint on everything, and it fails for a reason the EU AI Act names in Article 14(4)(b): people asked to confirm hundreds of actions develop a "tendency of automatically relying or over-relying on the output", which is a signature you cannot audit your way out of.
We are not arguing that annotations solve this. The same spec says those properties are "hints" that are "not guaranteed to provide a faithful description of tool behavior", and clients MUST treat them as untrusted from untrusted servers. They sort your catalog. They cannot be your authorization input.
Sort every action an agent can attempt by what happens if it is wrong, using reversibility rather than which system it touches, and enforce the resulting tier where the call leaves your infrastructure.
Overview
Traditional software performs predefined actions through predictable paths. A payroll system runs the payroll job. A ticketing tool moves a ticket through fixed states. What the software can do is enumerated by its developers, and permissions gate which of those fixed paths a user may trigger.
Agents do not work that way. One agent can interpret a request, choose which tools to call, chain several steps, and land on an action nobody wrote down in advance. Two people asking the same question can send it down different sequences. That is the point of building one, and it is why a grant answering "can this agent reach the system" tells you almost nothing about what happens next.
Reach and scope are covered elsewhere in this series. What follows is about the layer most permission systems skip because human-shaped access control never needed it: how much initiative an agent gets, on which specific action, and what enforcement that requires. OWASP's own taxonomy already separates the two. Its LLM06:2025 entry on excessive agency names three distinct drivers, excessive functionality, excessive permissions and excessive autonomy, and only the middle one is what an access-control system usually models.
The core shift: a static grant answers whether an action is allowed. It says nothing about whether the agent should take it without asking first.
Why Agents Are Different
We do not think you can secure an agent by enumerating its workflows, because the workflows are generated at runtime. That does not mean agents need unlimited freedom. It means the thing being secured is the boundary of what an agent may attempt, not a list of paths it may follow.
Most existing access-control thinking still applies. Attribute-based access control is the closest fit in the standards, and NIST defined it in SP 800-162 as authorization "determined by evaluating attributes associated with the subject, object, requested operations, and, in some cases, environment conditions against policy, rules, or relationships". That last clause is where the agent-specific work lands. The document is from January 2014, updated in 2019: context-aware authorization is not new, it is newly load bearing.
What none of it covers is a question with nothing to do with reach. Given that an action is fully allowed, should it happen automatically, or should a person see it first? An agent that is perfectly safe drafting a document can be dangerous the moment it may send, publish or delete without anyone looking.
Reach Is Not the Same as Initiative
Getting reach right is its own discipline: which tools an agent may connect to, which actions it may take inside each, which records are in scope, and whose authority it acts under when a person is involved. Those four answer "what could this agent possibly do."
Briefly: a procurement agent might reach the vendor catalog and the contracts repository and nothing else. Inside those it reads and drafts. And when it acts for a specific buyer, its effective reach narrows to whatever that buyer is separately allowed to do.
None of that says whether the agent should act the moment it decides to. That is a fifth question, orthogonal to the four above, and it is the one worth the rest of the page.
Autonomy Level
Even when an action clears every reach check, one thing is undecided: should the agent perform it automatically, or pause for a person? Authorization says the action is allowed. Autonomy level says how much initiative the agent gets.
Think of it as a dial rather than a switch:
- Suggest: the agent proposes an action and takes none.
- Draft: the agent prepares the work product, an email, a config change, a record, and stops short of committing it.
- Execute with a checkpoint: the agent is ready to act, and a person clears the final step:
aact:human-oversight/approval-for-high-riskin the Agent Access Control Taxonomy. - Execute autonomously: the agent acts on its own, where the action is cheap to undo.
The right level is rarely uniform across one agent. An IT-operations agent might restart a stateless service on its own, drain a node only with a checkpoint, and merely suggest a firewall change. The dial is set per action, not per agent.
There is a shipped example of this shape worth reading, because it shows the dial as enforcement rather than intent. Claude Code splits permissions into allow, ask and deny rules, where ask rules "prompt for confirmation whenever Claude Code tries to use the specified tool". Ordering is fixed and precedence explicit: "Rules are evaluated in order: deny, then ask, then allow", and "An allow rule can't carve an exception out of a deny rule." Above the rules sit modes, from a plan mode that reads but does not edit to a bypass mode the docs say to use "only in isolated environments like containers or VMs".
The sentence that matters most in that documentation is about where the boundary lives: "Permission
rules are enforced by Claude Code, not by the model. Instructions in your prompt or CLAUDE.md shape
what Claude tries to do, but they don't change what Claude Code allows."
Reversibility Has a Wire Format
The sorting key for the dial is reversibility, and it needs separating from two neighbours it gets confused with. RFC 9110 defines a method as safe when "their defined semantics are essentially read-only; the client does not request, and does not expect, any state change", and idempotent when "the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request". GET, HEAD, OPTIONS and TRACE are safe. PUT and DELETE are idempotent without being safe. POST is neither.
Reversibility is a third thing, and DELETE is the proof. It is idempotent, so a client "MAY automatically repeat a request if an error response is received, provided that the request method is idempotent and the request does not include content". Repeating it is harmless. Undoing it is impossible unless the store keeps versions. A model that conflates retry safety with undo will let an agent delete on its own initiative because the verb looked well behaved.
| Action | HTTP shape | Safe | Idempotent | Reversible | Autonomy tier |
|---|---|---|---|---|---|
| Read a ledger line | GET | yes | yes | not applicable | autonomous |
| Draft an email | POST /drafts | no | no | yes, delete the draft | autonomous |
| Send that email | POST /send | no | no | no | person, every time |
| Set a record field | PUT | no | yes | only if you stored the prior value | autonomous, with the prior value logged |
| Delete an object | DELETE | no | yes | no, unless the store versions | person |
| Charge a card | POST /charges | no | no | a refund is a second act, not an undo | person |
| Restart a stateless worker | POST /restart | no | no | yes | autonomous |
Table 1 — Safe, idempotent and reversible are three different columns, and only the last one should set the dial.
MCP gives this a vocabulary on the wire. A tool definition may carry annotations: readOnlyHint
("If true, the tool does not modify its environment", default false), destructiveHint ("If true,
the tool may perform destructive updates to its environment", default true, and meaningful only when
readOnlyHint is false), idempotentHint (default false) and openWorldHint ("If true, this tool
may interact with an 'open world' of external entities", default true).
{
"name": "archive_invoice",
"description": "Move an invoice to the archive",
"inputSchema": { "type": "object", "properties": { "id": { "type": "string" } },
"required": ["id"] },
"annotations": {
"title": "Archive invoice",
"readOnlyHint": false,
"destructiveHint": false,
"idempotentHint": true,
"openWorldHint": false
}
}Read the defaults again, because they are the designed-in position. A server that says nothing has declared a destructive, non-idempotent, open-world tool. That is the right default and it is also a trap in reverse: the schema's own comment says "all properties in ToolAnnotations are hints. They are not guaranteed to provide a faithful description of tool behavior", and the specification adds that clients "MUST consider tool annotations to be untrusted unless they come from trusted servers".
So we think annotations are an excellent way to sort a catalog on day one and an unacceptable place to put an authorization decision. Use them to generate the first draft of your tier table, then store the tier on your side, keyed to the tool identity you control.
Reversibility is the cheapest risk signal available, and it is not the same as idempotence. The question is not "is this allowed" or even "is this retry-safe". It is "can this be undone", and an action that cannot should cost a person's attention.
Context
The dial is not fixed forever either. The same action can be fine in one circumstance and wrong in another, which is the environment-conditions half of the ABAC definition doing real work.
A sales agent might apply a discount up to ten percent on its own and need a person past that. A data-pipeline agent might run transformations freely and be blocked from a full reprocessing job during month-end close. Size, timing and record count are worth checking when the action is attempted, not once when the grant was configured.
Google Cloud's IAM conditions are a usable model for the shape, and for the limits. Conditions are
written in a subset of Common Expression Language, evaluated per request, and a role binding with a
condition grants the role only "if the condition expression evaluates to true". Supported attributes
include resource type and name, request attributes such as date and time and destination address, and
API-specific parameters. The documented scaling advice is the part to plan around: "As a best practice,
don't add more than 100 conditional role bindings to a single allow policy."
action: opportunity.apply_discount
when discount_pct <= 10 and request.time.getHours("UTC") < 22: execute_autonomously
when discount_pct <= 25: execute_with_checkpoint
when discount_pct > 25: denyA hundred conditions is a real ceiling, and we would design against it from the start. The dial cannot be a per-record tangle of expressions. It has to be a few tiers with a handful of thresholds each.
The Autonomy Spectrum
Put level and context together and one principle falls out: autonomy is not binary. Most enterprise agents sit somewhere in the middle, and the same agent lands at different points for different actions.
Figure 1 — Reversibility, not the tool or the data touched, is what should decide where an action sits on the dial.
The checkpoint itself has stopped being a UI convention. MCP's tools specification says that "for trust
& safety and security, there SHOULD always be a human in the loop with the ability to deny tool
invocations", and the current revision gives that a message shape. A server needing input mid-call
returns an InputRequiredResult carrying an elicitation/create request; the client collects the
answer and retries the call with inputResponses and the server's opaque requestState. Responses use
accept, decline and cancel, which separates a refusal from a dismissed dialog. Two details bite:
the JSON-RPC id "MUST be different between the initial request and the retry", and servers "MUST NOT
use form mode elicitation to request sensitive information such as passwords, API keys, access tokens,
or payment credentials". Those go through URL mode.
Figure 2 — A checkpoint as a protocol message rather than a UI habit. Decline and cancel are distinct outcomes, and only one of them means the person said no.
A permission model's job is keeping four things distinct that human-shaped systems lump together: a suggestion, a draft staged for review, a reversible execution, and an irreversible act. The further along that list, the tighter the reach layer around it should be too.
Putting It Into Practice
Start from the action, not the agent. Catalog what the agent can attempt, sort by reversibility rather than by tool, and set the dial per action. A recruiting agent reading a candidate pipeline and the same agent sending a rejection are different decisions inside one grant.
Enforce at the boundary, not in the prompt. The dial belongs where the agent meets the system, the
gateway or the tool adapter (aact:enforcement-point/gateway). A prompt telling an agent not to send
external email is aact:enforcement-point/in-prompt, which the taxonomy rates as no security control at
all; a denied call is a guarantee. This is the line Claude Code's documentation draws explicitly, and
it holds regardless of framework.
Seed the table from annotations, then own it. Import readOnlyHint and destructiveHint for a first
pass across a few hundred tools, then treat the result as your data. A hint is a claim by the server, not
a fact about the world.
Make the decision auditable. When an action runs, record which tier governed it and whether a person cleared it. Regulation expects that record to exist and survive: Article 12 of the EU AI Act requires high-risk systems to "technically allow for the automatic recording of events (logs) over the lifetime of the system", and Article 26(6) requires deployers to keep them for at least six months. Where a two-person rule applies, Article 14(5) names the identity of the verifiers as part of the record.
Where These Models Fail
| Symptom | What is actually happening | The fix |
|---|---|---|
| An agent deleted something it was "allowed" to delete | Tier set from the verb's idempotence rather than reversibility | Re-sort on the reversibility column; DELETE is idempotent and irreversible |
| Checkpoints are cleared in under a second, every time | Automation bias, the effect Article 14(4)(b) names | Cut the number of checkpoints so the remaining ones carry weight |
| A tool billed as read-only wrote | An annotation trusted as authorization | Annotations sort; your own tier table decides |
| A denied command ran anyway under a different spelling | Text pattern matching mistaken for a boundary | Enforce on the call the adapter makes, not the string the model wrote |
| The checkpoint prompt asked for an API key | Form-mode elicitation used for a secret, which the spec forbids | URL mode, out of band, never through the client |
| Nobody can say which tier governed last Tuesday's action | The tier was applied but not recorded | Log the tier and the clearing identity with the call |
Table 2 — Six ways a reasonable-looking model fails, and what each one actually is.
The fourth row is the most common self-inflicted version. Claude Code's documentation is unusually honest about the limit: a Bash rule "doesn't match the same program invoked in a different form, so a deny rule covers the invocation Claude usually produces and isn't a security boundary around the program". That holds for every model that gates on command text rather than call effect. Gate on the call.
Where Fabriq Fits
Agentic Fabriq sits at the boundary this argument keeps pointing at. Credentials live in a vault and are attached server-side at the moment of the call, so the agent never handles the secret. Every call carries two identities, the agent and the acting user, and an agent's effective tool list is the intersection of the user's scopes and the agent's, enforced in the backend rather than requested in a prompt. Calls land in an audit record.
What it does not do is decide the initiative dial for you. There is no checkpoint step in the product, so the tiering here is something you implement in your own call path. And clearing a vault copy of a credential is not revoking the grant upstream; that call is still yours to make at the provider.
Frequently Asked Questions
What is the difference between authorization and autonomy for an AI agent? Authorization answers whether an action is permitted. Autonomy answers whether the agent may take it without a person. OWASP separates them the same way, listing excessive permissions and excessive autonomy as distinct drivers of excessive agency.
What are the levels of agent autonomy? There is no ratified standard for agents. The four tiers worth using are suggest, draft, execute with a checkpoint, and execute autonomously, assigned per action.
Should an agent need approval for every action? No, and trying is counterproductive. The EU AI Act names the failure mode directly in Article 14(4)(b): oversight personnel must "remain aware of the possible tendency of automatically relying or over-relying on the output produced by a high-risk AI system (automation bias)". Fewer, better-chosen checkpoints get read.
What does readOnlyHint do in MCP? It declares that a tool does not modify its environment, and it
defaults to false. destructiveHint and openWorldHint both default to true, so an unannotated
tool reads as destructive and open-world. All of these are hints, and clients are told to treat them as
untrusted from untrusted servers.
How do I implement a human-in-the-loop checkpoint for an agent? On MCP, use the elicitation flow:
return an InputRequiredResult containing elicitation/create, collect accept, decline or cancel,
and retry under a new JSON-RPC id. Otherwise put the pause in the adapter that makes the outbound call,
never in the prompt.
Is RBAC enough for AI agents? For reach, roles plus scopes get you most of the way. For initiative they say nothing, because a role is a static grant. Conditions move you toward the ABAC model of SP 800-162, with the request-time attributes a per-action dial needs.
Are idempotent actions safe to run autonomously? Not by themselves. Idempotence means a retry does no extra harm, which is why RFC 9110 permits automatic repetition of idempotent requests. It says nothing about whether the first call can be undone.
Does the EU AI Act require a person to approve agent actions? Not in general. Article 14 requires that oversight be possible, including the ability to "decide, in any particular situation, not to use the high-risk AI system or to otherwise disregard, override or reverse the output" and to "interrupt the system through a 'stop' button or a similar procedure that allows the system to come to a halt in a safe state". A blanket two-person rule appears only for specific cases such as remote biometric identification under Article 14(5).
Conclusion
Autonomous systems need permissions with enough resolution to tell a suggestion from an irreversible act, and a model that sets initiative independently from reach for each thing an agent might do.
The goal is not powerless agents. We think it is matching initiative to what an action costs when it goes wrong: sorting on reversibility rather than on which system is touched, seeding that sort from the hints the protocol already carries without trusting them, and enforcing the result where the call leaves your infrastructure. An agent that drafts freely, executes the reversible on its own and stops for the irreversible is both useful and safe.
Most permission models answer whether an action is allowed. The ones that hold up under real autonomy also answer whether it should happen without anyone watching, per action, in a record you can read back six months later.
Sources
All URLs read 2026-09-29.
- MCP specification, Tools (2026-07-28) — the human-in-the-loop guidance, the
annotationsfield, and the warning that clients MUST treat annotations as untrusted from untrusted servers. - MCP schema,
ToolAnnotations— the exact field definitions and defaults forreadOnlyHint,destructiveHint,idempotentHintandopenWorldHint, and the note that all of them are hints. - MCP specification, Elicitation (2026-07-28) —
InputRequiredResultwithelicitation/create, the accept/decline/cancel model, the different-id requirement on the retry, and the ban on form-mode requests for credentials. - RFC 9110: HTTP Semantics — sections 9.2.1 and 9.2.2 on safe and idempotent methods, and the conditional permission to retry automatically.
- NIST SP 800-162, Guide to Attribute Based Access Control — the ABAC definition including environment conditions, published January 2014 and updated August 2019.
- IAM Conditions overview, Google Cloud — CEL-based conditions, the attribute categories available at request time, and the 100-conditional-binding guidance.
- OWASP LLM06:2025 Excessive Agency — excessive functionality, permissions and autonomy as separate drivers, and human-in-the-loop approval for high-impact actions as a mitigation.
- Configure permissions, Claude Code documentation — allow, ask and deny semantics, the fixed deny-then-ask-then-allow ordering, permission modes, the statement that rules are enforced outside the model, and the honest limit of pattern-matched command rules.
- EU AI Act Article 14, Human Oversight — automation bias under 14(4)(b), override and interrupt duties under 14(4)(d) and (e), and the two-person verification rule in 14(5).
- EU AI Act Article 12, Record-Keeping — automatic recording of events over the system's lifetime.
- EU AI Act Article 26, Obligations of Deployers — the six-month minimum retention for automatically generated logs.
- Agent Access Control Taxonomy — the human oversight and enforcement point dimensions, and the questions they ask: when a person gets involved, and where the decision is actually enforced.
- Agent access control taxonomy, enforcement-point.yaml —
aact:enforcement-point/in-promptas restrictions "expressed as instructions to the model, with no enforcement outside it", and its note that this is "not a security control".