feat: model switching and plan mode (Steps 7-8)#4
Conversation
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add plan mode toggle (/plan, /plan off) that denies mutating tools (write_file, edit_file, bash) via isToolDenied callback, appends a planner-oriented system prompt, and prompts the user to approve, reject, or modify plans before execution. Updates /status to show plan mode state and changes prompt to [plan] > when active.
- Sync repl-chat-loop delta: add plan mode toggle commands and scenarios - Sync tool-permissions delta: add isToolDenied callback requirement - Add plan-mode as new main spec - Archive step-8-plan-mode change to openspec/changes/archive/
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughThis PR implements "plan mode," a new REPL operational state where the agent acts as an architect to generate ordered action plans before execution. The agent loop gains tool-denial capability via an Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant REPL
participant AgentLoop as Agent Loop
participant ToolExecutor as Tool Executor
User->>REPL: /plan (or message in plan mode)
REPL->>REPL: Set planMode = true<br/>Augment system prompt<br/>Set up isToolDenied callback
REPL->>AgentLoop: runAgentLoop(options with isToolDenied)
Note over AgentLoop: Model generates plan<br/>or attempts tool call
alt Tool called in plan mode
AgentLoop->>AgentLoop: isToolDenied(toolName) → true
AgentLoop->>ToolExecutor: ✗ Deny (plan mode)
ToolExecutor-->>AgentLoop: Error result
else Read-only tool
AgentLoop->>AgentLoop: isToolDenied(toolName) → false
AgentLoop->>ToolExecutor: Execute read-only tool
ToolExecutor-->>AgentLoop: Tool result
end
AgentLoop-->>REPL: Text response (plan)
REPL->>User: Display plan<br/>Prompt for approval
alt User approves (y)
User->>REPL: y
REPL->>REPL: planMode = false<br/>Clear tool denial<br/>Append execution instruction
REPL->>AgentLoop: runAgentLoop(normal mode)
AgentLoop->>ToolExecutor: Execute approved plan
ToolExecutor-->>AgentLoop: Results
AgentLoop-->>REPL: Completion
else User rejects/modifies
User->>REPL: n or feedback text
REPL->>REPL: planMode = true (stay)<br/>Append feedback as user msg
REPL->>AgentLoop: runAgentLoop(plan mode)<br/>with revised context
AgentLoop-->>REPL: Revised plan
REPL->>User: Display revised plan
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
1 issue found across 21 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/repl.ts">
<violation number="1" location="src/repl.ts:222">
P2: Approving a plan does not continue execution immediately; it only queues a message and waits for another user input before implementation starts.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c881bdc25b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| messages.push({ | ||
| role: "user", | ||
| content: [{ type: "text", text: "Plan approved. Please proceed with implementation." }], | ||
| }); | ||
| tokenTracker.addMessage(); |
There was a problem hiding this comment.
Re-run agent immediately after plan approval input
In startRepl, the plan-mode approval handler only appends a synthetic user message (messages.push(...)) and then falls back to the outer REPL loop without calling runAgentLoop again. This means y does not actually trigger execution of the approved plan, and free-text feedback does not trigger an immediate revised plan; both require an extra user prompt, which breaks the intended approval/revision flow.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
Implements ROADMAP Step 7–8 features for the interactive REPL: switching LLM models mid-session and introducing a read-only “plan mode” that blocks mutating tools and adds a post-response approval step.
Changes:
- Add
/modelcommand support (show current / switch to new model) and expose model state via REPL callbacks. - Add plan mode (
/plan,/plan off) with prompt indicator, planner system-prompt appendix, and mutating-tool denial via a newisToolDeniedagent-loop option. - Add unit tests plus OpenSpec/docs/roadmap updates to capture the new workflows.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/repl/commands.ts | Adds /plan + /plan off handlers and extends /status output with plan-mode state. |
| src/repl.ts | Introduces planMode state, dynamic prompt, planner system-prompt appendix, tool-denial wiring, and plan approval prompt. |
| src/agent.ts | Adds isToolDenied callback to override tool permissions before normal permission flow. |
| src/tests/step7-spec-anchors.test.ts | Updates command option mocks to include plan-mode callbacks. |
| src/tests/model-switching.test.ts | Updates command option mocks to include plan-mode callbacks. |
| src/tests/commands-plan.test.ts | Adds tests for /plan, /plan off, and /status plan-mode display behavior. |
| src/tests/agent-plan-mode.test.ts | Adds tests verifying isToolDenied blocks tools and runs before permission checks. |
| openspec/specs/tool-permissions/spec.md | Specifies isToolDenied override semantics and ordering. |
| openspec/specs/repl-chat-loop/spec.md | Extends REPL spec to include plan-mode commands, prompt indicator, and status output. |
| openspec/specs/plan-mode/spec.md | Adds the full plan-mode behavior spec (toggle, denial, prompt appendix, approval flow). |
| openspec/changes/archive/2026-04-18-step-8-plan-mode/tasks.md | Archives the step’s task checklist. |
| openspec/changes/archive/2026-04-18-step-8-plan-mode/specs/tool-permissions/spec.md | Archived spec delta for tool-permissions changes. |
| openspec/changes/archive/2026-04-18-step-8-plan-mode/specs/repl-chat-loop/spec.md | Archived spec delta for repl-chat-loop changes. |
| openspec/changes/archive/2026-04-18-step-8-plan-mode/specs/plan-mode/spec.md | Archived plan-mode spec addition. |
| openspec/changes/archive/2026-04-18-step-8-plan-mode/proposal.md | Archives the plan-mode proposal rationale/scope. |
| openspec/changes/archive/2026-04-18-step-8-plan-mode/design.md | Archives the design decisions and integration points. |
| openspec/changes/archive/2026-04-18-step-8-plan-mode/.openspec.yaml | Adds OpenSpec archive metadata for the change set. |
| docs/SOCRATIC_JOURNAL.md | Documents architectural reasoning for plan mode and isToolDenied. |
| docs/MANUAL_TESTING.md | Adds manual test procedures and updates REPL command list. |
| docs/FOR-Rob-Simpson.md | Adds narrative/architecture write-up for Step 8 plan mode. |
| ROADMAP.md | Marks Step 8 plan-mode items complete. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (planMode) { | ||
| const approval = await rl.question( | ||
| chalk.yellow("\n📋 Approve this plan? (y to approve / n to reject / or type modifications): "), | ||
| ); | ||
| const approvalTrimmed = approval.trim().toLowerCase(); | ||
|
|
||
| if (approvalTrimmed === "y") { | ||
| planMode = false; | ||
| messages.push({ | ||
| role: "user", | ||
| content: [{ type: "text", text: "Plan approved. Please proceed with implementation." }], | ||
| }); | ||
| tokenTracker.addMessage(); | ||
| shouldPersistSession = true; | ||
| } else if (approvalTrimmed === "n") { | ||
| messages.push({ | ||
| role: "user", | ||
| content: [{ type: "text", text: "Plan rejected. Please revise the plan." }], | ||
| }); | ||
| tokenTracker.addMessage(); | ||
| shouldPersistSession = true; | ||
| } else { | ||
| messages.push({ | ||
| role: "user", | ||
| content: [{ type: "text", text: `Plan feedback: ${approval.trim()}` }], | ||
| }); | ||
| tokenTracker.addMessage(); | ||
| shouldPersistSession = true; | ||
| } |
There was a problem hiding this comment.
After the plan approval prompt, the code only appends a user message (e.g. "Plan approved. Please proceed with implementation.") but does not re-enter runAgentLoop automatically. This means approving a plan won't actually execute anything until the user types another input line, which contradicts the spec/docs that say implementation should begin immediately after y. Consider looping back into runAgentLoop right after recording the approval (or treating the approval as the next user input and skipping the next rl.question).
| promptForApproval, | ||
| tokenTracker, | ||
| isToolDenied: planMode | ||
| ? (toolName: string) => MUTATING_TOOLS.has(toolName) |
There was a problem hiding this comment.
Plan mode currently denies only direct tool calls to write_file, edit_file, and bash. Because subagent is still allowed and subagents run runAgentLoop without promptForApproval (and without an isToolDenied hook), approving a subagent tool call during plan mode can still mutate files or run bash, bypassing the read-only guarantee. Consider propagating the plan-mode denial callback into subagent execution (or denying subagent while plan mode is active) so plan mode cannot be circumvented via subagents.
| ? (toolName: string) => MUTATING_TOOLS.has(toolName) | |
| ? (toolName: string) => toolName === "subagent" || MUTATING_TOOLS.has(toolName) |
…back Approval now triggers execution right away instead of waiting for the next user input. Rejection and feedback re-run in plan mode to produce a revised plan immediately.
|
Thanks for the reviews! Fixed in Re: the subagent bypass suggestion — not applicable to this codebase. There is no |
Summary
Implements the remaining Step 7 and all Step 8 items from ROADMAP.md:
Model Switching (Step 7)
/model <id>command to switch models mid-session/modelwith no args shows current model.ai-agent/config.jsonPlan Mode (Step 8)
/planactivates read-only plan mode — mutating tools are denied viaisToolDeniedcallback/plan offdeactivates — full tool access restored[plan] >prompt indicator when activeChanges
src/repl.ts— plan mode state, dynamic prompt, tool denial, approval flowsrc/agent.ts—isToolDeniedcallback inAgentLoopOptionssrc/repl/commands.ts—/modeland/plancommand handlersSummary by cubic
Adds model switching and a new Plan Mode to the REPL. You can switch models mid-session and safely plan changes with mutating tools disabled; plan approval now executes immediately, and rejection/edits re-run planning right away.
New Features
/model <id>to switch;/modelshows current; default via.ai-agent/config.json./planon,/plan offoff; prompt shows[plan] >;/statusdisplays mode.write_file,edit_file, andbashvia newisToolDeniedcallback.y(exits Plan Mode and executes immediately), reject withn, or type edits to revise; rejection/edits re-run planning immediately.isToolDeniedtoAgentLoopOptions, checked before permission flow.Bug Fixes
Written for commit 52753ac. Summary will update on new commits.
Summary by CodeRabbit
Release Notes
New Features
/planand/plan offslash commands to enable read-only planning without code mutations./statusoutput.Documentation