# Permissions and Confirmations

The two permission modes, which entry point uses which, and how confirmation prompts are routed.

## Permission mode

| Mode | Behavior |
|---|---|
| `safe` | Each non-ReadOnly tool call requires user confirmation |
| `auto` | Every tool runs without confirmation, except writes outside the working directory and irreversible commands (below); only takes effect while the working directory qualifies, otherwise the `safe` rules apply |

The working directory qualifies for `auto` when it is a subdirectory of `$HOME`, not `$HOME` itself, and not a denied or sensitive path (`filesystem.WorkDirAllowsAll`). The gate is enforced by the harness in `internal/agents/exec/toolCall.go`, not by the prompt. Under `auto` a call still asks when:

1. Outside the working directory — an `edit_file`, `remove_file` or `restore_file` path, a `run_command` / `background_command` argv token that is absolute, `~` or `..`, a `write_paths` entry, or an `rm` operand that lands inside `$HOME` but outside the working directory asks once; after approval that path is granted for the rest of the session
2. Irreversible command (`internal/agents/exec/irreversible.go`) — asks even when an allow rule matches; only "Yes, allow this turn" skips it for the rest of the turn. Covered: Git history loss (`reset --hard`, `clean`, force push, remote branch deletion); a download piped into an interpreter, or a script that reaches the network and reads a secret; uploading a sensitive file; infrastructure apply / delete (`terraform`, `tofu`, `pulumi`, `cdk`, `kubectl`, `helm`); cloud CLI delete / deploy / IAM changes (`gcloud`, `az`, `doctl`, `aws`, `gsutil`); deploy CLIs (`firebase`, `vercel`, `netlify`, `wrangler`, `fly`, `serverless`); database migrations; destructive SQL (`DROP` / `TRUNCATE`, `DELETE` / `UPDATE` without `WHERE`); `gh pr merge`, `gh repo delete` and similar, and mutating `gh api` calls

Path tiers apply in every mode: a sensitive path inside `$HOME` needs a plain confirmation (no password); a path outside `$HOME`, an argv starting with `sudo`, and `sandbox: false` need the system password. Password prompts drop "Yes, don't ask again" and "Yes, allow this turn"; plain confirmations keep them. Before v1.3.7 a path outside `$HOME` under `auto` was rejected without a prompt; it now asks for the password. Since v1.3.10 `read_files` and `find_files` read a path outside `$HOME` without a prompt when it is on the readable list (system and package locations such as `/usr`, `/etc`, `/opt`, plus `readable_path` in `config.json`).

`rm` in an argv moves its targets to the trash: inside the working directory it runs under `auto`, elsewhere in `$HOME` it asks, outside `$HOME` it needs the password; `rm` inside `sh -c` is refused. `network: true` on `run_command` / `background_command` confirms under `safe` and runs under `auto`.

## Mode by entry point

The active permission mode (`safe` vs `auto`) is decided by entry point:

| Entry | Mode |
|---|---|
| TUI | `auto` when the starting working directory qualifies, otherwise `safe`; toggled per session with `Shift+Tab` |
| `POST /v1/send` | `safe` while the web dashboard has a confirmation listener open and the request sets neither `enable_edit` nor `enable_sandbox`; confirms are answered there. Otherwise (v1.3.9) API auto mode: no prompts, read-only unless `enable_edit:true`, sandboxed unless `enable_sandbox:false`, see [REST API](rest-api). `agen -p` always uses API auto mode. The `allow_all` request field was removed |
| `POST /v1/chat/completions` | `auto`; the work directory defaults to `$HOME`, which does not qualify, so the `safe` rules apply unless the request names a qualifying work directory |
| Telegram | `safe` (confirm gate uses a Telegram inline-keyboard select) |
| Discord | `safe` (confirm gate uses a Discord select menu) |
| Resumed pending task | Keeps the mode stored with the task |
| Subagent | Inherits the parent's mode; under `auto` it is not limited to the working directory |

The permission mode is not rendered into the turn context or the system prompt: the `## Permission Mode` section and `configs/prompts/system_prompt/inject/permission/*.md` were Removed in v1.3.7, and the gate above is applied by the harness alone. Commands on the read-only list skip the gate in every mode.

## Auto-approval rules

These rules decide which calls ask under `safe` (and under `auto` when the working directory does not qualify). A tool that carries a `mode` is gated by it: `list` / `read` / `search` are treated as read-only and skip confirmation, while `mode=remove` (`edit_skill` / `edit_tool`) always confirms even on an otherwise auto-approved tool. The same holds for `remove_file` and `restore_file`, which since v1.3.11 replace `edit_file`'s `remove` / `restore` modes (Removed in v1.3.11): an allow rule never skips them.

Beyond that, a call skips the prompt when its tool is registered `AlwaysAllow`, an `http_request` uses `GET`, or a `run_command` / `background_command` argv matches the read-only list (an argv token that touches a sensitive path stops the match); `open_file` on a URL and `read_files` on a sensitive file always ask, and so does a command call with `sandbox: false` (v1.3.0) or an argv starting with `sudo` (v1.3.2), which needs the system password even under `auto`, an allow-all Skill or a matching allow rule. Every other call is checked against the allow list (`internal/agents/exec/allow`):

| Item | Detail |
|---|---|
| Files | `~/.config/agenvoy/allow_tool` (global) and `<workDir>/.agenvoy/allow_list` (project), one entry per line, `#` starts a comment; both are loaded and unioned |
| Entry | `name` approves every call of that tool; `name(argGlob)` approves only calls whose subject matches. The subject is the space-joined argv for `run_command` and `background_command` (`run_command_background` was renamed, Removed in v1.3.2), the absolute `path` for `edit_file`, `METHOD URL` for `http_request` (method defaulting to `GET`), otherwise the `path` (made absolute against the work directory), `url` or `target` argument, else the raw JSON arguments. Since v1.3.0 relative paths are resolved before matching, and an empty `argv`, `edit_file` path or `http_request` URL never matches a rule and cannot be remembered |
| Glob | Used for both the name and `argGlob`, anchored to the whole string: `*` matches within one `/` segment, `**` crosses `/` (since v1.3.0 `**/` also matches zero directories, so `src/**/*.go` covers `src/main.go`), `?` matches one non-`/` character, and `\*`, `\?`, `\\` are literal |
| Remember | Approving a prompt with remember writes `name(<subject>)` to the project `allow_list`, escaping `*`, `?` and `\` in the subject so the entry matches only that exact call |
| MCP | `/mcp` → server → tools, or `POST /v1/allowlist` with `{tool: {prefix, entries}}`, replaces every global entry under `mcp__<server>__` with the given set; each entry must start with that prefix, and including `prefix*` collapses the set to a whole-server grant |
| Skills | `~/.config/agenvoy/allow_skill` and `<workDir>/.agenvoy/allow_skill` list Skill names; a run whose active Skill is listed executes as allow-all |

## Origin-routed confirmations

Every interactive request carries an origin prefix — `cli-`, `chat-`, `tg-`, or `dc-`. CLI confirmations are consumed only by the TUI — since v1.2.0 they travel over the daemon socket to the TUI window that started the run, not to every open TUI — web requests by the web confirmation stream, and Telegram or Discord requests by their matching channel listeners. A confirmation from a channel other than the TUI that is left unanswered for five minutes is skipped and the task is kept as pending (since v1.1.1 TUI confirmations wait without a time limit), so one channel can neither intercept nor indefinitely hold another channel's prompt.
