Skip to content

Approval gates

An agent that can write files, run shell commands, and reach the internet needs a place where you say yes. In Atomic Agent that place is the approval gate: before a dangerous tool runs, the runtime classifies what it is about to do and either lets it through or stops and asks you.

How much it asks is one number. agent.approvalLevel runs from 1 (ask about everything) to 5 (ask about nothing), and each step up hands the agent a broader class of actions to take on its own.

The ladder

The default is level 1. Every gated action prompts you before it happens. In the TUI you can also shift the stance for one session with the coding mode control.

LevelNameRuns without asking
1paranoidNothing. Every gated action prompts.
2workspaceFile writes, edits, and patches strictly inside the session working directory.
3homeAdds file writes anywhere under your home directory, moves to Trash, archive extraction, and HTTP requests (only if http.approvalMode asks for them, see below).
4operatorAdds guarded shell commands, skill scripts, process kills, network git (push, pull, fetch, clone, remote), GitHub publishing (PRs, issues, comments), and Fusion fan-outs.
5full trustEverything, including browser navigation to non-web URLs, writes to the agent’s own trust config, sending e-mail, reads outside the working directory, and anything the runtime could not categorise.

A short allowlist of harmless shell probes (pwd, whoami, date, which <cmd>, node --version-style version probes for common runtimes, a literal echo) and a few read-only CLI rules run without a prompt even at level 1.

The ladder is cumulative by construction. The gate asks level >= threshold, so a category that is silent at level 3 is silent at 4 and 5 too — there is no level where something starts asking again.

// <stateDir>/config.json
{
"agent": {
"approvalLevel": 2
}
}

Categories

Every call site that asks for approval names one of fifteen categories. The category decides both the level at which it goes quiet and the label you see in the prompt.

CategoryPrompt labelAuto-approves from
fs_write_workspacefile write · workspace2
fs_write_homefile write · home3
fs_trashmove to Trash3
httpHTTP request3
shellshell command4
scriptskill script4
proc_killprocess kill4
publishpublish · GitHub4
git_remotegit · remote4
fusion_fanoutfusion · fan-out4
browser_nonwebbrowser · non-web URL5
trust_configagent trust config5
emaile-mail send5
fs_read_outsideread outside the working directory5
otheruncategorised5

other is the deliberate fallback for anything a call site cannot place precisely. Because it only goes silent at 5, an uncategorised action keeps asking on every level below full trust — the conservative default.

http only comes into play when http.approvalMode asks for it. The default is "never": os.http.request runs without a prompt at every level. Set "writes" to ask before POST requests, or "always" to ask before every request; the http category then goes quiet from level 3. os.web.fetch and os.web.search are read tools and are never gated.

Reads outside the working directory

By default (agent.readScope: "working-dir") the agent reads freely inside the session working directory and inside paths you named in the conversation. A read, or a shell command that names a path, anywhere else stops and asks under fs_read_outside. Answering y opens that directory (a file’s parent folder) for the rest of the session; s allows reads anywhere for the session. Level 5 never asks. Set agent.readScope: "unrestricted" to turn the scope off.

The prompt has four answers

When the gate stops, the CLI prompt on stderr is not a yes/no. It takes four:

AnswerEffect
yApprove this one call.
sApprove, and allow this whole category for the rest of the session.
aApprove, and allow this shell command shape for the rest of the session.
NDeny.

Only y and yes count as approval. Anything else — a typo, an empty line, maybe — is a refusal. Denial is the failure mode, which is what you want from a gate.

» approval required for tool: os.fs.write
kind: file write · home
reason: write 2.1 KB to ~/notes/plan.md
affects: /Users/you/notes/plan.md
approve? [y = approve once, s = allow this kind this session, N = deny]

s and a appear only when the request is grantable. a needs a shell command shape to generalise from, so it shows up on shell requests and not on file writes. It is also not offered for opaque interpreter calls such as bash -c, where the binary name hides what runs. And trust_config, email and fusion_fanout are never grantable: those requests only offer y/N, so each e-mail and each Fusion fan-out is its own decision. The prompt says so:

(trust-config writes are never granted for the session)

(The CLI currently prints this trust-config line for every non-grantable request, including e-mail and fan-out.) For trust_config this is the grant-side half of the same invariant as the level pin. A broad category grant earlier in the session must not later cover a write to the file that holds the ladder.

Skipping approvals for one run

Every interactive entry point (run, tui, serve) accepts --no-approval, which forces level 5 for that process.

Terminal window
atomic-agent run --no-approval

The persisted agent.approvalLevel is the baseline and the flag overrides it for the process. run, tui, and serve all resolve the boot level the same way, so the level you set in config applies consistently across entry points.

Changing the stance from the TUI

The TUI has one approval control: the coding mode chip in the composer. Open it with ctrl+g M, click the chip, or type /mode <name>.

ModeEffect
defaultApprovals follow agent.approvalLevel from config.json.
planRead-only: the agent reads and proposes, and every tool that would change something is refused.
autoRaises the level to at least 2 (never lowers it): file writes inside the working directory stop asking, everything else still asks.
bypass permissionsLevel 5 for the rest of this session. Hardline shell-guard rules still block.

The mode applies to the current session only and is never written to config.json. Switching back to default restores the level you configured. To change the persisted baseline, run atomic-agent config set agent.approvalLevel 3 (or edit config.json).

Approval-gated calls are not batched

Atomic Agent lets the model emit several tool calls in one step, executed by resource class. Approval-gated tools are normally excluded from that: they carry the virtual approval_gated class and must run one per step.

If the model emits one anyway, the runtime does not fail the step. It trims the batch to the first approval-gated call, executes that one, drops the rest, and tells the model what happened in the next step’s prompt so it can re-emit the dropped calls one at a time.

When nothing in the batch could ask (every call is auto-approved at the current level or covered by an s grant), the batch is not trimmed and the gated calls run one after another in the order the model emitted them. In practice file writes only qualify at level 5 (for example under --no-approval), because a write could land on the agent’s own config. fusion.delegate always runs alone.

These are the approval-gated tools:

ToolWhat it does
os.fs.writeWrite a file
os.fs.editEdit a file in place
os.fs.patchApply a patch
os.fs.restorePut back a file’s previous content
os.fs.trashMove to Trash
os.fs.archive.extractExtract an archive
os.shell.runRun a shell command
os.proc.killKill a process
os.http.requestIssue an HTTP request
skill.run_scriptRun a skill script
os.git.checkout, os.git.commit, os.git.init, os.git.addLocal git writes
os.git.remote, os.git.fetch, os.git.pull, os.git.push, os.git.cloneNetwork git
github.pr.create, github.issue.create, github.issue.commentPublish on GitHub
os.email.sendSend an e-mail
fusion.delegateFan work out to Fusion workers
verify.runRun a check in a throwaway copy of the working directory

Tools from MCP servers at the default trust level ask as other, unless the tool advertises readOnlyHint.

Read-only counterparts (os.fs.read, os.fs.grep, os.web.fetch, os.web.search, os.git.status|log|diff|show|blame|branch, the memory.* recalls) are pure_read and batch freely.

The legacy key

Before config v37 the setting was a boolean, agent.approvalRequired. It is migrated automatically:

Legacy valueBecomes
approvalRequired: trueapprovalLevel: 1
approvalRequired: falseapprovalLevel: 5

The migration runs when the config file is read, and the legacy key is removed from disk afterwards. If a file somehow carries both, agent.approvalLevel wins — an explicit level is never overridden by a stale boolean.

Choosing a level

Level 1 — anything untrusted

The default. Use it when you are exploring, running a new skill, or pointing the agent at code you did not write.

Level 2 — focused project work

The agent edits freely inside the working directory but still asks before touching anything outside it or running shell.

Level 3 — research and fetching

Adds home-directory writes, Trash and archive extraction (and HTTP, if http.approvalMode asks). Good for tasks that read the web and take notes; shell still stops for you.

Level 4-5 — trusted automation

Shell and skill scripts run unattended. Only for workloads and inputs you fully control, ideally in a sandbox.