You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Paseo currently treats "plan" as transient chat content. There is no dedicated surface for the plan an agent is executing, no way to watch it evolve over time, and no diff view when the agent revises it. This proposal makes plan a first-class entity alongside subagents (#532) and todos.
Why now
Two converging signals make this the right moment:
Claude Code Plan Mode shifted to file-based storage in v2.0.51+.ExitPlanMode now fires with an empty input field — the actual plan body is written to a markdown file in a plans/ folder. Listening to the SDK stream alone is no longer enough to recover plan content. Refs:
File-based planning skills are now the dominant pattern.planning-with-files (Manus-style task_plan.md + findings.md + progress.md), spec-kit (.specify/spec.md, plan.md, tasks.md), and similar workflows write plans to disk during execution. None of the major Claude Code clients (Happy, Claudia, Cline, Aider) render these as a dedicated, live-updating view — they are buried inline in the chat stream.
This is a clean market gap and a natural extension of the same surface area #532 is opening up for subagents.
Today in Paseo
The only plan-related issues/PRs to date are bug-fixes against existing in-stream plan rendering:
There is no model of "plan" as an entity, no persistence, no diffs, no separate view.
Proposed model
Add a Plan entity to the agent state, with the following minimal shape:
typePlan={id: string;agentId: string;source: "claude-plan-mode"|"file"|"tool-output";filePath?: string;// when source is file or claude-plan-mode (v2.0.51+)body: string;// current rendered markdownstatus: "draft"|"approved"|"executing"|"completed"|"abandoned";createdAt: number;updatedAt: number;revisions: Array<{at: number;body: string}>;// bounded ring buffer};
A plan is created when any of the following triggers fire, and updated thereafter:
Trigger
Provider
Source
tool_use.name === "ExitPlanMode" with non-empty input.plan
Claude (≤2.0.34)
tool-output
tool_use.name === "ExitPlanMode" with empty input
Claude (2.0.51+)
resolve via file watcher on agent worktree plans/*.md
File created/changed at conventional paths
any
file (paths listed below)
Codex / OpenCode plan tool equivalents
Codex / OpenCode
tool-output
File watcher conventions (configurable, with sensible defaults):
The watcher lives in packages/server and pushes plan deltas to clients over the existing WebSocket protocol as a new plan.updated event (additive, optional fields, backward compatible per the WS schema rules).
UI
Where these surfaces live in the existing layout
Paseo's agent screen (packages/app/src/panels/agent-panel.tsx) is structured as:
Surface 1 (the plan strip) is a new sibling to the subagent strip from #532 — both live in a vertical stack between AgentStreamView and AgentComposerSection. Concretely:
AgentStreamView ← scrollable, flex: 1
─────────────────────────
PlanStrip (new) ← only present if a plan exists
SubagentStrip (#532) ← only present if subagents exist
─────────────────────────
AgentComposerSection ← fixed height, anchored bottom
Order matters: plan above subagents because plan is the "what we are doing" context, subagents are the "who is doing it" — plan reads like a header for the subagent list. Both strips collapse to zero height when empty, so the composer stays visually anchored to the stream when there is nothing to show.
Surface 2 (the plan detail view) opens differently per form factor:
Form factor
How it opens
Why
Compact (phone, narrow web)
Full-screen modal pushed onto the navigation stack
Composer + stream already saturate the viewport; a side panel would crush both
Wide (tablet, desktop, Electron)
New workspace tab registered in panel-registry, opened as a sibling pane
Reuses the existing pane infrastructure (workspace-pane-content.tsx / panel-registry). User can then split-pane it next to the agent or swap it in the same pane. No new "side drawer" primitive needed.
This means on wide form factor, opening a plan does not cover the agent panel — the user can have the agent stream + plan detail side by side via the existing pane split. On compact, it's a modal because that's how every other detail surface in Paseo behaves.
A new panel kind plan-detail is registered in register-panels.ts, with target { kind: "plan-detail", agentId, planId }. Tapping the strip calls onOpenTab({ kind: "plan-detail", ... }), which the workspace already knows how to route.
Surface 1 — Plan strip
A persistent strip rendered between AgentStreamView and AgentComposerSection, sibling to the subagent strip from #532. When no plan exists for the current agent, the strip is absent (zero chrome cost).
Layout (single row, ~36–40px tall):
┌─────────────────────────────────────────────────────────────────┐
│ ▸ ● Migrate auth to OAuth2 3/7 steps · 2m ago ⋯ │
└─────────────────────────────────────────────────────────────────┘
↑ ↑ ↑ ↑ ↑ ↑
│ │ plan title (first H1 / │ │ overflow
│ │ first line, truncated) │ timestamp menu
│ │ │ of last
│ status dot (color-coded) step counter update
expand/collapse chevron (parsed from
`- [ ]` / `- [x]`)
Status dot colors: gray = draft, blue = executing, green = completed, amber = abandoned. Pulses while a plan.updated event is in-flight (<500ms after a file change).
Tap title or chevron → opens Surface 2 (detail view).
Overflow menu (⋯): Open file in editor, Copy plan, Mark abandoned (read-only safe ops; no plan editing).
Multiple plans (e.g. spec-kit's three files): the strip becomes a horizontal pager with a 1/3 indicator; swipe (mobile) or arrow keys (web) cycle. We do not stack multiple strips — vertical real estate near the composer is precious.
Surface 2 — Plan detail view
Opens as a side pane on wide form factor (≥768px, via the existing pane split) and a full-screen modal on compact. Reuses useIsCompactFormFactor() per CLAUDE.md gating rules.
Current — full markdown render of the latest plan body.
Checkbox items (- [ ] / - [x]) rendered as visual checkboxes (read-only — checking is a no-op with a tooltip "Plan is read-only in Paseo").
Headings get anchor links so deep-linking to a section works.
Live-updates in place when plan.updated fires; the changed region briefly highlights (1.5s yellow fade) so the user can see what just moved.
Diff — previous revision vs current.
Reuses the existing diff gutter component.
Default comparison: revisions[N-1] vs current. A dropdown lets the user pick any earlier revision as the base.
Empty state when only one revision exists: "No prior revision yet — the plan will be diffed against its previous version after the next update."
History — vertical timeline of revisions.
Each entry: timestamp, first-line summary, +N/−M line counts, tap → loads that revision into the Diff tab as the "right side" with current as the base.
Bounded to the last 20 revisions in-memory (per the entity definition); older ones collapse into "… N earlier revisions discarded".
Empty / loading / error states
State
Strip
Detail view
No plan detected
Strip hidden
N/A
File watcher just started, no event yet
Hidden until first event
N/A
Plan file deleted while open
Strip dims to 50% opacity, shows "Plan file removed"
Banner: "The underlying plan file no longer exists. Showing last known content."
Web/desktop: detail view opens as a new pane in the existing workspace pane system; ESC closes; remembers last open pane width in user settings.
Hover affordances (overflow menu, revision summary preview): isHovered || isNative || isCompact per CLAUDE.md, so they're always visible on touch.
Diff/Current tab content uses existing markdown + diff renderers — no new rendering pipeline.
Out of scope for v1
Editing plan content (read-only is a hard constraint for v1).
Side-by-side diff (top/bottom split only initially; side-by-side is a follow-up).
Cross-agent plan comparison ("how does this plan differ from the one in agent X?").
Plan templates / library.
Phasing
Phase 1 — entity + file watcher. Wire up the Plan entity, the daemon-side file watcher on conventional paths, and the WS event. No UI yet; verify via CLI (paseo plan show).
Phase 2 — Claude ExitPlanMode resolution. Handle both the legacy input.plan path and the v2.0.51+ file-resolved path. Add a deterministic test for both.
Should plan revisions be persisted across daemon restarts, or only kept in-memory? (Lean: in-memory + on-disk file is the source of truth.)
How do we handle two plans in the same agent worktree (e.g. spec-kit's three files)? Treat as one logical plan with sub-documents, or separate Plan entities? (Lean: separate, with optional grouping.)
Backward compat for older clients: plan.updated is additive, but should the plan body also continue to appear inline so 6-month-old clients still see it? (Lean: yes, no regression on the inline path.)
Is there a Codex equivalent of ExitPlanMode we can latch onto, or is it purely file-based there?
Summary
Paseo currently treats "plan" as transient chat content. There is no dedicated surface for the plan an agent is executing, no way to watch it evolve over time, and no diff view when the agent revises it. This proposal makes plan a first-class entity alongside subagents (#532) and todos.
Why now
Two converging signals make this the right moment:
ExitPlanModenow fires with an emptyinputfield — the actual plan body is written to a markdown file in aplans/folder. Listening to the SDK stream alone is no longer enough to recover plan content. Refs:planning-with-files(Manus-styletask_plan.md+findings.md+progress.md),spec-kit(.specify/spec.md,plan.md,tasks.md), and similar workflows write plans to disk during execution. None of the major Claude Code clients (Happy, Claudia, Cline, Aider) render these as a dedicated, live-updating view — they are buried inline in the chat stream.This is a clean market gap and a natural extension of the same surface area #532 is opening up for subagents.
Today in Paseo
The only plan-related issues/PRs to date are bug-fixes against existing in-stream plan rendering:
opusplanmodel mode #517 — request for ClaudeopusplanmodeThere is no model of "plan" as an entity, no persistence, no diffs, no separate view.
Proposed model
Add a
Planentity to the agent state, with the following minimal shape:A plan is created when any of the following triggers fire, and updated thereafter:
tool_use.name === "ExitPlanMode"with non-emptyinput.plantool-outputtool_use.name === "ExitPlanMode"with empty inputplans/*.mdfile(paths listed below)tool-outputFile watcher conventions (configurable, with sensible defaults):
plans/*.md(Claude Code 2.0.51+)task_plan.md,findings.md,progress.md(planning-with-files).specify/spec.md,.specify/plan.md,.specify/tasks.md(spec-kit)PLAN.md,TASK_PLAN.md(legacy / common ad-hoc)The watcher lives in
packages/serverand pushes plan deltas to clients over the existing WebSocket protocol as a newplan.updatedevent (additive, optional fields, backward compatible per the WS schema rules).UI
Where these surfaces live in the existing layout
Paseo's agent screen (
packages/app/src/panels/agent-panel.tsx) is structured as:Surface 1 (the plan strip) is a new sibling to the subagent strip from #532 — both live in a vertical stack between
AgentStreamViewandAgentComposerSection. Concretely:Order matters: plan above subagents because plan is the "what we are doing" context, subagents are the "who is doing it" — plan reads like a header for the subagent list. Both strips collapse to zero height when empty, so the composer stays visually anchored to the stream when there is nothing to show.
Surface 2 (the plan detail view) opens differently per form factor:
panel-registry, opened as a sibling paneworkspace-pane-content.tsx/panel-registry). User can then split-pane it next to the agent or swap it in the same pane. No new "side drawer" primitive needed.This means on wide form factor, opening a plan does not cover the agent panel — the user can have the agent stream + plan detail side by side via the existing pane split. On compact, it's a modal because that's how every other detail surface in Paseo behaves.
A new panel kind
plan-detailis registered inregister-panels.ts, with target{ kind: "plan-detail", agentId, planId }. Tapping the strip callsonOpenTab({ kind: "plan-detail", ... }), which the workspace already knows how to route.Surface 1 — Plan strip
A persistent strip rendered between
AgentStreamViewandAgentComposerSection, sibling to the subagent strip from #532. When no plan exists for the current agent, the strip is absent (zero chrome cost).Layout (single row, ~36–40px tall):
draft, blue =executing, green =completed, amber =abandoned. Pulses while aplan.updatedevent is in-flight (<500ms after a file change).Open file in editor,Copy plan,Mark abandoned(read-only safe ops; no plan editing).1/3indicator; swipe (mobile) or arrow keys (web) cycle. We do not stack multiple strips — vertical real estate near the composer is precious.Surface 2 — Plan detail view
Opens as a side pane on wide form factor (≥768px, via the existing pane split) and a full-screen modal on compact. Reuses
useIsCompactFormFactor()per CLAUDE.md gating rules.Header bar:
Three tabs:
Current — full markdown render of the latest plan body.
- [ ]/- [x]) rendered as visual checkboxes (read-only — checking is a no-op with a tooltip "Plan is read-only in Paseo").plan.updatedfires; the changed region briefly highlights (1.5s yellow fade) so the user can see what just moved.Diff — previous revision vs current.
revisions[N-1]vs current. A dropdown lets the user pick any earlier revision as the base.History — vertical timeline of revisions.
Empty / loading / error states
Cross-platform notes
isHovered || isNative || isCompactper CLAUDE.md, so they're always visible on touch.Out of scope for v1
Phasing
Planentity, the daemon-side file watcher on conventional paths, and the WS event. No UI yet; verify via CLI (paseo plan show).ExitPlanModeresolution. Handle both the legacyinput.planpath and the v2.0.51+ file-resolved path. Add a deterministic test for both.Each phase ships independently behind a flag and is reverted-without-regret if it doesn't pan out.
Non-goals
Open questions
Planentities? (Lean: separate, with optional grouping.)plan.updatedis additive, but should the plan body also continue to appear inline so 6-month-old clients still see it? (Lean: yes, no regression on the inline path.)ExitPlanModewe can latch onto, or is it purely file-based there?Related
opusplanmodel mode #517, fix(server): preserve Codex fast mode after plan approval #526