Permission Policies diagramTask, Agent, Policy, Developer, Shell, Result. Step captions follow the diagram.TaskAgentPolicyspent $1.40 of $2DeveloperShellResultPermission Policies diagramTask, Agent, Policy, Developer, Shell, Result. Step captions follow the diagram.TaskAgentPolicyspent $1.40of $2DeveloperShellResult

All steps

Allow, ask and deny

Budget cap reached

Parts of this pattern
Task Input / output
The request that starts the run, such as 'Fix the failing date-parsing test in this repo'.
Agent Agent
The coding agent. It proposes tool calls one at a time and reads each result, denial or refusal before its next move.
Policy Guard
Plain code: denies undeclared tools, matches the rest in the order deny, ask, allow, and keeps the run's budget meter, here at $1.40 of $2.
Developer Human
The person asked about calls in the ask lane only, such as installing a new package. Everything else is decided without them.
Shell Tool
The tool that actually runs commands. It only ever receives calls the policy allowed or the developer approved.
Result Input / output
The agent's final answer, or a stop report naming the cap that ended the run.
Task → Agent
The task goes to the agent, which starts proposing tool calls.Fix the failing test in tests/date.test.ts without changing its assertions.
Agent → Policycall
Every tool call the agent proposes goes to the policy first; nothing reaches the shell directly.{"tool": "shell", "command": "npm install date-fns@4"}
Policy → Shellallow
A call that matches an allow rule, and no deny or ask rule, runs at once with no person involved.git status matched allow rule shell(git status); spent $0.02 of $2.00.
Policy → Developerask
A declared tool whose call matches an ask rule, or no rule, waits for the developer. No answer before the timeout counts as a deny.npm install date-fns@4 matched ask rule shell(npm install *). Agent's reason: the parser needs date-fns.
Developer → Shellapprove
An approved call goes on to the shell exactly as proposed.approve: npm install date-fns@4
Policy → Agentdeny
A denied call never runs. The agent gets an error naming the rule, so it can choose another approach.Error: shell(rm -rf /) denied by rule shell(rm *). Choose a different approach.
Shell → Agentresult
The command's output goes back to the agent, which decides its next call.On branch fix/date-parse. Changes not staged: src/date.ts
Agent → Result
When the agent is finished, its answer leaves the loop.Fixed: parseDate now handles ISO week dates. tests/date.test.ts passes (14/14).
Policy → Resultcap hit
When the next call would pass the spend or step cap, the policy ends the run instead of running it.Stopped: spend cap reached ($2.00 of $2.00) after 31 calls. Partial work is on branch fix/date-parse.

← → neighboursesc atlas

Safety & Oversight

Permission Policies

Also known as Tool allowlists · Allow/ask/deny rules · Budget caps · Least privilege · Policy engine

Every tool call is checked against declarative allow, ask and deny rules and a budget before it runs.

No single inventor · see source

OpenAI's 2025 agent guide rates each tool's risk to decide when to escalate, and Claude Code ships tiered allow, ask and deny rules. The idea itself is classic least privilege, with no single inventor for agents.

Measured over one run

Nodes
6. Boxes in the diagram: models, tools, stores, people and plain code.
LLM calls
5. Model invocations in the first scenario: each time a model or agent node starts thinking, plus each branch it decides.
Tool calls
2. Messages sent to a tool node in the first scenario.
Steps
5. Steps in the first scenario, with repeated steps counted every time they run.
Scenarios
2. Distinct example runs you can switch between on the diagram.
Loop
yes. Whether the flow cycles back on itself until a condition is met.
Parallel
no. Whether two or more messages are in flight at the same time.
Branch
yes. Whether the flow takes one path out of several, chosen at run time.
Human in the loop
yes. Whether a person appears as a node, to approve, correct or answer.
Autonomy
medium. How much the model decides the control flow itself, from fixed code paths (low) to open-ended (high).
Cost
low. Relative token spend per request.
Latency
low. Relative wall-clock time per request.

Counted from the first scenario's timeline when the site is built.

Problem

An agent that acts on real systems will sooner or later propose something it should not do. A coding agent fixing a failing test might sensibly run git status, reasonably want to npm install a new package, and, after reading a hostile comment in the code, try rm -rf /. Asking a person to approve every call makes the agent too slow to be worth using, and approving none of them trusts the model with everything it can reach. A sentence in the system prompt saying “never delete files” is a request, not a limit.

How it works

Put a policy between the agent and its tools. The policy is plain code reading declarative rules, so the model cannot argue with it. Every proposed call is matched against three lists, checked in a fixed order: deny first, then ask, then allow. The order matters, because it means a broad allow rule can never override a specific deny.

In the example, git status matches an allow rule and goes straight to the shell; nobody is bothered. npm install date-fns@4 matches an ask rule, so the policy sends it to the developer with the agent’s reason, and it runs only after they approve. rm -rf / matches a deny rule. It never runs, and the agent gets an error naming the rule, so it can pick another approach instead of retrying the same one. The defaults fail closed. A tool the policy does not declare at all is denied outright. A declared tool whose arguments match no rule goes to ask, and an ask that nobody answers before its timeout, or that comes up in an unattended run with no person attached, is treated as a deny.

The policy also keeps a budget meter for the whole run, covering the agent’s own model calls as well as the tools. Each call adds its cost and counts as a step, and so does each denial. When the next call would pass the cap, here $2, the policy ends the run and sends a stop report to the result instead of running it. Every decision goes into an audit log: the call, the rule it matched, the verdict and who approved it.

When to use

  • The agent acts on real systems: files, repositories, cloud accounts, money, messages to people.
  • You want predictable, reviewable limits without a person approving every call.
  • Different calls carry very different risk, so one blanket rule would be either too loose or too strict.

When to avoid

  • The tools are pure read-only lookups. The overhead buys nothing.
  • Every action is high-stakes. Then you need a person on every call, which is plain human-in-the-loop.
  • You cannot describe the risky calls as rules over tool names and arguments. A rule list that is mostly exceptions will be wrong in both directions.

Trade-offs

A policy makes autonomy auditable, but only as good as its rules. Rules written too tight bury the developer in ask prompts, and people who are asked too often start approving without reading. Rules written too loose let through the call nobody thought of. Matching on arguments is harder than it looks: a shell command can hide a second command after a semicolon, so match on parsed calls, not raw text. The policy itself adds almost no latency; the ask lane adds as much as the person takes to answer.

Failure modes

  • Rule bypass: the agent reaches a denied effect another way, such as writing a script that does what the denied command would. Deny by effect where you can, and pair the policy with a sandbox.
  • Approval fatigue: the ask lane fires so often that the developer approves everything. Move safe, frequent calls to allow, and keep ask for the few that matter.
  • Retry storms: the agent keeps rephrasing a denied call. Tell it, in the prompt and in the error, not to retry denied calls, and count denials toward the step cap.
  • Silent defaults: a new tool with no rule is allowed by mistake, or an ask with no one to answer hangs the run or gets wired to auto-approve. Deny undeclared tools, and let unanswered asks time out as denials.

Build it with your AI

Paste this into Claude Code, Cursor, ChatGPT or any AI coding assistant.

Implement the Permission Policies pattern in this codebase, using the LLM client and language we already use.

## Goal

Put a policy check between an agent and its tools, so every proposed call is allowed, escalated to a person, or denied by declarative rules, and the run stops when a spend or step budget is used up.

## How it works

1. Load the policy from a config file: ordered deny, ask and allow rules, each a tool name plus an optional argument pattern, and a budget with a spend cap and a step cap.
2. Run the agent with the Agent prompt with a permission policy, which tells it that some calls may be denied or need approval and how to react.
3. When the agent proposes a tool call, deny it at once if its tool is not declared in the policy. Otherwise match it against the rules in the order deny, then ask, then allow; a declared tool whose arguments match no rule goes to ask.
4. On allow, run the tool and return its result to the agent. On deny, do not run it; return an error naming the rule that matched, and count the denial as a step toward the step cap.
5. On ask, send the person a message built from the Approval request message and wait up to a timeout (default 10 minutes). Run the tool only if they approve. A rejection, a timeout, or a run with no person attached is a denial, reported to the agent like any other.
6. Keep one budget meter for the run: add the agent's own model-call spend after each turn and each tool call's estimated cost before it runs. If the next call would pass the spend or step cap, stop the run instead of calling the tool.
7. Record every decision (the call, the matched rule, the verdict, who approved it and the running spend) in an audit log.

## Prompts

### Agent prompt with a permission policy

```markdown
You are a coding agent working in {{repo}}. Your task:
<task>{{task}}</task>

Every tool call you make is checked against a permission policy before it runs.
- Some calls run at once. Some need a person's approval, so say why you need them;
  if nobody answers in time, the call is denied.
- Only the tools listed for you exist. Calls to any other tool are denied.
- A denied call returns an error naming the rule. Do not retry it or try to
  reach the same effect another way; find a different approach or explain
  why the task cannot be finished.
- The run has a budget of {{budget}}. Prefer cheap, read-only calls first.

Before each call that changes anything, write one line saying what it will
change and why it is needed.
```

### Approval request message

```markdown
**The agent wants to run a call that your policy marks as "ask".**

Tool: `{{tool_name}}`
Arguments: `{{arguments}}`
Matched rule: `{{rule}}`
Agent's reason: {{rationale}}
Spent so far: {{spent}} of {{budget}}

Reply approve or reject within {{timeout}}. A rejection is passed to the agent
with your reason; no reply in time counts as a rejection.
```

## Stop rule

Stop when the agent returns a final answer, or when the spend or step cap is reached (denials count as steps). On a cap, return the partial work with a status naming the cap. An unanswered ask never blocks the run: it times out as a denial.

## Requirements

- Keep the policy in data (a file or a table), not in prompt text, and never let the agent change it.
- Evaluate deny before ask before allow, so a broad allow rule can never override a specific deny.
- Fail closed: deny any tool not declared in the policy, send a declared tool with unmatched arguments to ask, and turn an ask that times out or has no person to answer into a deny. Never fall through to allow.
- Match rules on the parsed tool name and arguments, not on the raw text, so that 'git status; rm -rf ~' does not match an allow rule for 'git status'.
- Test with no approver attached and rules allow 'git status', deny 'rm *', npm declared with no rule: a stub LLM proposes 'git status', 'rm -rf /', 'npm install x' and the unlisted http_post. Assert the shell ran once, three denials came back, and 4 steps were counted.

Prompt templates

Agent prompt with a permission policy
You are a coding agent working in {{repo}}. Your task:
<task>{{task}}</task>
Every tool call you make is checked against a permission policy before it runs.
- Some calls run at once. Some need a person's approval, so say why you need them;
if nobody answers in time, the call is denied.
- Only the tools listed for you exist. Calls to any other tool are denied.
- A denied call returns an error naming the rule. Do not retry it or try to
reach the same effect another way; find a different approach or explain
why the task cannot be finished.
- The run has a budget of {{budget}}. Prefer cheap, read-only calls first.
Before each call that changes anything, write one line saying what it will
change and why it is needed.
Approval request message
**The agent wants to run a call that your policy marks as "ask".**
Tool: `{{tool_name}}`
Arguments: `{{arguments}}`
Matched rule: `{{rule}}`
Agent's reason: {{rationale}}
Spent so far: {{spent}} of {{budget}}
Reply approve or reject within {{timeout}}. A rejection is passed to the agent
with your reason; no reply in time counts as a rejection.

Related patterns

Composes well with:

Sources

  1. Configure permissions (Claude Code docs, Anthropic)
  2. A practical guide to building agents (OpenAI, 2025)

From the Agentic Atlas by Joran Kikke · Edit on GitHub · Suggest a correction