omp packages

@nredd/pi-plan-mode0.59.3-nredd.2

Pi extension that adds a Codex-like read-only /plan collaboration mode.

GitHub Actions Published 6 days ago MIT ~2.6K downloads/mo

Untested on omp

This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

pi-plan-mode (nredd fork) -- Plan Before Pi Edits Code

Pi extension License: MIT

Maintained fork of @narumitw/pi-plan-mode (MIT). Imported into nredd/pi-harness from nredd/pi-plan-mode commit 4c2bfc3459b07b294efab62e9a8a01849f67a831, retaining its history, authorship and MIT license. The source fork rebases the patch queue onto upstream @narumitw/pi-plan-mode@0.59.3 (release commit 9b8befda), with the core fork's v1.3.0-nredd.6 development host. The latest released fork remains v0.59.2-nredd.4; the install examples below deliberately use that tag, not this review branch. Install a v*-nredd.N tag, not the default branch: main is the retired 0.58.0 fork. dist/ is committed because pi loads it straight from the git checkout.

Differences from upstream. The presentational ones come first; the ready-plan flow, decline gate, and bash policy changes follow:

  • "Planning" and "implementing" (Plan mode on with no plan ready yet, and actively implementing an approved plan) have no above-editor widget at all -- no Plan mode — ... / Implementing plan — ... line, no divider. Both are steady states with nothing pending, so the widget only duplicated the footer. The only indicator is the footer status chip, colored with the theme's accent role like Codex's Plan mode footer badge, instead of upstream's plain unstyled text. The tool policy is still reachable via /plan and /plan settings.
  • States with a concrete pending action (plan ready, plan saved) keep their existing one-line above-editor widget, since there's a decision to make that the footer chip alone doesn't surface. After a valid plan settles, Pi opens a compact, once-only action chooser with no policy prose: use arrows, Tab, Enter, or click an action cell. The completed plan remains directly above it for review.
  • Both Plan tools render for Pi 1.0's one-line collapsed rows (<renderCall first line> · <renderResult first line>), so the summary sits on the first line and expanded rendering carries the detail:
    • plan_mode_question: call plan question · Scope, Tests; collapsed result Scope → Small · Tests → Full or cancelled (reason); expanded shows every question with its options (the chosen one marked) and notes. No more raw JSON.
    • plan_mode_complete: call is the plan title (first heading); collapsed result plan proposed, so the row reads <title> · plan proposed; expanded shows the full plan.
    • The proposed-plan transcript message (shown when a plan settles, and by /plan show) has a registered renderer too, so it sits in the same disclosure gutter as tool rows: collapsed it is Proposed Plan · <title> (or Saved Plan, Active Implementation Plan), click or Ctrl+O expands the full plan. The message carries details: { version: 1, title, plan }; messages persisted before this fall back to parsing the **Title** content. Upstream renders it as an always-expanded purple box.
  • Ready-plan approval opens by itself once a valid plan_mode_complete settles idle with no queued messages. Upstream leaves it closed until /plan. Cancellation, incomplete prose, reload, supersession, exit, and session replacement don't open it.
  • Escape in the ready-plan chooser runs Stay in Plan mode: the plan is kept and Plan mode stays active. Ctrl+C closes the chooser with no action. The plan is discarded only through the explicit Discard plan and exit item.
  • Declined plans (decline gate). Escape / Stay in Plan mode on a ready plan, or sending a message while a plan awaits action, counts as a decline (declinedPlans, once per proposal, persisted with the Plan state). The counter resets when a new planning workflow starts or Plan mode exits.
    • Hard gate: after a decline plan_mode_complete returns an error until plan_mode_question runs in explicit reproposal_confirmation mode and its canonical Re-propose now choice is selected, or you run /plan finalize. Ordinary clarification, Not yet, cancellation, custom text, and answer-label inference never grant permission. A <proposed_plan> block is ignored with a warning while blocked.
    • Contract: with 1 decline the model is told to restate what changed and what is open and re-verify the facts your feedback touched. With 2 or more it must list every thread raised since the last proposal as resolved/open. Before either replacement, confirmation mode uses its fixed review-only question and tool-produced Re-propose now / Not yet choices, not model-provided labels. On ui_unavailable it tells the user to run /plan finalize; after cancellation or Not yet, it continues material planning or awaits input rather than repeating the confirmation. declinedPlans remains historical while persisted reproposalReview derives both the guard and injected contract. The text is injected per request (src/decline-contract.ts), not into the stable Plan contract. Upstream's "clarification-only, resubmit the unchanged plan" rule is removed.
  • Bash policy (src/tool-policy.ts), tightened and extended:
    • A safeSubcommands prefix approves only its own segment; the rest of a compound command is validated independently. Redirections and pipes into interpreters are rejected. Upstream trusts the whole command.
    • cd <dir> and git -C <dir> are allowed under the cwd or a trustedDirectories entry (new global-only setting); cd moves the effective directory for later segments.
    • ssh <host> <cmd> (with a configured prefix) re-validates the remote command; gh api is GET only; curl has no output/data/upload flags.
  • Extension tools that declare annotations.readOnlyHint: true classify as read-only (annotated) and are admitted by default, on top of defaultPlanTools or the safe built-ins. Only user-installed extensions and packages (and explicit -e files) count; native MCP tools (builtin:mcp), other built-in extensions, and project-scope extensions keep upstream's explicit opt-in, since their hints are third-party or could ship with an untrusted repository. A package that proxies an MCP server is admitted on that package's say-so. Mutating tools must stay unannotated.
  • An ssh_exec tool call ({ host, command }, from pi-dev-tools) is gated like ssh <host> <cmd>: host must be a configured ssh prefix, command must pass the remote reviewed policy, cwd must be a plain path, and any other parameter (stdin included) is refused.
  • Approving a plan for implementation (Implement here, /plan implement, Start fresh) emits pi-plan-mode:plan-approved on pi.events with { version: 1, planId, plan, sessionId }, the plan verbatim. rpiv-todo turns it into plan-sourced todos that need evidence to complete. The approval is also recorded as a plan-mode-approved-plan session entry; a fresh implementation session restores late setup and announces it before its first model request, including an extension-triggered custom start, and a resume re-announces the same planId (listeners treat that as a no-op). Nothing listening is fine.
  • In print and JSON mode, /plan commands that send a model turn (finalize, implement, /plan <prompt>) return only after that turn has run, so a scripted pi -p "/plan start" "plan it" "/plan finalize" gets every turn answered. Upstream returns right after sending, and print mode exits with the turn unanswered.
  • Start fresh and implement starts the fresh session right away. Upstream stages it until the run that produced the plan settles (fresh-handoff-coordinator.ts); the fork drops that path because its chooser only opens once the run is already idle.

Upstream 0.59.3 now preserves ready plans on custom-message starts; the fork keeps that guard after late state restoration and approval announcement. The workflow mutex and saved/implementation plan lifecycle otherwise remain upstream-owned. The fork's policy and decline-contract differences are listed above.

Use a Codex-like /plan mode to explore a codebase, resolve important questions, and approve an implementation-ready plan before Pi edits files.

✨ Features

  • Starts and manages Plan mode through /plan, /plan start, or /plan <prompt>.
  • Blocks mutations, inactive helpers, and unsafe shell forms while keeping helper schemas stable.
  • Uses structured questions for important ambiguity and explicit completion for a decision-ready plan.
  • Reviews the complete plan before implementation, export, save, further planning, or discard.
  • Implements in the planning session or a fresh linked session with the approved plan.
  • Configures persistent or one-shot destination model and thinking choices for fresh implementation.
  • Restores Plan state and one saved plan across resume and compaction.
  • Configures the Plan tool allowlist, reviewed shell commands, user-trusted subcommands, export path, plan reinjection, shortcut, and thinking level.
  • Publishes statusline state and cooperates anonymously with Workflow Mutex Protocol v1 participants.

📦 Install

This release requires Pi 0.80.6 or newer. Native PowerShell tool support requires Pi 0.84.3 or newer on Windows; earlier Pi versions omit that optional tool and retain the existing Plan policy.

pi install git:github.com/nredd/pi-plan-mode@v0.59.2-nredd.4

Try without installing permanently:

pi -e git:github.com/nredd/pi-plan-mode@v0.59.2-nredd.4

Build and try this package locally from the repository root:

npm ci --ignore-scripts
npm run build
pi -e .

The package declares dist/index.ts, so build an unbuilt local checkout before Pi loads the package directory. Install only from sources you trust because Pi extensions run with Pi's permissions.

🚀 Quick start

Run /plan to open the state-aware menu, then start Plan mode and ask the agent to inspect and design the change. Run /plan <prompt> when the first planning request is already known. When the plan is ready, Pi opens a compact action chooser after the run settles; review the completed plan above it, then use the keyboard or click an action cell.

🗺️ How it works

Plan mode keeps exploration and implementation on opposite sides of an explicit review boundary:

flowchart LR
    start["Start: /plan or /plan with a prompt"]
    start --> explore["Explore safely: inspect and clarify"]
    explore --> complete["Complete the plan with plan_mode_complete"]
    complete --> review["After settlement: compact ready-plan actions"]
    review -->|Revise| explore
    review -->|Implement here| current["Current session: planning context retained"]
    review -->|Start fresh| fresh["Fresh session: approved plan transferred"]
    review -->|Save| saved["Saved for later"]
    review -->|Export| exported["Markdown file"]

During planning, the agent can inspect the project and ask material questions, but Plan mode blocks editing tools and unsafe shell forms. Implementation starts only after the plan is complete and you choose a handoff:

sequenceDiagram
    actor User
    participant Pi
    participant Plan as Plan mode
    participant Work as Implementation

    User->>Pi: Start planning
    Pi->>Plan: Apply the configured Plan tool policy
    Plan->>User: Ask material questions when needed
    User-->>Plan: Answer or refine the request
    Plan->>Pi: Submit the complete plan
    Pi-->>User: Show compact ready-plan action cells after settlement
    User->>Pi: Review and choose with keyboard or mouse
    alt Implement here
        Pi->>Work: Restore Normal mode in the current session
    else Start fresh and implement
        Pi->>Work: Create a fresh session with the approved plan
    end

💬 Commands

Command Purpose
/plan Start or manage planning, review a plan, or choose same-session or fresh-session implementation.
/plan start Enter Plan mode without sending a model message.
/plan <prompt> Start planning with a prompt, or send a follow-up while already active.
/plan tools Choose a session-specific tool policy, then start; cancellation changes nothing.
/plan show Display the stored plan without starting a model turn.
/plan finalize Ask the active planner to finish or ask one remaining material question. Also lifts the decline gate.
/plan implement Implement a completed or saved plan in this session, without a selector.
/plan save Save a ready plan in this Pi session and leave Plan mode.
/plan settings Open the same Plan Settings screen available from the menus.
/plan export [path] Write a ready, saved, or active implementation plan to Markdown.
/plan exit (alias: off) Leave Plan mode and discard its ready plan, or clear a saved/active plan.

All routes support TUI and RPC. Print and JSON modes reject the menu, tools, and settings; stored-plan display and implementation have additional mode restrictions. Exact subcommand words select routes; other text is a planning prompt, so /plan start a migration sends a prompt rather than rejecting trailing text. There is no startup flag.

State-changing transitions require an idle run; tool selection is locked once planning starts. show, save, export, and implement require an applicable stored plan; finalize requires active Plan mode. Export defaults to the configured destination (PLAN.md when unset), never overwrites an existing target, and ends Plan mode only when exporting a ready plan. See command workflows for tool selection, busy-state recovery, path handling, and export cancellation, and Security and privacy before allowing custom tools.

🔒 Security and privacy

While Plan mode is active, the policy blocks built-in editing tools and instructs the agent not to edit files or implement the change. It should explore first and ask structured questions when a preference or tradeoff materially changes the plan. Configure persistent defaults or a one-workflow tool override before activation; active and ready workflows lock those controls.

Plan mode registers plan_mode_question and plan_mode_complete during extension load and never changes their active status itself. Another active-tool policy may hide them, in which case Plan start or restore fails without widening that policy. By default, the Plan policy allows active safe built-ins such as read, limited bash, limited powershell, grep, find, and ls. The optional native powershell tool must be active when an automatic Plan policy starts, for example through Pi's Windows defaultTools setting, unless its name was explicitly retained for first-request resolution. Tools named edit, write, or update_plan and deselected tools are blocked at execution time even though active schemas remain visible. Tools outside the reviewed core policy are denied by default except trusted user-scope/package extension tools declaring annotations.readOnlyHint: true, as described above. Native MCP, native orchestration, built-in extensions, and project-scope extensions still require explicit opt-in. Server annotations such as readOnlyHint are unverified hints, not permission or a safety guarantee; the fork trusts the declaration only from the admitted extension sources. For example, opt into mcp__docs__read, mcp__firecrawl__firecrawl_scrape, or lsp_diagnostics through /plan tools or defaultPlanTools before the next workflow.

Direct and model-only tools must be active in Pi; model-only tools cannot be called by another tool. Registered codemode and deferred tools are selectable without direct activation and can run through Pi's nested tool pipeline. MCP codemode-deferred follows its public deferred exposure. Select an orchestrator such as codemode or tool_search separately when needed: allowing it does not authorize its callees, and selecting an MCP tool does not enable its orchestrator. Pi's tool_search may declare tools independently, but declaration does not admit unselected names to the frozen Plan policy. Every nested call remains individually gated, including editing and limited-shell restrictions. Hidden tools remain unavailable even when selected or listed as active; unsupported exposure and unavailable policy metadata fail closed. Registered but inactive direct tools, unregistered tools, blocked core tools, and unselected tools report distinct denial guidance. A tool admitted before later deactivation can be reactivated and reused in the current workflow without restarting. After they become visible, the Plan-only helpers remain visible in Normal mode, but their handlers and the tool_call policy reject calls unless Plan mode owns the active workflow.

Limited bash uses a fail-closed Bash policy, including when an extension overrides the canonical bash tool name. It accepts common inspection commands, read-only Git and npm queries, pipelines and command lists composed entirely of accepted commands, plus selected checks such as npm test, npm run typecheck, and cargo test. Case-insensitive -i is allowed for inspection commands such as rg, grep, and git grep. Commands where -i edits in place, including sed, stay blocked. It also accepts hostname without arguments and local Windows tasklist queries using reviewed display, filter, module, and service flags. Reviewed Git inspections may place --no-pager before the accepted subcommand. They may also place one or more complete -C <path> pairs before the accepted subcommand only when every path stays under the Pi working directory or a trustedDirectories entry (no .., no flags, no expansion beyond a leading ~). Other targets are rejected so git -C cannot introduce executable configuration, hooks, filters, signing programs, or lazy-fetch remotes from an untrusted repository. cd <dir> follows the same rule and moves the effective directory for the following segments (cd ~/code/pi && git status). gh api (GET only) and curl (no output, data, or upload flags, GET only) are accepted with fixed argument checks, and ssh <host> <cmd> is accepted for a configured ssh <host> prefix with the remote command re-validated. It rejects output/input redirects, shell expansion, substitutions, subshells, background jobs, piping into interpreters (sh, bash, python, node, ...), incomplete -C pairs, other Git global options, Git config overrides, mutating flags, dependency changes, editors, and unknown commands.

Limited powershell uses a separate fail-closed PowerShell policy, including when an extension overrides the canonical powershell tool name. It accepts canonical inspection cmdlets such as Get-ChildItem, Get-Content, Get-Item, Get-Location, Resolve-Path, Select-String, Test-Path, Measure-Object, Sort-Object, Format-List, Format-Table, Out-String, and Write-Output. It accepts local Get-Process and Get-Service queries with reviewed static selectors while rejecting remote and object-input parameters. It also accepts the same reviewed git and configured gh queries as limited Bash, including pipelines and semicolon-delimited command lists composed entirely of accepted commands. It rejects redirects, variables, substitutions, script blocks, call operators, type or method expressions, stop-parsing tokens, multiline input, non-ASCII quotation delimiters, aliases, mutating cmdlets, and unknown commands. Use canonical cmdlet names because PowerShell aliases are intentionally outside the reviewed policy.

A rejected parsed command list or pipeline identifies its first blocked command segment; malformed or unsupported shell syntax reports the complete submitted input instead. Tests and builds may still write ignored caches or build artifacts and may execute project-defined hooks; enable or invoke them only when the repository is trusted. Both limited-shell policies reduce risk but do not provide an OS sandbox or confidentiality boundary. A configured safeSubcommands match bypasses the argument checks for its own segment only; the other segments of a &&/||/;/| list are validated independently, and redirections, substitutions, and pipes into interpreters stay rejected.

🧭 Planning and implementation

plan_mode_question follows Codex's request_user_input pattern: the agent can ask 1-3 concise questions, each with meaningful options and a free-form Other path. In TUI mode, a single question shows its header as plain muted text, submits as soon as its preset or custom answer is confirmed, and does not show tabs, Review, or question-navigation controls. Add an optional note with n before confirming a single preset answer. With two or three questions, one question appears at a time with question tabs and a final Review tab. Use Tab, Shift+Tab, left, or right to visit any question or Review, including unanswered future questions. Use up and down to choose an option, Enter to record it and advance, or n to record the highlighted option and open its optional note editor. Press n again on an answered item to edit or clear its note. Revisit a question to replace its answer; changing the chosen option clears its prior note. Review lists every answer and note, blocks incomplete submission, and requires returning to a question to edit its answer or note. Custom answers and notes retain their raw submitted text in the tool result, while terminal rendering is sanitized. In TUI mode, a custom editor registered through Pi's ctx.ui.setEditorComponent() also handles answer and note editing in a separate instance, without changing the main prompt draft. While that editor is active, it controls Escape and submission keys; Ctrl+C still cancels the question. The editor's submitted value is retained, including any whitespace normalization it intentionally applies. Without a custom editor, existing editing and cancellation behavior is unchanged. The TUI rejects either field above 4,000 characters instead of truncating it. RPC keeps the existing sequential select and editor dialogs because Pi RPC cannot render custom TUI components. If you cancel, the agent should call plan_mode_question again or proceed only with a clearly stated low-risk assumption. The agent must never ask questions in assistant text; plain text is allowed only after plan_mode_question actually returned ui_unavailable. After a declined plan, only mode: "reproposal_confirmation" with canonical Re-propose now grants permission to propose again; ordinary questions never do.

Pi identifies tools by tool name. The pre-start selector stores accepted session policy names and shows each effective tool's source and availability from Pi metadata, including native built-in extension registrations. A selected name can run in Plan mode only when the effective tool is registered, available through its exposure, and permitted by policy at the workflow's first provider-bound context. The allowlist freezes at that boundary and restores without admitting new names after reload, resume, or tree navigation. A tool registered or first made available later waits for the next Plan workflow; an admitted callable tool later declared to the model keeps its existing admission. If an extension overrides a built-in tool with the same name, Pi exposes the effective tool for that name and the selector shows that source.

A complete Plan mode answer should appear only after the agent has resolved discoverable facts and high-impact user decisions. The agent must call plan_mode_complete({ plan }) alone as its final action, passing the complete Markdown plan. The tool rejects empty or whitespace-only plans and plans longer than 50,000 JavaScript characters; it does not truncate. Its visible result contains the full plan, and versioned result details let the extension restore it safely from the active session branch.

plan_mode_complete uses Pi's terminate: true hint. Termination is best effort: if a model puts it in a parallel tool batch, Pi terminates the batch early only when every finalized sibling tool also terminates. The prompt therefore requires the completion call to be standalone and last. The extension deliberately does not infer completion from phrases such as “I will present the plan,” and ordinary research or clarification turns never trigger automatic retries. /plan finalize and its exact canonical finalization prompt are explicit recovery requests. If one of those requests ends normally without a valid structured question or completed plan, Plan mode waits for agent_settled and retries once with stronger tool guidance. A valid question, accepted completion, user cancellation, explicit exit, workflow supersession, reload, session replacement, or shutdown cancels the retry. A second prose-only failure leaves Plan mode active, warns in interactive modes, and requires another explicit /plan finalize request.

Legacy sessions and models may still submit one non-empty <proposed_plan> block with tags on their own lines. That compatibility path remains accepted, but it is not the primary workflow. Empty, malformed, unclosed, or multiple legacy blocks keep Plan mode active and produce a warning.

After completion, /plan opens the ready actions when interactive UI is available. The same flat menu shows Implement here and Start fresh and implement, explains which conversation context each choice uses, and previews the selected Plan reinjection policy. Implement here—and the compatibility route /plan implement—appends the Normal contract, lifts the Plan runtime policy, captures the reinjection setting, and starts implementation in the current session with its complete planning conversation and tool calls. Start fresh and implement opens a settings page before replacement. Its searchable model list snapshots the session's scoped models when configured, otherwise Pi's currently available models, and shows each provider, model ID, and friendly name. Pi 0.80.6–0.82.1 does not expose session model scopes to extensions, so the list uses all currently available models on those releases. One-shot provider and model identifiers are limited to 512 characters each; longer custom identifiers are rejected before the source session is replaced. The model and thinking rows start from the persistent Fresh model and Fresh thinking defaults; when either setting is omitted, they show the planning session's current value with same as plan and carry it into the fresh session independently. The fixed thinking choices are off, minimal, low, medium, high, xhigh, and max. Back navigation preserves this menu-local draft, while closing and reopening the menu restores both persistent defaults. If the configured model is outside the current scope or no longer available, the row reports the fallback and uses same as plan without deleting the stored preference. The saved-plan menu keeps its direct fresh-session action without optional rows and applies the same persistent defaults and fallback.

Starting from that settings page waits for the source session to become idle, re-resolves and authenticates an explicit model, creates a new session linked to the persisted source as its parent, and transfers the exact approved plan without copying planning messages, tool results, or compaction/branch summaries. The destination consumes the one-shot choices before its first provider request, applies an explicit model first, and then applies explicit thinking. Concurrent prompts that arrive while those choices are being applied are queued as follow-ups behind the kickoff prompt. If durable consumption blocks the kickoff, the handoff reports a partial start and restores a conversation-history prompt to the editor instead of reporting success. Changing only the model keeps the planning thinking level; changing only the thinking level keeps the planning model. If Pi clamps an unsupported thinking level, the extension reports the effective level. If the chosen model disappears or loses authentication after source preflight, the destination reports the race, consumes the intent without retrying it later, and continues with its default model. The destination still loads its normal AGENTS.md, skills, project resources, and extensions. Choosing Export plan… asks for a destination, writes the plan, appends the Normal contract, restores inherited thinking, and leaves Plan mode without starting a model turn or changing active tools. Choosing Save for later—or running /plan save—instead stores one plan in the current Pi session before leaving Plan mode.

When a resumed active Plan workflow completes before /plan has run in that resumed session, the automatic menu cannot obtain Pi's command-only session replacement capability; choosing fresh asks you to reopen /plan, where the same action is available. A successful fresh handoff does not delete or consume the source planning session. Resume it later to inspect or hand off the ready/saved plan again; this deliberate duplication is the recovery path if the destination work is abandoned. In-memory sessions create an unlinked fresh session because no parent file exists. Escape, Ctrl+C, menu disposal, source replacement/shutdown, model/auth failure, or cancellation by another extension before replacement leaves the source plan unchanged. Under Off — conversation history only, the destination receives the complete plan in its initial user prompt and does not persist active-plan state. When a one-shot runtime choice exists, only that temporary non-model state is persisted and it is removed before the first request. If that removal cannot be persisted, the request is not sent and can be retried after session persistence is available. If that kickoff fails, the complete request remains in the destination editor and the source remains resumable. Under either guaranteed-plan policy, the destination persists active-plan state before kickoff. If guaranteed-plan persistence fails, the complete request is placed in the destination editor and the source remains resumable. If a guaranteed-plan kickoff fails, the destination retains the active plan; send a message to continue, use /plan exit to clear it, or resume the parent planning session.

A saved plan appears as plan saved and remains available after reload, resume, branch-local fork, and compaction in that session. It does not expire automatically, cross into a new session, or participate in ordinary model context. Open /plan to Show, Implement here, Start fresh and implement, Export, open Settings, or Clear it; /plan show, /plan implement, /plan export [path], and /plan exit/off retain their direct routes in TUI and RPC. Fresh implementation checks idle state, the selected model, and authentication before session replacement; Implement here keeps its established preflight behavior. Starting another workflow with /plan start, /plan <prompt>, or /plan tools is blocked until the saved plan is implemented or cleared, so the single saved slot is never silently overwritten. Resuming that session keeps the plan saved; open /plan to review, implement, export, or clear it. Cancellation or failed implementation preflight leaves it unchanged.

Text print and JSON modes cannot display the bare /plan menu and reject that route before changing state; use /plan start for direct no-prompt activation or /plan <prompt> to start planning with a prompt. /plan tools also rejects before changing state because its staged selector requires TUI or RPC. These modes can export any stored plan with /plan export [path], save a ready plan with /plan save, and clear it with /plan exit or /plan off. Successful export is observable through the created file; exporting a ready plan also leaves Plan mode, while saved and active implementation state remains unchanged. An existing target or missing plan fails the command without changing state. These modes reject saved-plan display and implementation before changing state because Pi provides neither printable custom-message output nor acknowledged extension-triggered turns; resume the session in TUI or RPC to show or implement it.

Both implementation paths apply the current Plan reinjection policy in their destination. The default Off — conversation history only policy does not create active-plan state or inject a hidden plan context. Implement here sends Implement the plan. and leaves the accepted plan in ordinary planning history. Start fresh and implement, or implementing a saved plan here, puts the complete plan in one ordinary initial user prompt because no reliable planning conversation is present. Later model calls then rely on Pi's normal conversation history and compaction behavior. Through first implementation run guarantees the exact plan throughout that run, including retries, compaction retries, and queued continuation, then clears active-plan state at agent_settled. Until manually cleared guarantees the exact plan across later turns, resume, and manual or automatic compaction until /plan exit or supersession. The guaranteed policies avoid a duplicate context block while the original implementation handoff remains available and inject one hidden canonical copy after that handoff is compacted away. Reinjection can consume up to the existing 50,000-character plan limit in model context. Cleanup is bound to the matching implementation, so an older run settling cannot clear a newer handoff.

While a guaranteed plan is active, /plan show displays the accepted plan. Interactive /plan offers Show, Export plan…, Settings, Start a new plan, and Clear; /plan exit and /plan off are the direct clear routes. Settings changes never alter the policy already captured by an active guaranteed-plan implementation. Automatic first-run cleanup removes the active status and future injected context after the triggering implementation run has received the complete plan. Starting a new Plan-mode workflow or implementing a replacement plan supersedes an active guaranteed plan. The extension deliberately does not infer completion from assistant prose or agent settlement under Until manually cleared, so clear the active plan when it no longer applies. Under Off — conversation history only, implementation messages remain ordinary conversation history and there is no active plan for /plan exit to remove. Choosing Stay before implementation keeps the plan ready. Revision feedback starts another Plan-mode turn and clears the previous implementable plan until an updated completion arrives. Any reply to a ready plan counts as a decline (see Declined plans); the agent can clarify normally, but a replacement requires explicit reproposal_confirmation permission instead of label-text inference. Before saving or implementation, exit/off discards the ready plan and removes its completion result from later non-Plan model context.

While Plan mode is enabled, the extension also publishes a compact status for Pi statuslines. With @narumitw/pi-statusline, this appears in the extension status area:

  • plan active: Plan mode is enabled and still gathering context or drafting a plan.
  • plan ready: A completed plan is stored until you implement it, export it, save it, continue planning, or exit Plan mode.
  • plan saved: One completed plan is stored outside model context in the current session until you implement or clear it.
  • plan implementing: The exact accepted plan is guaranteed under Through first implementation run or Until manually cleared.

You can also exit directly. Before implementation, direct exit discards the latest proposed plan; while a plan is saved, it clears that saved plan. During a guaranteed-plan implementation, it removes both the original implementation handoff and the extension's canonical active-plan block from later model calls; an earlier Pi-generated compaction summary may still describe prior work:

/plan exit

🧱 Cache-stable mode transitions

Plan and Normal requests share one append-only conversation. The extension appends one hidden, model-visible, versioned Plan contract before the first Plan prompt and one Normal contract before the first post-Plan Normal or implementation prompt. Ordinary linear turns do not rewrite or duplicate these contracts. The Plan contract explains native-tool opt-in and per-call nested enforcement; this wording changes future Plan transitions without rewriting existing transition messages. Implement here retains the Plan dialogue, structured questions, tool calls, completion evidence, and Implement the plan. kickoff in order. Start fresh and implement is the isolation path and transfers only the approved plan plus the Normal contract to a linked session.

The context hook filters repeated legacy plan-mode-context artifacts but preserves current transition messages. If compaction removes the effective transition, the hook inserts one canonical fallback at a deterministic retained-history boundary. Repeated transforms leave that fallback in place instead of moving it to the newest turn. An inactive legacy state entry does not inject a Normal contract, so sessions that never entered Plan mode keep their ordinary context after resume or reload. Manual /tree navigation restores branch-owned Plan state and chooses the matching contract without navigating or adding a branch summary. Pi lists hidden transition messages in /tree; Plan mode rejects those internal targets, so select an adjacent conversation entry.

Plan mode registers plan_mode_question and plan_mode_complete once and keeps their names and definitions stable across Normal, Plan, ready, implementation, and restored workflows. Visible helpers do not mean /plan is active, and their descriptions exclude ordinary planning, the writing-plans skill, roadmaps, checklists, and plan-file work. Only the latest active Plan contract authorizes the helpers; inactive or stale calls fail without accepting a plan or opening question UI. Plan mode does not widen a restrictive active-tool policy; start or restore fails when a required helper is unavailable. Stable schemas preserve a cache-eligible prefix but cannot guarantee a hit because provider serialization, cache lifetime, minimum prefix size, implementation details, and session affinity remain external.

The default thinkingLevel: "inherit" avoids a Plan-specific reasoning-parameter change. A fixed Plan thinking level remains supported, but changing reasoning parameters can prevent provider-side state reuse even when prompts and tool schemas stay stable.

🤝 Workflow coexistence

Plan mode is independently installable and keeps its standalone behavior when no other protocol participant is present. On the characterized Pi 0.84.2 runtime, it participates in the anonymous workflow:mutex:v1 agent-workflow group. It holds the group while Planning is active, while a completed plan awaits review, and while revision is underway. Saved plans and ordinary implementation after Plan handoff do not hold the group.

Every inactive start performs one final synchronous admission after asynchronous preflight and before changing Plan state, persistence, prompts, tools, thinking level, queues, or status. If another participant is active, TUI and RPC show an anonymous warning that another workflow is active. Print and JSON direct routes throw the same anonymous error before mutation.

Launch-menu, selected-tool, shortcut, active-implementation Start a new plan, and restored activation use the same admission boundary. A rejected selected-tool launch does not save its draft choices.

Restored active Plan state acquires before restoring restrictive tools, thinking, status, or model hooks. If restoration is busy, Plan mode stays non-running, leaves persisted history and active tools untouched, and requires a later reload or explicit new start after the other workflow ends. Planning-session cancellation during a fresh implementation preflight keeps the source Plan and its ownership. Successful session replacement relies on source-session shutdown to clean up and release; the destination's ordinary active implementation does not acquire the Plan mutex.

The coexistence guarantee is cooperative and applies only when every contender implements v1 on the characterized Pi runtime and shares its event bus and session-manager identity. A pre-v1, mixed-version, non-participating, forked, or otherwise uncharacterized counterpart remains unsupported for mutual exclusion. Plan mode does not identify, inspect, configure, start, stop, or depend on another extension. Guaranteed coexistence with Goal requires @narumitw/pi-goal 0.53.0 or newer and this package at 0.52.0 or newer on the characterized Pi 0.84.2 runtime.

Installation Support
Plan mode without another workflow participant Supported standalone behavior
Plan mode >=0.52.0 with Goal >=0.53.0 on Pi 0.84.2 Workflow Mutex v1 coexistence guarantee
Either package below its floor, or another Pi runtime Standalone behavior only; mutual exclusion unsupported

🛠️ Tools

  • plan_mode_question asks up to three structured questions, supports optional answer notes, submits one answer directly, and reviews multiple answers before TUI submission.
  • plan_mode_complete records the complete approved Markdown plan and terminates the planning turn when called alone.

⚙️ Settings

Run /plan settings, open Settings from an inactive /plan menu, or edit <getAgentDir()>/pi-plan-mode.json (normally ~/.pi/agent/pi-plan-mode.json). The optional file is read at session start and watched for changes; only an explicit save creates it.

{
  "thinkingLevel": "inherit",
  "implementationPlanRetention": "clear-on-start",
  "defaultImplementationModel": {
    "provider": "anthropic",
    "modelId": "claude-sonnet-4-5"
  },
  "defaultImplementationThinkingLevel": "high",
  "defaultPlanExportPath": "PLAN.md"
}

By default, Plan mode inherits thinking, allows active safe built-ins, uses the planning model and thinking level for fresh implementation, exports to PLAN.md, and relies on ordinary conversation history after implementation starts. The shortcut is disabled unless configured; enabling, changing, or removing it takes effect after /reload or restarting Pi. Until then, the current shortcut binding stays unchanged. Settings saves apply to later workflows; an active implementation keeps its captured reinjection policy. The export destination affects the next export immediately. Inactive built-in grep, find, and ls must be enabled in Pi settings; Plan policy only grants execution permission.

[!WARNING] safeSubcommands is a JSON-only trust override for the matched command segment, not a read-only allowlist. A matching prefix skips the argument checks for that one segment (e.g. gh pr view 1 --web); other segments, redirects, substitutions, and pipes into interpreters are still checked. trustedDirectories (JSON-only, global settings only) widens where cd and git -C may point. See docs/settings.md. Configure both only for commands and directories you fully trust.

Saves are ordered within one Pi process, preserve unknown fields, and publish atomically; separate Pi processes can still race. Invalid settings remain untouched and make Settings read-only; session-start failures use safe defaults.

Read the settings reference for all accepted values, tool-policy resolution, reinjection choices, shortcut configuration, shell-override examples, and legacy-file migration.

🧠 Codex-like behavior

This extension maps Codex's ModeKind::Plan behavior onto Pi's extension API:

  • Plan mode is conversational collaboration, not TODO or progress tracking.
  • /plan <prompt> enters Plan mode before submitting the prompt.
  • The agent uses plan_mode_question for material preferences and completes with a standalone plan_mode_complete call instead of prose detection.
  • update_plan is blocked until the explicit implementation boundary restores Normal mode.
  • The default clear-on-start policy uses conversation history; clear-after-first-run and keep add exact-plan guarantees.
  • Append-only Plan and Normal contracts keep helper schemas stable, but Pi's tool policy is risk reduction rather than Codex sandbox enforcement.

🗂️ Package layout

pi-plan-mode/
├── src/                               # Policy, questions, settings, and handoff modules
│   ├── index.ts                       # Thin Pi entrypoint
│   └── plan-mode.ts                   # Planning policy and implementation handoff
├── dist/                              # Generated Jiti runtime
├── scripts/build-runtime.mjs          # Runtime builder
├── docs/                              # Published reference documentation
└── test/                              # Behavior and lifecycle coverage

The generated runtime is built from src/index.ts and does not import back into src.

test/{confirmation,decline,policy,transcript}-session.test.ts ports the seven retired process scenarios through the real core session and interactive harnesses. File-backed restore, canonical RPC select choices, all seven allowed/eleven blocked shell rows, and raw terminal clicks/Escape run without Pi binaries, owner configuration, polling, or sleeps. Only the Bash executor is inert; the production factory and admission hook run unchanged. npm run check includes these scenarios on Bun (verified with 1.3.14); npm test runs the full Vitest suite. The release-tarball development host provides /testing and /testing/interactive; @xterm/headless@5.5.0 supplies its terminal emulator. Peer dependencies and the shipped runtime are unchanged.

Fork maintenance follows the split/diff-replay queue, never unreleased upstream main. Exclude dist/ from replay and regenerate the standalone lockfile for the release's development pins. Run npm run check && npm test locally; the typecheck covers source and test files. Test fixtures resolve relative to the package's source location rather than the runner's working directory, so both gate layouts exercise the same files. Also apply the queue in a full upstream monorepo clone at the release tag, run npm ci --ignore-scripts, build packages/pi-tui-kit, run the Plan package's npm run check, then run root PI_EXTENSIONS_TEST_BASE='@narumitw/pi-plan-mode@0.59.3' npm test. Rebuild the Plan package there after the final source change, copy its dist/ back here, and commit those artifacts last. The vendored harness tracks that release, including the mock UI's onTerminalInput method.

🔎 Keywords

Pi extension, Pi coding agent, plan mode, Codex-like plan mode, AI coding workflow, read-only planning, implementation plan.

Lockfile: there is no nested package-lock.json; the root workspace lock governs (npm ci --ignore-scripts at the repository root).

📄 License

MIT. See LICENSE.