Skip to main content

Role

Plan Mode lets the agent “figure out and write down intent” before executing. While active, the agent is in a read-only phase:
  • Only read-only tools plus 4 whitelisted tools work: plan_enter / plan_write / plan_exit / todo_write (the shell can be opted in — see below).
  • Any other tool call is rejected immediately (the agent sees a “plan-mode denied” note).
  • Exiting Plan Mode requires HITL confirmation (reusing the permission system’s ASK), so the model can’t unilaterally jump into execution.
This pipeline encodes “design → plan → human review → execute” — combined with todo_write and subagents, it noticeably reduces “improvise-then-break-things” outcomes on long tasks.

Opt-in

Builder options: You can also call enableTaskList() so that todos created during the plan phase show up as a small reminder before each reasoning step.

The three tools

plan_write is a dedicated write entry for Plan Mode — avoids the security risk of whitelisting the generic write_file (which would let the model write anywhere during plan).

Workflow

Any non-whitelisted tool call (e.g. write_file, or execute unless you opt in) during the plan phase is rejected immediately with something like:
Seeing the denial, the model naturally switches back to “write the plan first”.

Reading the outcome

Plan-mode entry is autonomous, so a run can end in four states. isPlanModeActive() == false alone is ambiguous — don’t treat it as success without checking whether planning actually happened: To tell these apart programmatically, track whether plan_enter / plan_write were called (e.g. from ToolCallStartEvent) alongside the final isPlanModeActive() and the plan file’s existence.

Allowing the shell during the plan phase (opt-in)

By default the shell tool (execute) is denied during the plan phase. The shell is dual-use: a single tool call can read (cat / ls / grep / git log) or mutate (rm / > / git commit / npm install), and Plan Mode decides what to permit purely by tool name — so it cannot tell a read invocation from a write one. Denying the shell keeps the read-only guarantee intact. But shell access is often the most flexible way to investigate a codebase and produce a realistic plan. When you accept that trade-off, opt in:
With the opt-in enabled:
  • execute is added to the plan-phase allow-list, so the model can investigate via the shell.
  • The plan banner gains an extra instruction telling the model to keep shell usage read-only (cat / ls / grep / git log/diff/show/status) and not to run mutating commands until the plan is approved.
  • The dedicated file-editing tools (write_file / edit_file) remain denied — they are the primary mutation path, so the read-only intent is still enforced for file writes.
This mirrors how OpenCode handles its plan agent: it allows the shell for investigation, hard-blocks the edit/write tools, and relies on the prompt to keep the shell read-only. The guarantee is therefore softer than the default (the model could still mutate via the shell), so prefer enabling this together with a sandboxed filesystem to contain the blast radius.

Runtime permission switching (the “bypass” escape hatch)

Plan Mode is one specific phase switch. Underneath it, every session carries a PermissionMode that the permission engine evaluates against. You can flip that mode at runtime — for example to grant a deliberate, user-initiated “skip all permission prompts” toggle (similar to a YOLO / dangerous-skip switch in other coding tools):
setPermissionMode(...) preserves the session’s configured allow/deny/ask rules and working directories — only the mode changes — and rebuilds that session’s cached permission engine so the switch takes effect on the next call. An in-flight call keeps the engine it started with. BYPASS disables all rule evaluation, so treat it as an explicit, per-session, opt-in action and prefer pairing it with a sandbox. To run unattended without prompts but with enforcement, use PermissionMode.DONT_ASK instead (ASK decisions become DENY rather than being auto-allowed).

Plan state is persisted

Plan Mode is runtime state and is auto-persisted along with AgentState — process restarts, node failovers, and cross-replica restores all bring back the plan phase. The plan file itself is written to plans/ in the workspace and goes through whichever filesystem mode you’ve configured (local / sandbox / remote KV), so it’s distributed-safe.

Programmatic enter/exit

When app code drives Plan Mode (e.g. an admin console button):
If you use agentscope-admin-spring-boot-starter, the admin HTTP API also exposes Plan Mode controls (POST /v1/admin/sessions/{id}:enter-plan-mode / :exit-plan-mode / GET /v1/admin/sessions/{id}/plan).

Interaction with subagents

⚠ Current known gap: subagents spawned via agent_spawn during Plan Mode do not automatically inherit the read-only restriction. To restrict the child:
  • Narrow tools in the child’s declaration to a read-only set, or
  • Also enablePlanMode() on the child’s own builder and enter it explicitly
A future release will propagate plan-mode restrictions parent → child automatically.

Interaction with todo_write

Plan Mode and todo_write (provided by core) are independent but commonly used together:
  • Plan Mode — phase switch + plan file + HITL exit
  • todo_write — maintain a structured “what to do now” list during execution (whole-list replace; exactly one in_progress)
Typical workflow: write PLAN.md during the plan phase → plan_exit → in execution use todo_write to slice the PLAN into 5–8 todos → progress one at a time. Each reasoning step shows the agent a todos reminder to stay focused. ⚠ Don’t confuse with subagent background tasks (task_output / task_cancel / task_list) — that’s a different concept; see Subagent.

Viewing the task list

The task list lives in AgentState.tasksContext and is persisted automatically with every call(). To read it from application code:
If you use agentscope-admin-spring-boot-starter, the admin REST API provides a ready-made endpoint:
It returns each task’s subject, state, owner, and dependency info (blocks / blockedBy). To observe task changes in real time through the event stream, listen for todo_write tool calls in streamEvents():
  • Workspaceplans/ directory location
  • Subagenttodo_write ≠ subagent task; don’t confuse them
  • Architecture — where Plan Mode sits in the call() timeline