Control what Mnemo AI can do¶
Decide what runs without asking, what asks first, and what is refused outright.
This page covers:
- Approve actions as they happen — the
Proceed?prompt and its toggles - Stop being asked, for a while —
/autoand its tiers - Investigate before changing anything — plan mode
- Understand the safety floor — what is refused even with confirmations off
- Keep git operations recoverable — git-specific protections
- Why a write was refused — the read-before-write gate
Approve actions as they happen¶
Destructive tools stop and ask before they run — shell commands and background
launches (execute_bash, start_background_task)
and file modifications (fs_write, file_edit):
▶ Run shell command?
rm -rf build/
Proceed? (y/N/a=allow all this session):
▶ Write to file?
create ~/script.py
Proceed? (y/N/a=allow all this session):
Only an explicit y/yes proceeds. a trusts that whole category for the rest
of the session, so a multi-step task doesn't re-prompt. Anything else — including
Enter — declines, and the model is told you declined rather than being left to
retry.
This is a hard gate enforced client-side: the prompt fires regardless of what the model does, because the client owns the terminal. The MCP server is a piped subprocess and cannot prompt you.
| Toggle | Default | Gates |
|---|---|---|
REQUIRE_BASH_CONFIRMATION |
true |
execute_bash, start_background_task |
REQUIRE_WRITE_CONFIRMATION |
true |
fs_write, file_edit |
REQUIRE_MEMORY_CONFIRMATION |
false |
memory writes to MEMORY.md |
REQUIRE_GIT_CONFIRMATION |
true |
Overriding a git safety refusal |
Set any to false for a trusted or automated setup. Foreground runs with no TTY
— tests, pipes, CI) auto-proceed so they can't hang waiting for a keypress.
Tired of confirming the same harmless command? A tool hook can
answer the prompt for you — a PreToolUse hook that prints
{"decision": "allow"} for git status waives that one prompt without turning
the whole execute_bash gate off. A hook can also deny a call outright, which
is enforced everywhere the prompt is. What it can't do is reach past plan mode or
the safety floor below.
Sub-agents can't approve on your behalf
Spawned sub-agents and parallel/background orchestrator
workers never ask for approval. Read-only and already-approved actions run;
unapproved destructive actions are refused immediately. Approval can come
from the confirmation toggles, session trust, an approved plan, the current
/auto tier, or a hook. Delegation itself grants no permission, including at
/auto off. Tool allowlists, plan mode, hook denials, and server protections
still apply. A flagged git override without prior trust is refused.
Lone or sequential orchestrator steps running in the foreground can ask for
approval, just like the main assistant. This includes a dependency chain and
SUBAGENT_MAX_CONCURRENCY: 1. Running a step inline does not remove restrictions
inherited from an already-headless or spawned caller.
Starting a background command is a foreground decision: the main assistant can
ask you to approve start_background_task before launching it. The running
command is unattended. A worker attempting the same launch must already have
permission; selecting a background tool does not grant it.
Stop being asked, for a while¶
On a task that touches thirty files the prompt stops being a safeguard and becomes
a keystroke tax. /auto turns it off for a tier of operations, for this
session only — and unlike the two other ways out, it's reversible: a trusts a
whole category with no way back, and setting REQUIRE_WRITE_CONFIRMATION: false
is permanent and applies everywhere.
| Tier | Runs without asking |
|---|---|
off |
Nothing — every destructive tool asks (the default) |
edits |
File writes inside the current working directory |
writes |
All file writes, any path, plus MEMORY.md updates |
all |
The above plus shell commands |
/auto steps to the next tier and wraps back to off; /auto edits (or any tier
name) jumps straight there. While a tier is active the input prompt carries a
colored badge — green ⏵ auto-edits, amber ⏵⏵ auto-writes, red ⏵⏵ auto-all —
so the state is visible on every line you type, not just at the moment you set it.
For a run that should go uninterrupted from the very first turn, start there:
That opens the session at all. It's a bare switch on purpose — the tiers live in
the command, so use /auto at the prompt to narrow or drop it. Either way it's the
same session state, with the same red badge.
edits is the tier to reach for. It waives the prompt for exactly the thing that
interrupts most, scoped to the tree you're working in: a write to ~/.ssh/config
or /etc/hosts still asks, and so does one whose path can't be resolved — when
the scope is unknowable, asking is the safe answer. Symlinks are resolved on both
sides, so a link pointing out of the directory can't smuggle a write past the
scope.
It replaces the keypress and nothing else. Everything below the prompt still applies, unchanged:
- The server-side safety floor still refuses system-destroying commands and system-directory writes.
- Plan mode still hard-blocks the
mutating tools — and turning plan mode on resets the tier to
off, since a mode that blocks and a mode that waives would leave one of the two doing nothing. - A tool hook that denies a call still denies it.
- Git safety overrides are never auto-approved, at any tier. When a tool refuses an operation as risky and asks to override it, that stays your decision — a flagged operation asks in every mode.
The tier is session state: it's gone on restart, and /auto off ends it
immediately.
Investigate before changing anything¶
Toggle plan mode with /plan before asking for a complex change. The assistant
investigates and drafts a plan without touching anything, then you approve and it
executes.
Reach for it when the change spans multiple files, needs an architectural decision, or the requirements aren't settled yet.
While plan mode is on, only read-only tools work: file reads, glob_search
and grep_search, read-only shell (ls, cat, git status, git log,
git diff, …), web search, and document readers. Any attempt to edit files, run
mutating shell, perform git writes, or start a background task is hard-blocked
client-side — regardless of what the model tries.
Built-in tools are explicitly classified, with tests that fail when the tool
registry and policy drift. Planning controls and cancel_background_task remain
available so existing work can be stopped; clear_documents and
clear_completed_tasks are blocked. Only writes to the designated plan file are
exempt from the file-write block.
External MCP tools: while planning, an external tool must declare
annotations.readOnlyHint: true. Unannotated external tools are refused with an
explanation, even if their name sounds read-only. This is a compatibility
tightening for servers that omit annotations; outside plan mode their normal
availability is unchanged. A declaration is supplied by the external server,
not inferred from a tool name, and is not an OS sandbox.
When the plan is ready the assistant calls exit_plan_mode, which shows it and
asks how to proceed:
| Key | Effect |
|---|---|
y |
Approve and run — plan mode turns off and the plan executes in the same turn |
e |
Open the plan in $EDITOR to tweak it, then re-review |
n |
Keep planning — stays read-only and keeps refining |
The approved plan is saved to ~/.mnemoai/plans/plan_<timestamp>.md, so it
survives compaction and can be re-read later.
Pre-approved commands. A plan can declare the shell commands it will run
during execution — tests, builds, installs. Approving the plan pre-approves them,
so they run without a per-command Proceed? prompt while the plan executes;
anything not on the list still prompts. These pre-approvals are scoped to that
plan and are cleared when you /clear or re-enter plan mode.
Plan mode is enforced at the same client-side chokepoint as the confirmation gate, so it holds across the normal loop and the orchestrator workers alike. A misbehaving local model cannot mutate anything while it is on.
Understand the safety floor¶
The confirmation prompt is a convenience layer you can switch off. Below it sits a floor that is always on and lives inside the MCP server, so it holds even with confirmations disabled, in non-interactive runs, or if the server is driven by a different client.
| Layer | What it does | Can be disabled? |
|---|---|---|
| Your tool hooks | Whatever you scripted — can deny a call, or waive its prompt | Yes (it's your file) |
| Client confirmation prompt | Asks Proceed? (y/N/a) before each shell, file, or memory operation |
Yes (REQUIRE_*, /auto) |
| Plan mode, while on | Hard-blocks every mutating and executing tool | Yes (/plan) |
| Server-side safety floor | Refuses system-destroying commands and system-directory writes | No |
Shell commands (execute_bash, start_background_task) are refused when they
would:
- recursively force-delete a root or home target —
rm -rf /,rm -rf ~,rm -rf /*, includingrm -rf / --no-preserve-rootand variants that hide the target behind trailing flags or a redirection - create a filesystem or overwrite a raw device —
mkfs…,dd of=/dev/…,shred/wipefson a device - change the machine power state —
shutdown,reboot,halt,poweroff,init 0/6 - fork-bomb the machine —
:(){ :|:& };: - write into a protected system directory —
echo x > /etc/hosts,sh -c '… > /etc/…',tee /etc/…,cp … /usr/local/bin/…,sed -ion a system file. Shell writes are classified with the same path policy as the file tools, so the two cannot disagree.
File writes (fs_write, file_edit) are refused when the target is inside a
critical system directory — /, /etc, /bin, /usr, /boot, /dev,
/System, /Library, … — with .. and ~ resolved first, so a symlink can't
step around the check. Your home, project trees, temp directories, and the app
home stay fully writable. /dev/null and the other standard sinks stay writable
too, so 2>/dev/null keeps working.
The floor is deliberately narrow. Scoped, everyday-destructive commands like
rm -rf build/ or git reset --hard are not blocked here — they stay gated by
the main assistant's confirmation prompt. These checks block common catastrophic
command forms. They are not an operating system sandbox: arbitrary scripts can
have effects a shell-text classifier cannot determine.
Web requests go through a URL policy: only http/https, and every resolved
address must be public. Loopback, link-local, private ranges, and cloud metadata
endpoints such as 169.254.169.254 are refused as initial destinations. This is
a preflight check: browser redirects, subresources, and DNS changes after
validation require network-level controls for complete isolation.
Keep git operations recoverable¶
Use git_safe, git_status_safe, and git_commit_safe rather than driving git
through execute_bash — they add checks that plain git has no reason to.
| Operation | Protection |
|---|---|
Force push to main/master |
Blocked — cannot be overridden |
Option injection (-c, --exec-path, --upload-pack, …) |
Blocked — these make git run arbitrary commands |
git reset --hard |
Asks first |
git push --force |
Asks first (prefer --force-with-lease) |
git commit --amend |
Checks whether the commit was already pushed, then asks |
git clean -f / -fd |
Asks first |
Force-delete branch (-D) |
Asks first |
Skip hooks (--no-verify) |
Asks first |
Checks run against the command as git will receive it, not the raw string, so
quoting can't smuggle a flag past them: push origin main --for"ce" is still
recognized as a force push. Conversely, a commit message that merely mentions a
dangerous operation is not treated as one.
When an operation needs confirmation, you are asked — not the model:
▶ Proceed with flagged operation?
git reset --hard HEAD~1 — Hard reset will discard all uncommitted changes permanently.
Proceed? (y/N/a=allow all this session):
Decline and the model is told you declined and instructed not to retry with an
override. This is gated by REQUIRE_GIT_CONFIRMATION (default true).
Why a write was refused¶
A write can also be refused for a reason that has nothing to do with safety policy: the assistant must read a file before writing it, and its read must still be current.
| Message contains | Cause | Fix |
|---|---|---|
read it first |
The file was never read this session | Read it, then retry |
changed on disk since you last read it |
The file changed after it was read | Re-read so the edit is based on current content |
This exists because a blind edit to a file that changed underneath silently discards someone else's work — including your own edits in another editor.
See also¶
- Configuration: the
REQUIRE_*toggles and every other key - Tool hooks: script your own allow/deny rules around tool calls
- Tools reference: every tool and its parameters
- Sub-agents & orchestration: how confirmations behave in a delegated run
- Troubleshooting: diagnosing a refused write