Skip to content

Plan Mode

Plan mode puts the agent into a read-only exploration state where file writes and destructive commands are blocked at the tool level. The agent can read code, search, and analyze — but cannot modify anything until a plan is reviewed and approved.


There are two ways to enter plan mode:

Method Who uses it How
/plan slash command You (human) Type /plan in the chat input (web UI or TUI) — toggles plan mode on/off
toggle_plan_mode tool The agent Agent calls toggle_plan_mode with enabled: true to enter or enabled: false to exit

When plan mode activates, you’ll see a notification:

⏸ Plan mode ON — read-only exploration. Write/edit tools blocked.

When it deactivates:

▶ Plan mode OFF — full tool access restored.


Plan mode enforces read-only access through two layers:

Tool access in plan mode is an allowlist: a tool runs only if it is known to be read-only. Everything else is blocked until plan mode is exited.

Allowed:

  • Built-in read tools: read, grep, find, ls, and bash (with the command restrictions below)
  • Plan and interaction tools: toggle_plan_mode, plan_mode, AskUserQuestion, update_todo
  • Read-only PizzaPi tools: for example bash_output, memory_read, memory_list, list_tunnels, list_models, list_workflows, check_messages, web_search, web_fetch
  • Tool discovery and codemode. Every tool call nested inside codemode goes through the same plan-mode check.
  • MCP tools that the server annotates as read-only (annotations.readOnlyHint: true in the MCP tool listing)

Blocked:

  • edit, write, write_file: file modification
  • subagent, spawn_session: spawn child contexts with full write access
  • MCP tools without readOnlyHint: true, including tools with no annotations
  • Any other tool that is not known to be read-only. This includes tools that mutate state (kill_shell, create_tunnel, memory_save, send_message, run_workflow, and so on), powershell, and tools from plugins or overlay packages.

MCP annotations come from the configured server, so PizzaPi trusts them to the same degree that it trusts the server. To allow a specific tool that you trust to be read-only, list its registered name in PIZZAPI_PLAN_MODE_ALLOWED_TOOLS. MCP tools are registered as mcp__<server>__<tool>.

Terminal window
PIZZAPI_PLAN_MODE_ALLOWED_TOOLS="mcp__linear__search_issues,my_overlay_lookup"

The variable takes a comma-separated list and is empty by default. It cannot re-enable edit, write, write_file, subagent, or spawn_session.

Bash commands go through a destructive-command detector. It normalizes each command segment before matching: paths and quotes are stripped from the executable (/usr/bin/curl, \kill, 'rm'), and it looks past leading (, {, !, if/then/do, variable assignments, and redirections. If the shell cannot know which executable runs, the segment is blocked. That covers $cmd, ${X}, $'…', globs in the command word, function definitions, case, trap, and hash.

Blocked commands:

  • Command, backtick, and process substitution, multi-line commands, and /dev/tcp / /dev/udp redirection

  • Process and system control: sudo, doas, kill, pkill, killall, reboot, mutating systemctl / service / launchctl, crontab, at, open / xdg-open

  • Opaque code execution: python, node, ruby, perl, php, deno, tsx, and other interpreters (except bare --version / --help); bash script.sh; eval / source; npx, bunx, uvx, bun -e, npm exec, pnpm dlx, go run, cargo run; parallel

  • Network and remote mutations: ssh, scp, rsync, nc, socat, telnet; curl with a request body, a form, an upload, a config file, or a non-GET method; wget --post-* / --method; HTTPie writes; any gh command except read-only ones (list, view, status, diff, checks, search, and gh api GET requests without fields); docker / podman / nerdctl, kubectl / oc, and helm except read-only subcommands (known global options such as -n default, --context prod, or docker compose -f file.yml are skipped with their values; an unrecognised option written as a separate word before the subcommand is blocked, because it might take a value that hides the real subcommand; use the --option=value form for options plan mode does not know; kubectl / oc with --profile (other than none), --profile-output, --log-file, or --log-dir is blocked because it writes local files); npm publish and similar registry commands

  • Hidden execution: awk system() and pipes, sed e and w commands, and find -exec / xargs / watch running a blocked command

  • Dangerous inline environment variables: PATH=…, LD_PRELOAD=…, GIT_*=…, *PAGER=… (except cat), NODE_OPTIONS=…, and others, whether set as VAR=x cmd, env VAR=x cmd, or export VAR=x

  • Git mutations: only read-only git subcommands are allowed (status, log, diff, show, blame, grep, branch (listing), ls-files, etc.). Mutating operations like git commit, git push, git checkout, and git stash push are blocked.

  • File-mutating commands: rm, mv, cp, mkdir, touch, chmod, ln, tee, truncate, dd, sed -i, etc.

  • Package managers: npm install, bun add, pip install, brew install, etc.

  • Output redirection: >, >>, and <> (except 2>/dev/null)

  • make (without -n), and find -exec / -delete

  • Read any file (read tool)
  • Run read-only bash commands (ls, find, grep, rg, cat, head, wc, git log, git diff, curl -s <url>, gh pr view, etc.)
  • Use MCP tools annotated readOnlyHint: true, and tools listed in PIZZAPI_PLAN_MODE_ALLOWED_TOOLS
  • Call toggle_plan_mode to exit plan mode
  • Call plan_mode to submit a structured plan for review

The core workflow in plan mode follows this pattern:

  1. You activate plan mode — via /plan or the agent enters it with toggle_plan_mode.

  2. The agent explores — reads files, searches code, runs read-only commands to understand the problem.

  3. The agent submits a plan — calls the plan_mode tool with a title, optional description, and a list of steps.

  4. You review the plan — a plan review card appears with the plan details and action buttons.

  5. You respond — approve, suggest edits, or cancel.

  6. The agent executes — if approved, plan mode exits automatically and the agent proceeds with full tool access.

The agent submits a plan by calling plan_mode with these parameters:

Parameter Type Required Description
title string ✅ Short title summarizing the plan
description string No Detailed description in markdown
steps array No Ordered list of steps, each with a title (required) and optional description

The tool blocks until the user responds — the agent cannot continue until you act.

When a plan is submitted, you see four actions:

Action What it does
Clear Context & Begin Clears the conversation history and starts fresh execution of the plan. The agent gets a clean context with only the approved plan.
Begin Approves the plan and proceeds with the current conversation context intact.
Suggest Edit Opens a text input where you describe changes. The agent receives your feedback and should resubmit an updated plan.
Cancel Rejects the plan entirely. The agent is told not to proceed.

In the PizzaPi web UI, the plan review appears as an interactive Plan Review panel that replaces the text input area:

  • A blue-accented card shows the plan title, description, and numbered steps
  • Steps auto-collapse when there are more than 5 (click to expand)
  • Four action buttons are displayed in a responsive grid (2×2 on mobile, inline row on desktop)
  • The Suggest Edit button reveals an inline text input — type your feedback and press Enter or click Send
  • The panel is capped at 50% viewport height (60% on desktop) and scrolls internally so it never blocks the full screen

In the terminal TUI, the plan renders as an ANSI-styled box with numbered options (1–4). Type a number to choose, or type free text to submit it as an edit suggestion.


After a plan is approved via the legacy TUI menu path, the agent enters execution mode. If the plan had steps, PizzaPi tracks progress:

  • The agent marks steps complete by including [DONE:n] tags in its responses (e.g., [DONE:1])
  • A checklist of plan steps is displayed, updating as steps are completed
  • When all steps are marked done, a Plan Complete! ✓ message appears with all steps struck through

In the TUI, after the agent finishes exploring (if it hasn’t used the plan_mode tool), a menu appears:

  • Execute the plan (track progress) — exits plan mode and starts execution with step tracking
  • Stay in plan mode — keep exploring
  • Refine the plan — opens an editor to provide feedback

In multi-agent setups where a parent agent spawns child sessions, plan review integrates with the trigger system.

When a child session calls plan_mode, it fires a plan_review trigger to the parent session instead of waiting for a human response. The parent sees:

🔗 Child "my-child" submitted a plan for review:
## Refactor Authentication Module
1. Extract token validation into shared utility
2. Update all route handlers to use new utility
3. Add unit tests for edge cases
Respond with `respond_to_trigger` using trigger ID `abc-123`.
Use respond_to_trigger with action: "approve" to accept, "cancel" to reject, or "edit" with feedback.

The parent agent can respond with:

  • respond_to_trigger(triggerId, "approve", "approve") — child proceeds with execution
  • respond_to_trigger(triggerId, "cancel", "cancel") — child stops
  • respond_to_trigger(triggerId, "Your step 2 should also handle middleware", "edit") — child revises the plan
  • escalate_trigger(triggerId) — passes the decision to the human viewer

Separate from plan_mode (which submits a plan for review), the toggle_plan_mode tool lets the agent enter or exit read-only exploration mode programmatically.

Parameter Type Required Description
enabled boolean ✅ true to enter plan mode (read-only), false to exit (full access)

Response examples:

  • "Plan mode is now ON (read-only)." — when entering
  • "Plan mode is now OFF (full access)." — when exiting
  • "Plan mode was already ON (read-only). No change." — if already in the requested state

If the agent exits plan mode without ever submitting a plan via plan_mode, it receives a soft nudge:

Note: you exited plan mode without submitting a plan for user review. Next time, use the plan_mode tool to present your plan before exiting.


  • State persists across resumes. Plan-mode state is saved in the session transcript. When the session starts, resumes, reloads, or forks (from the TUI, from --continue, or from the web UI’s in-place switch), PizzaPi restores the latest saved state. It also reapplies the sandbox read-only overlay before any tool call can run. A session that was in plan mode stays read-only until plan mode is exited. In-progress execution steps are restored as well.
  • New sessions start with plan mode off. A new session has no saved state, so /new and switching to a session that never used plan mode start with full access and clear the sandbox overlay. Plan mode from the previous session does not carry over.
  • Context filtering — when plan mode is off, the [PLAN MODE ACTIVE] context messages are automatically stripped from the conversation to keep the context window clean.

Use plan mode when:

  • The task is complex and touches many files — you want to verify the approach before changes begin
  • You’re working with an unfamiliar codebase and want the agent to explore first
  • The change is risky or hard to reverse (database migrations, config changes, refactors)
  • You’re using a multi-agent setup and want parent approval before child agents make changes

Skip plan mode when:

  • The task is simple and well-defined (fix a typo, add a log line)
  • You’ve already described the exact changes you want
  • Speed matters more than review — you can always undo with git