Add plan mode to the chat agent #638

Closed
vdmkenny wants to merge 1 commit from vdmkenny/plan-mode into main
vdmkenny commented 2026-06-01 18:36:39 +02:00 (Migrated from github.com)

Summary

Adds a plan mode to the agent: it investigates read-only, proposes a plan as a checklist, and waits for approval before changing anything. On approval it runs with full tools and checks items off as it goes.

Enforcement reuses the existing disabled_tools gate (already applied at both system-prompt/schema build and the runtime choke point in execute_tool_block), so restricting the tool set is the whole mechanism.

How it works

  • Allowlist (src/tool_security.py): only read-only/inspection tools stay enabled; everything else is blocked. Allowlist not blocklist → new tools default to blocked; fails closed if schemas can't load.
  • MCP (src/mcp_manager.py): read-only MCP tools stay usable; write/unknown ones blocked, classified via readOnlyHint/destructiveHint with a tool-name verb fallback (fail closed).
  • bash/python stay usable for inspection but can't be gated at the tool layer, so the plan-mode directive forbids shell writes hard (no >/touch/rm/installs/…). This is a prompt boundary, not a hard guarantee — every other write path is hard-blocked.
  • Agent loop (src/agent_loop.py): plan_mode unions the disabled set, filters MCP to read-only (schema + runtime), and prepends the directive so it dominates the action-oriented base prompt. The plan must be a - [ ] checklist.
  • Route (routes/chat_routes.py): parses plan_mode, forces agent mode, unions the read-only set.

UI

  • Plan toggle pill next to the web/shell toggles, gated to agent mode, off by default (reuses the existing per-mode toggle framework).
  • A plan turn gets Approve & Run (executes with full tools; instructs the agent to work the checklist and re-emit - [x] per step) and Open in window — a draggable, side-dockable window reusing makeWindowDraggable.
  • - [ ]/- [x] task lists render as checkboxes.

Files

src/tool_security.py, src/mcp_manager.py, src/agent_loop.py, routes/chat_routes.py, static/index.html, static/app.js, static/js/chat.js, static/js/planWindow.js, static/js/markdown.js, static/style.css, tests/test_plan_mode.py.

Testing

  • python -m pytest tests/test_plan_mode.py — passes (allowlist, mutating-tool blocking incl. XML-only tools, fail-closed fallback, MCP read/write classification).
  • python -m py_compile on changed Python; node --check on changed JS — pass.
  • Manual: enable Plan in agent mode, send a multi-step task → agent returns a - [ ] checklist and makes no changes; Approve & Run executes and checks items off; Open in window shows a dockable plan window. Task-list checkbox rendering verified in-browser.

When requesting something in plan mode:
Screenshot 2026-06-01 at 18 50 13
When opening the full plan window:
Screenshot 2026-06-01 at 18 50 25
After accepting the plan:
Screenshot 2026-06-01 at 18 50 50

## Summary Adds a **plan mode** to the agent: it investigates read-only, proposes a plan as a checklist, and waits for approval before changing anything. On approval it runs with full tools and checks items off as it goes. Enforcement reuses the existing `disabled_tools` gate (already applied at both system-prompt/schema build and the runtime choke point in `execute_tool_block`), so restricting the tool set is the whole mechanism. ## How it works - **Allowlist** (`src/tool_security.py`): only read-only/inspection tools stay enabled; everything else is blocked. Allowlist not blocklist → new tools default to blocked; fails closed if schemas can't load. - **MCP** (`src/mcp_manager.py`): read-only MCP tools stay usable; write/unknown ones blocked, classified via `readOnlyHint`/`destructiveHint` with a tool-name verb fallback (fail closed). - **bash/python** stay usable for inspection but can't be gated at the tool layer, so the plan-mode directive forbids shell writes hard (no `>`/`touch`/`rm`/installs/…). This is a prompt boundary, not a hard guarantee — every other write path is hard-blocked. - **Agent loop** (`src/agent_loop.py`): `plan_mode` unions the disabled set, filters MCP to read-only (schema + runtime), and prepends the directive so it dominates the action-oriented base prompt. The plan must be a `- [ ]` checklist. - **Route** (`routes/chat_routes.py`): parses `plan_mode`, forces agent mode, unions the read-only set. ## UI - Plan toggle pill next to the web/shell toggles, gated to agent mode, off by default (reuses the existing per-mode toggle framework). - A plan turn gets **Approve & Run** (executes with full tools; instructs the agent to work the checklist and re-emit `- [x]` per step) and **Open in window** — a draggable, side-dockable window reusing `makeWindowDraggable`. - `- [ ]`/`- [x]` task lists render as checkboxes. ## Files `src/tool_security.py`, `src/mcp_manager.py`, `src/agent_loop.py`, `routes/chat_routes.py`, `static/index.html`, `static/app.js`, `static/js/chat.js`, `static/js/planWindow.js`, `static/js/markdown.js`, `static/style.css`, `tests/test_plan_mode.py`. ## Testing - `python -m pytest tests/test_plan_mode.py` — passes (allowlist, mutating-tool blocking incl. XML-only tools, fail-closed fallback, MCP read/write classification). - `python -m py_compile` on changed Python; `node --check` on changed JS — pass. - Manual: enable Plan in agent mode, send a multi-step task → agent returns a `- [ ]` checklist and makes no changes; Approve & Run executes and checks items off; Open in window shows a dockable plan window. Task-list checkbox rendering verified in-browser. When requesting something in plan mode: <img width="731" height="408" alt="Screenshot 2026-06-01 at 18 50 13" src="https://github.com/user-attachments/assets/7fbf1316-b842-4e21-b45f-eb986b406f0a" /> When opening the full plan window: <img width="433" height="310" alt="Screenshot 2026-06-01 at 18 50 25" src="https://github.com/user-attachments/assets/2ce5a58d-e7bd-4038-87bc-02c0fc343064" /> After accepting the plan: <img width="706" height="879" alt="Screenshot 2026-06-01 at 18 50 50" src="https://github.com/user-attachments/assets/6b592179-efb2-430f-9519-bac6f0df10c3" />
sleepy closed this pull request 2026-06-01 19:44:56 +02:00

Pull request closed

Sign in to join this conversation.
No description provided.