---
title: "Permission"
description: "Before any tool runs, it passes a rule gate: allow runs it, deny blocks it, ask escalates to you. Set boundaries first, then grant freedom."
source: https://vizlearn.app/ai/agents/permission
access: premium (interactive player requires Lifetime; this doc is free)
---

# Permission

Permission is a check before execution in an agent's harness, the program that runs its tools. A policy can allow the call, deny it, or ask for human approval. Here, deny takes precedence over ask and allow. Asking starts a wait; only an affirmative reply lets a call on the ask path reach the tool.

An agent needs to read project notes and update a file. You want ordinary reads to proceed, a protected file to stay unread, and writes to wait for review. The permission check is the checkpoint before each action. Checking after a read would be too late: the contents have already been accessed.

## How Permission Stops a Call Before Execution

The model proposes a tool name and arguments. The harness receives that request and checks its rules before calling the tool. Here, each rule contains an effect, a tool name, and a target. A target matches either one exact filename or `*`, which covers every target for that tool.

The dispatcher first collects matching effects. It then looks for `deny`, followed by `ask`, followed by `allow`. This priority is independent of the order in which rules appear on screen. If a broad allow and a narrower deny both match, deny wins because of its effect, not because narrower patterns automatically win. With no matching rule, this particular harness defaults to deny.

Each decision takes a different path:

- **deny:** return a blocked status before calling the tool. A human reply is not requested.
- **ask:** request a response for this exact call. An affirmative reply lets dispatch continue. A refusal returns blocked. No reply leaves the call waiting.
- **allow:** continue to the tool invocation without an approval prompt.

Those are policy decisions. The outcomes are different words: **executed**, **blocked**, and **waiting**. An allow decision authorizes an invocation; it does not prove that the invocation has happened or that the tool will succeed.

The Python dispatcher makes the execution boundary explicit. Every blocking path returns above `run_tool(call)`. The approval adapter returns `True`, `False`, or `None`; the last value represents an unanswered request. A production asynchronous adapter would retain that pending call and resume it when a response arrives. It would not send a fabricated tool error to the model while waiting.

## Permission Step by Step

The default sets the write rule to `ask` and the simulated approval reply to `approve`. The in-memory file `notes.txt` initially contains `draft`. All three requests and the initial world are the same each time you replay the lesson.

| Rule | Effect | Tool | Target |
|---|---|---|---|
| r1 | allow | Read | `*` |
| r2 | deny | Read | `secrets.txt` |
| r3 | ask | Write | `notes.txt` |

**1. Read the ordinary file.** Request c1 is `Read("readme.txt")`. Only r1 matches. The dispatcher chooses allow, passes its deny and ask tests, and invokes the read. The tool returns `Project notes`. There has been one tool execution and no approval prompt. Reading does not change `notes.txt`.

**2. Read the protected fixture.** Request c2 is `Read("secrets.txt")`. Both r1 and r2 match: the wildcard covers the target, and the explicit deny names it. The priority search chooses deny. The dispatcher returns `blocked` with the reason `Denied by policy`; it never invokes Read. This call has zero tool runs; the total remains one from c1. The harness can report that refusal to its caller without manufacturing a tool result.

**3. Request a write.** Request c3 is `Write("notes.txt", "reviewed")`. Rule r3 selects ask. The approval card names c3 and `notes.txt`, while the request stays before the execution boundary. There is now one prompt, still one tool execution, and the file still contains `draft`.

**4. Receive approval, then execute.** The simulated reply approves c3. At the response beat the file remains unchanged: the dispatcher still has to reach its invocation line. The next beat calls Write, replaces `draft` with `reviewed`, and raises the execution count to two. The dispatcher then returns the tool result.

The final ledger keeps each decision beside its outcome: c1 follows `allow → executed`, c2 follows `deny → blocked`, and c3 follows `ask → approve → executed`. There are three requests, two tool runs, and one blocked call. The header counts runs across all calls; while a request is active, the world panel counts only that call's invocations.

## Time and Space Complexity

For R rules, matching takes O(R) time under the lesson's fixed-size tool and target strings. Resolving the three possible effects takes O(1) time. The set of distinct effects has at most three elements, so the displayed dispatcher's auxiliary space is O(1), excluding its input rules and the tool's own work.

The teaching trace additionally retains matching rule IDs and snapshots so you can inspect earlier steps. That history is a visualization cost, not a requirement of permission dispatch. Human response latency is also separate from computational complexity: a quick policy check can leave a request waiting indefinitely.

## Edge Cases

**An unanswered prompt.** Keep the write rule at ask and choose Unanswered. The write stays waiting, the file stays `draft`, and the whole run contains only the earlier read. Playback reaches its last frame, but the pending request has not become authorized. Waiting is not the same outcome as a refusal.

**A refused write.** Choose ask and Decline. The dispatcher receives the negative response and returns blocked. The final ledger has two blocked calls: the protected read and the declined write. The single executed call is the ordinary read.

**More autonomy than intended.** Choose allow and Decline. No write approval is requested, so the hypothetical reply is not consulted. Write executes and the file becomes `reviewed`. If you want a person to decide, the policy must actually take the ask path. Selecting a reply does not create a prompt.

**A deny beside an affirmative reply.** Choose deny and Approve. The dispatcher returns before it calls the approval adapter, so that reply cannot override the rule. The file stays `draft`.

**No matching rule.** This teaching dispatcher denies unmatched requests. Agent products can use other defaults or modes; read the contract of the system you configure. Matching here is exact filename or `*`, so `notes.txt` does not match `notes.txt.bak`.

## Common Mistakes

- **Running first and checking later.** A blocked return cannot undo a read that already exposed data. The invocation must be below the permission guards.
- **Letting the first displayed row win.** Rule display order and effect priority are separate. Checking allow before deny would let r1 authorize the protected fixture.
- **Treating a prompt as a grant.** The prompt starts a decision; it is not its answer. In this dispatcher only an affirmative reply lets an ask-controlled call proceed.
- **Counting requests as executions.** All three requests reach the harness. Only two reach a tool in the default run.
- **Confusing authorization with successful execution.** Permission decides whether an attempt may proceed. A permitted tool can still fail. [Tool Calling](https://vizlearn.app/ai/agents/tool-calling) covers how tool errors return to the surrounding loop.

## A Note on Simplification

This is an explainer of a deterministic permission dispatcher, not a production security implementation or a substitute for formal security training. Requests are authored fixtures and files live entirely in browser memory; there is no model recording, real credential, filesystem write, or external action. The dispatcher computes the matches, decisions, and effects for every selected input.

Production systems also need path canonicalization, permission modes, hooks, remembered or scoped grants, cancellation, and checks on every route that can reach a resource. Denying one Read tool does not by itself prevent a different tool from opening the same file. Application permission checks and OS sandbox enforcement address different boundaries. Those mechanisms are omitted here so the early returns and the single invocation point remain inspectable.

## References and Next Steps

[Claude Code's permission documentation](https://code.claude.com/docs/en/permissions) describes deny/ask/allow rules and their precedence. Its full policy engine includes modes and hooks beyond this example. [The Agent SDK approval guide](https://code.claude.com/docs/en/agent-sdk/user-input) describes handling requests for human approval; this lesson's synchronous response values are a smaller teaching interface.

Return to [Agent Loop](https://vizlearn.app/ai/agents/agent-loop) to place the check between the model's requested action and the harness's tool invocation. [Task System](https://vizlearn.app/ai/agents/task-system) addresses a different harness responsibility: preserving completed work across a process restart.
