Every tool declares a risk tier: read_only, side_effecting, or destructive. Tools at or below the auto-approve threshold (runtime.auto_approve, default read_only) run without asking. Everything else stops the turn and asks you, with the real artifact of the decision on screen.
Some calls ask whatever the threshold says: a send or a skill save (see below), a shell command, Shortcut, or MCP call while the air-gap is on, the first call that can act after flagged content, and a held URL (see the trust stack). One call can pass the other way: a remember whose words you typed yourself is saved without asking (see knowledge).
┠ approve? y / N / s / a / e (Enter = no) »| key | does |
|---|---|
y | approve the batch |
N / Enter | reject. This is the default, and anything unrecognized also rejects (with a note saying so). |
s | decide per call |
a | approve, and always-allow these tools for as long as runtime.grant_scope says (see below) |
e | explain: the checklist step the call serves and the model's recorded reasoning, then ask again |
::what the prompt shows
- –File writes and edits render as a colored diff against the current file. A byte-identical rewrite reads
no change, an existing binary file is named as binary, and a path outside the folders Saturn can reach is flaggedREFUSED. - –Shell commands render in full, untruncated, byte for byte, exactly as the shell will receive them.
- –A text shows the number, whose number it is (
+1305… is Ian Smith's mobile number (from search_contacts), oryou typed it), and the exact text. A group text lists every member and their number. - –A skill save (
create_skill) shows the whole skill, every line, or the full diff against the skill it replaces. Nothing is folded or cut. If outside content entered the conversation first, the prompt says to read each step as if a stranger wrote it. - –Everything else, notably every
mcp_*tool, renders its arguments full-width. For a tool with no custom preview, the arguments are what you approve. - –Nothing in the frame can hide. A character that reverses text direction or prints as nothing shows by code point (
⟨U+202E⟩), and an escape character shows as␛, so a command can't display differently from how it runs. - –A secret scan warns inline when a call's arguments carry a key, token, or private-key block.
- –A batch that follows instruction-shaped content opens with a quarantine banner. A call that asks despite the threshold says why: a held URL or address, an air-gap hold, or a tool that always asks.
- –If a preview fails to draw, a plain view names the call and the same reject-by-default prompt runs. The prompt always renders.
A rejected call doesn't run, isn't re-issued this turn, and the answer says it wasn't done. Every prompt is recorded as a gate event, which feeds the receipt's n gated count, /trace, and the headless --json gates record. An empty record always means you were never asked.
::grants have a lifetime
By default (runtime.grant_scope: task), answering a relaxes those tools for the rest of the current turn only. session keeps the grant until Saturn exits; persist writes it to permissions.json. The scope is a trust setting, so it's session-only unless you pass --save.
Six tools never take a blanket grant:
- –
run_shell:aoffers a prefix grant covering the full command you just reviewed, or a shorter prefix you type deliberately. - –
run_shortcut: it keeps asking. Allow one shortcut by its exact name with/policy shortcut <name>. - –
send_message: it always asks. No tier, open gate, risk override, always-allow, or--yololets a send through, and headless mode refuses it. - –
create_skill: it always asks, on the same terms as a send. A saved skill is followed as your own instructions every time it runs, and headless mode never saves one. - –
add_documentandremove_document: they always ask, on the same terms. A document's text reaches the model on every search that matches it, so what the knowledge base holds is yours to approve, and headless mode refuses both.
::shell prefix allowlist
/policy allow <prefix> saves a run_shell prefix that runs without asking. Matching is strict: whole tokens, case-insensitive, and never when the command contains a shell metacharacter (chaining, piping, redirection, substitution, or a newline), so chained and redirected commands always face you. The arguments after a granted prefix are screened every time it's used:
- –flags that open a new exec or write path (
--output,-c,--exec, …), globs and brace expansion, and paths outside the working folder (bare or as a flag's value) disqualify the command; - –a general-purpose interpreter or launcher (
python,npm,sh,ssh, …) is only exempt as the exact command you granted; - –non-ASCII text, such as a lookalike
;, never passes.
Grant narrow, read-only prefixes like git status, not broad ones like git or python.
::/policy is the front door
Every relaxation (the threshold, Shift+Tab cycling, per-tool overrides, the allowlists, headless --yolo) is a view of one policy object, and /policy is where you change it. Bare forms report; changing something always takes an explicit verb.
/policy the whole posture: what runs without asking, what can leave
/policy risk [<tool> <tier>|<tool> reset] [--save] every tool's tier, or override one
/policy allow [<prefix> | add <prefix> | list | remove <n|prefix>] the run_shell prefix allowlist
/policy shortcut [<name> | list | remove <name>] the Shortcuts allowlist
/policy open [on|off] open the gate (threshold → destructive)
/policy egress [clear|n] the egress ledger
/policy airgap [on|off] [--save] seal the network boundary/policy risk refuses to change run_shell, run_shortcut, send_message, create_skill, add_document, or remove_document. A hand-edited override for one of them in permissions.json is ignored on load. Saved state is one JSON file, database/permissions.json. A file with a garbled or wrong-shaped field fails closed: Saturn runs on strict defaults, says so at startup, and keeps the bad file as permissions.json.corrupt.
note :: careful
The ⚠ GATE OFF indicator in the status bar is read live from the threshold, so there is no separate flag to drift out of sync. /policy open off restores the tier you had before opening the gate. On a gate that isn't open it changes nothing.