History

Rewrite plan-mode deep dive as implementation blueprint

- Reframe doc into a provider-agnostic implementation plan for Codex, Claude, and Cursor - Define canonical mode, plan, and structured user-input models plus capability-driven UI behavior - Add concrete orchestration, adapter, frontend, and test work items for rollout

Julius Marminge committed 8 months ago

Commit df6bb181 parent 03053d91 file · +460 −441Browse files

Unified · Split

docs/plan-mode-deep-dive.md+460−441
@@ -1,629 +1,648 @@
1# Plan Mode Deep Dive1# Plan Mode Implementation Plan
2 2
3## Goal3## Goal
4 4
5Understand how the three providers expose:5Implement plan mode in a provider-agnostic way across:
6
7- planning / read-only execution modes
8- structured plan updates
9- structured user-input / question prompts
10- response plumbing needed for a provider-agnostic adapter layer
11
12Providers covered:
13 6
14- Codex App Server7- Codex App Server
15- Claude Agent SDK / Claude Code8- Claude Agent SDK / Claude Code
16- Cursor ACP9- Cursor ACP
17 10
18---11The implementation must support:
19
20## Existing T3 Code groundwork
21
22Current repo groundwork already exists in these places:
23
24- `EVENTS.md`
25- `packages/contracts/src/providerRuntime.ts`
26- `apps/server/src/provider/Layers/CodexAdapter.ts`
27- `apps/server/src/provider/Layers/ClaudeCodeAdapter.ts`
28- `apps/server/src/provider/Layers/CursorAdapter.ts`
29- `apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts`
30
31Canonical runtime events already modeled:
32 12
33- `turn.plan.updated`13- provider-native or adapter-derived plan mode state
34- `content.delta` with `streamKind: "plan_text"`14- structured user-input question flows where available
35- `user-input.requested`15- graceful fallback when a provider lacks structured prompts or structured plan updates
36- `user-input.resolved`16- a front-end UX that renders from capabilities and canonical events rather than provider-specific conditionals
37- `request.opened`
38- `request.resolved`
39 17
40This is a good base, but provider coverage is uneven:18This document now serves as both:
41 19
42- Codex: strong native support for both structured plans and structured user questions.20- the architecture plan for shared orchestration and UI wiring
43- Claude: native plan mode exists, but plan updates are mostly implicit via tool/message stream; user questioning appears tool-based rather than a dedicated runtime event.21- the provider adapter implementation plan for each supported agent runtime
44- Cursor ACP: native plan mode exists, but current ACP surface appears to expose neither structured plan updates nor structured ask-user prompts in the same way as Codex.
45 22
46---23---
47 24
48## Provider 1: Codex App Server25## Source of truth
49
50### What exists natively
51
52Codex has the richest first-class support for this feature set.
53
54### Real local session evidence
55 26
56The local Codex rollout file confirms at least one important distinction:27### Codex
57 28
58- session metadata explicitly records collaboration mode as plan via `turn_context.payload.collaboration_mode.mode = "plan"`29Codex protocol decisions should be based on the open-source app-server protocol, not on local rollout session files.
59- final plan handoff is emitted as a normal assistant message containing a `<proposed_plan>` block
60 30
61From the sampled rollout file, I did **not** find raw `event_msg.plan_update` / `event_msg.plan_delta` records, which suggests either:31Upstream Codex app-server explicitly supports:
62 32
63- this particular session never emitted incremental structured plan updates, or
64- the desktop rollout file persists a higher-level message projection rather than every low-level app-server event
65
66That means the app-server protocol still supports native structured plan events, but local rollout files should not be assumed to contain every transport-level plan artifact.
67
68
69Native surfaces confirmed from upstream docs/source:
70
71- collaboration mode with plan behavior
72- `turn/plan/updated`33- `turn/plan/updated`
73- `item/plan/delta`34- `item/plan/delta`
74- `item/tool/requestUserInput`35- `item/tool/requestUserInput`
75- `serverRequest/resolved` cleanup / completion notification for user input36- `serverRequest/resolved`
76- `request_user_input` tool in core/runtime37- lower-level `EventMsg` variants `plan_update`, `plan_delta`, and `request_user_input`
77 38
78Evidence inspected:39Important implication:
79 40
80- upstream `codex-rs/app-server/README.md`41- local Codex rollout JSONL files are a lossy or higher-level projection
81- upstream `codex-rs/app-server-protocol` schemas42- they may preserve plan mode state and final `<proposed_plan>` output
82- upstream `codex-rs/core/src/tools/handlers/request_user_input.rs`43- they should not be treated as proof that low-level plan events do or do not exist
83- upstream `codex-rs/core/templates/collaboration_mode/plan.md`
84 44
85### Semantics45### Claude
86 46
87#### Plan mode47Claude protocol decisions should be based on:
88 48
89Codex plan mode is not just “tool execution denied”. It has explicit behavioral guidance and explicit planning outputs.49- installed SDK typings
50- real local session JSONL evidence
90 51
91Two distinct plan surfaces exist:52Confirmed from local Claude transcript:
92 53
931. **Structured plan state**54- `AskUserQuestion` is a native `tool_use`
94 - `turn/plan/updated`55- `ExitPlanMode` is a native `tool_use`
95 - payload contains:56- `AskUserQuestion.input.questions[]` is structured and adapter-parseable
96 - optional `explanation`
97 - `plan: Array<{ step, status }>`
98 - statuses are `pending | inProgress | completed`
99 57
1002. **Plan text stream**58### Cursor
101 - `item/plan/delta`
102 - useful for rendering streaming plan prose / bullet lists before or alongside structured state
103 59
104This maps very cleanly to our canonical events:60Cursor protocol decisions should be based on:
105 61
106- `turn/plan.updated` -> `turn.plan.updated`62- ACP docs
107- `item/plan/delta` -> `content.delta(plan_text)`63- current local ACP probe output
108 64
109#### Structured user questions65Confirmed from probe:
110 66
111Codex has a dedicated `request_user_input` capability.67- ACP session modes include `agent`, `plan`, and `ask`
68- currently observed ACP updates do not include native structured plan updates or native structured question prompts comparable to Codex or Claude
112 69
113Server request:70---
114 71
115- `item/tool/requestUserInput`72## High-level product model
116 73
117Shape characteristics from upstream protocol/docs:74The implementation should separate three concepts that are currently easy to conflate.
118 75
119- 1–3 questions76### 1. Operating mode
120- each question has:
121 - prompt text
122 - stable id
123 - required options
124- response carries answers per question
125- freeform notes are supported in UX and answer model
126 77
127Important nuance:78This is the agent runtime mode.
128 79
129- upstream handler currently requires non-empty options for every question80Canonical operating modes:
130- Codex TUI also supports an optional freeform note / text alongside selected options
131- pending question requests are turn-scoped and cleaned up on turn completion/interruption
132 81
133### Why Codex is the reference model82- `default`
83- `plan`
84- `ask`
85- `execute`
86- `unknown`
134 87
135Codex should be treated as the reference shape for our provider-agnostic abstraction because it already matches the desired UI:88This should be represented at runtime independently of plan content or prompt cards.
136 89
137- explicit plan lifecycle90### 2. Structured plan state
138- explicit multi-question prompt flow
139- explicit answer submission
140- explicit pending/resolved bookkeeping
141 91
142### Adapter implications92This is the current structured plan snapshot, if the provider can supply one natively or if the adapter can synthesize one confidently.
143 93
144For Codex, the provider adapter layer should stay close to native protocol:94Canonical model:
145 95
146- keep `turn.plan.updated`96```ts
147- keep `content.delta(plan_text)`97interface CanonicalPlanState {
148- map `item/tool/requestUserInput` to `user-input.requested` and `request.opened`98 explanation?: string | null;
149- map answer completion / cleanup to `user-input.resolved` and `request.resolved`99 steps: Array<{
100 id?: string;
101 text: string;
102 status: 'pending' | 'inProgress' | 'completed';
103 source: 'native' | 'synthesized';
104 }>;
105}
106```
150 107
151---108### 3. Structured user-input prompt
152 109
153## Provider 2: Claude Agent SDK / Claude Code110This is the product-level question card model.
154 111
155### What exists natively112Canonical model:
156
157Claude has an explicit plan permission mode:
158
159### Real local session evidence
160
161The sampled Claude session file provides concrete payloads for both key plan-mode tools:
162
1631. `AskUserQuestion` appears as a normal assistant `tool_use` block with input like:
164
165```json
166{
167 "type": "tool_use",
168 "name": "AskUserQuestion",
169 "input": {
170 "questions": [
171 {
172 "question": "What does 'adding profiles to canvas' mean to you? What should profiles enable in canvas?",
173 "header": "Scope",
174 "options": [
175 { "label": "Organize by profile", "description": "..." },
176 { "label": "Profile icon bar in canvas", "description": "..." },
177 { "label": "Per-profile canvas settings", "description": "..." },
178 { "label": "All of the above", "description": "..." }
179 ],
180 "multiSelect": false
181 }
182 ]
183 }
184}
185```
186 113
1872. `ExitPlanMode` also appears as a `tool_use` block, with a long-form `plan` string payload containing the finalized implementation spec.114```ts
115interface CanonicalUserInputPrompt {
116 promptId: string;
117 title?: string;
118 description?: string;
119 questions: Array<{
120 id: string;
121 header?: string;
122 label: string;
123 description?: string;
124 options: Array<{
125 id: string;
126 label: string;
127 description?: string;
128 recommended?: boolean;
129 }>;
130 multiSelect?: boolean;
131 allowFreeform?: boolean;
132 freeformPlaceholder?: string;
133 required?: boolean;
134 }>;
135 source: 'native' | 'tool-derived' | 'synthesized';
136}
188 137
189That gives us two strong adapter conclusions:138interface CanonicalUserInputAnswer {
139 promptId: string;
140 answers: Array<{
141 questionId: string;
142 selectedOptionIds?: string[];
143 text?: string;
144 }>;
145}
146```
190 147
191- Claude user-input prompting is definitely tool-based and structurally parseable.148This should remain distinct from approvals.
192- Claude final plan handoff is tool-based too, not necessarily a dedicated transport event like Codex `turn/plan/updated`.
193 149
194### Concrete adapter mapping from transcript evidence150---
195 151
196Recommended Claude mapping based on real session data:152## Shared orchestration plan
197 153
198- `tool_use.name === "AskUserQuestion"` -> `user-input.requested`154## Summary
199- user answer submission / tool result -> `user-input.resolved`
200- `tool_use.name === "ExitPlanMode"` with `input.plan` -> final plan handoff event or synthesized `turn.plan.updated` snapshot
201 155
202The `AskUserQuestion` payload shape is already close to the UI we want:156Implement a canonical plan-mode interaction pipeline that starts in provider adapters, flows through provider runtime ingestion and orchestration projection, and terminates in capability-driven frontend rendering and response submission.
203 157
204- `questions[]` array158### Shared goals
205- each question has `header` and `question`
206- each question has `options[]` with `label` + `description`
207- `multiSelect` is explicit
208 159
209The main remaining unknown is the exact runtime shape of the **answer** payload that Claude expects back for this tool.160- normalize plan mode state from each provider
161- normalize structured prompts where possible
162- preserve native payloads for later refinement
163- avoid UI coupling to provider names
164- support degraded conversational fallback where native structured prompts are unavailable
210 165
166---
211 167
212- `PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan' | 'dontAsk'`168## Orchestration work items
213 169
214Confirmed from installed SDK typings:170### 1. Extend canonical provider runtime contracts
215 171
216- `/Users/julius/.bun/install/cache/@anthropic-ai/claude-agent-sdk@0.2.62@@@1/sdk.d.ts`172Files:
217- `/Users/julius/.bun/install/cache/@anthropic-ai/claude-agent-sdk@0.2.62@@@1/sdk-tools.d.ts`
218 173
219Claude also exposes plan-related tools/types:174- `packages/contracts/src/providerRuntime.ts`
175- `packages/contracts/src/provider.ts`
176- `EVENTS.md`
220 177
221- `ExitPlanMode`178Add or formalize the following runtime concepts:
222- `AskUserQuestion`
223 179
224These are strong signals that Claude’s plan UX is built around tool usage within a `permissionMode: 'plan'` session rather than a dedicated standalone `turn.plan.updated` event.180- provider interactive capability payload
181- plan-mode state event or mode metadata surface
182- canonical structured user-input payload schema
183- canonical structured user-input answer payload schema
184- final-plan handoff payload for providers that emit a completed plan as text or tool output
225 185
226### What the wire stream exposes186Required additions:
227 187
228Claude adapter input today is built from SDK messages like:188```ts
189interface ProviderInteractiveCapabilities {
190 supportsPlanMode: boolean;
191 supportsAskMode: boolean;
192 supportsStructuredPlanUpdates: boolean;
193 supportsPlanTextStreaming: boolean;
194 supportsStructuredUserInput: boolean;
195 supportsFreeformUserInput: boolean;
196 supportsExitPlanMode: boolean;
197}
198```
229 199
230- `system:init`200Canonical runtime behavior:
231- `system:status`
232- `stream_event`
233- `assistant`
234- `result`
235- hook/task/tool summary events
236 201
237The SDK stream exposes:202- `request.opened/request.resolved` remain transport-oriented
203- `user-input.requested/user-input.resolved` remain product-oriented
204- `turn.plan.updated` remains the canonical structured plan update event
205- `content.delta(streamKind=plan_text)` remains the canonical streaming plan text surface
238 206
239- message deltas207### 2. Add canonical mode/capability publication from providers
240- tool-use starts/stops via content blocks
241- permission-mode metadata
242- final result and usage
243 208
244What it does **not** appear to expose as a first-class event:209Files:
245 210
246- no direct equivalent to Codex `turn/plan/updated`211- `apps/server/src/provider/Layers/*Adapter.ts`
247- no direct equivalent to Codex `item/plan/delta`212- `apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts`
248- no dedicated top-level `user-input.requested` transport event in the SDK stream
249 213
250Instead, plan-mode-related behavior appears to surface through tool uses:214Each adapter should publish provider capabilities and current operating mode early in session startup and when mode changes.
251 215
252- `AskUserQuestion` tool216Preferred shape:
253- `ExitPlanMode` tool
254- regular content/tool stream events
255 217
256### Semantics218- capabilities included in `session.configured`
219- current mode included in `session.configured`, `session.state.changed`, or a dedicated mode field on turn/session runtime metadata
257 220
258#### Plan mode221### 3. Preserve and project plan/user-input state in orchestration
259 222
260Claude’s plan mode is primarily a **permission / behavior mode**:223Files:
261 224
262- session or turn is created with `permissionMode: 'plan'`225- `apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts`
263- tool execution is prevented / constrained226- `apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts`
264- assistant can explore, reason, and propose a plan227- any orchestration projection models feeding the web socket domain events
265- eventually it may call `ExitPlanMode` to transition toward implementation
266 228
267That means Claude plan mode is native, but **structured plan state is not guaranteed**.229Implementation requirements:
268 230
269In practice, there are two likely UI strategies:231- persist latest structured plan snapshot per active turn
232- persist pending structured user-input prompts per turn
233- persist resolved answers for history rendering
234- preserve raw/native payloads for debugging and future adapter improvements
270 235
2711. **Best-effort structured extraction**236### 4. Add frontend state model for plan mode
272 - detect plan-like assistant/tool outputs
273 - synthesize `turn.plan.updated` from them
2742. **Text-first plan rendering**
275 - show reasoning / assistant output as plan narrative
276 - only emit `turn.plan.updated` when we can confidently extract steps
277 237
278#### Asking user questions238Files likely involved:
279 239
280Claude’s `AskUserQuestion` appears to be a tool, not a dedicated transport-level question request primitive.240- web app session/thread stores
241- conversation/event rendering layer
242- input composer / pending interaction surfaces
281 243
282That means the adapter likely needs to:244Add frontend state for:
283 245
284- identify `tool_use` for `AskUserQuestion`246- current provider capabilities
285- parse its input payload into canonical question schema247- current operating mode
286- expose `user-input.requested`248- pending structured prompt
287- accept a user answer and feed it back as the tool result / synthetic user response, depending SDK control path249- structured plan snapshot
250- streaming plan text buffer
251- final plan handoff state when present
288 252
289This is adapter work, not just schema plumbing.253### 5. Implement capability-driven UI rendering
290 254
291### Recommended stance for Claude255UI rules:
292 256
293Claude should support the same canonical user-facing contract, but the adapter will need to **manufacture structure from native tool activity**.257- if `supportsStructuredUserInput`, render the multi-question card UI
258- if not, fall back to standard conversational reply flow
259- if `supportsStructuredPlanUpdates`, render status-tracked plan steps
260- if only `supportsPlanTextStreaming`, render streaming plan prose
261- if neither exists, render standard assistant content in plan mode with clear mode indicator
294 262
295Recommended mapping:263### 6. Implement front-to-back response submission path
296 264
297- `permissionMode: 'plan'` => canonical session/turn metadata `mode = plan`265Requirements:
298- `AskUserQuestion` tool use => canonical `user-input.requested`
299- submitted answer => canonical `user-input.resolved`
300- `ExitPlanMode` tool use => canonical state transition or request to continue with implementation
301- assistant text / tool summaries in plan mode => optional synthesized `turn.plan.updated`
302 266
303### Risk / uncertainty267- question card submit sends canonical answer payload to server
268- server routes answer to the correct provider adapter pending request/tool context
269- resolved provider response emits both:
270 - `user-input.resolved`
271 - `request.resolved` when applicable
304 272
305Claude currently appears less deterministic than Codex for structured plans.273### 7. History and reconnect behavior
306 274
307Main unknowns to resolve during implementation:275Requirements:
308 276
309- exact `AskUserQuestion` tool input/output payload shapes at runtime277- pending structured prompts should survive reconnect/resubscribe if still active
310- whether freeform responses are supported natively or need synthetic augmentation278- resolved prompts should be rendered in history as completed interactions
311- whether `ExitPlanMode` should be exposed to UI explicitly or handled internally by the adapter/session controller279- plan snapshots should be replayable from persisted orchestration activity
280- current streaming plan text should resume cleanly on reconnect if provider continues emitting deltas
312 281
313---282---
314 283
315## Provider 3: Cursor ACP284## Provider adapter plans
316 285
317### What exists natively286## Provider 1: Codex App Server
318 287
319Cursor ACP does have native session modes.288### Summary
320 289
321Confirmed by local probe output in `.tmp/acp-plan-probe/summary.json`:290Codex is the reference implementation and should be wired as the strongest native provider.
322 291
323- `agent`292### Native protocol mapping
324- `plan`
325- `ask`
326
327The probe also confirmed current primary ACP update surfaces:
328 293
329- `available_commands_update`294Source of truth: upstream open-source app-server protocol.
330- `agent_thought_chunk`
331- `agent_message_chunk`
332- `tool_call`
333- `tool_call_update`
334- `session/request_permission`
335 295
336### What the probe did not find296Native incoming surfaces:
337 297
338The existing probe did **not** surface:298- `turn/plan/updated`
299- `item/plan/delta`
300- `item/tool/requestUserInput`
301- `serverRequest/resolved`
302- lower-level `EventMsg.plan_update`
303- lower-level `EventMsg.plan_delta`
304- lower-level `EventMsg.request_user_input`
339 305
340- structured plan update events306### Adapter implementation
341- a dedicated “ask user question” request
342- a dedicated plan delta stream
343 307
344The current ACP surface therefore looks much thinner than Codex.308Files:
345 309
346### Semantics310- `apps/server/src/provider/Layers/CodexAdapter.ts`
311- related tests in `apps/server/src/provider/Layers/*Codex*.test.ts`
347 312
348#### Plan mode313Implementation requirements:
349 314
350Cursor `plan` mode clearly exists as a session mode, but as currently observed it seems to mean:3151. Keep native structured plan updates as-is:
316- `turn/plan/updated` -> `turn.plan.updated`
351 317
352- read-only / design-first behavior3182. Keep native plan text streaming as-is:
353- regular thought/message chunk streaming319- `item/plan/delta` -> `content.delta` with `streamKind: "plan_text"`
354- no dedicated structured plan payload
355 320
356So the plan is native as a mode, but not obviously as a structured protocol surface.3213. Keep native structured user-input as-is:
322- `item/tool/requestUserInput` -> `user-input.requested`
323- also emit `request.opened` with a transport request type
357 324
358#### Asking the user questions3254. On client answer submission:
326- route answer to the corresponding Codex pending request id
327- emit `user-input.resolved`
328- observe/forward `serverRequest/resolved` -> `request.resolved`
359 329
360Cursor ACP currently gives us a nearby primitive only for **permission requests**:3305. Publish capabilities:
331- `supportsPlanMode = true`
332- `supportsAskMode = false` unless Codex exposes a distinct ask mode separately
333- `supportsStructuredPlanUpdates = true`
334- `supportsPlanTextStreaming = true`
335- `supportsStructuredUserInput = true`
336- `supportsFreeformUserInput = true` if answer payload supports note/text in practice
337- `supportsExitPlanMode = false` unless a separate explicit tool/event is introduced
361 338
362- `session/request_permission`339### Codex-specific tests
363 340
364That is not the same as product-level question asking.341- `turn/plan/updated` maps to canonical structured plan state
342- `item/plan/delta` maps to `plan_text`
343- `item/tool/requestUserInput` maps to canonical structured prompt shape
344- answer response resolves pending request and emits both canonical resolved events
345- reconnect/history replay preserves plan snapshot and pending prompt
365 346
366The existence of `ask` mode suggests Cursor may handle asking by:347---
367 348
368- normal assistant prose in ask mode349## Provider 2: Claude Agent SDK / Claude Code
369- possibly mode-switch semantics
370- possibly skills/commands that we have not yet triggered
371 350
372But based on current probe evidence, we should **not assume** a Codex-style structured question API exists in ACP.351### Summary
373 352
374### Recommended stance for Cursor353Claude requires tool-aware adaptation. Native structured interaction exists, but it is surfaced through `tool_use` blocks rather than dedicated transport events equivalent to Codex.
375 354
376Treat Cursor as:355### Real native evidence
377 356
378- native support for `mode = plan`357From the local Claude session transcript:
379- no proven native support for structured plan steps
380- no proven native support for structured user-input questions
381 358
382Therefore Cursor likely needs the most adapter synthesis:359- `AskUserQuestion` arrives as:
360 - assistant message
361 - content block type `tool_use`
362 - `name: "AskUserQuestion"`
363 - `input.questions[]`
364- `ExitPlanMode` arrives as:
365 - assistant message
366 - content block type `tool_use`
367 - `name: "ExitPlanMode"`
368 - `input.plan` containing finalized plan text/spec
383 369
384- derive plan-mode state from ACP session mode370Claude also has native:
385- optionally synthesize `turn.plan.updated` from assistant text if we can extract steps safely
386- for interactive questions, likely fall back to regular conversational turns unless a richer ACP mechanism is discovered
387 371
388### Important product implication372- `permissionMode: 'plan'`
373- `system:init.permissionMode`
374- `system:status.permissionMode`
389 375
390If we want the same rich “multiple-choice question card” UX across all providers, Cursor may need one of these paths:376### Adapter implementation
391 377
3921. **Provider-native path** if later ACP exploration finds structured user question primitives378Files:
3932. **Adapter-mediated path** where the provider is instructed to emit a machine-readable question block in text and adapter parses it
3943. **Capability downgrade** where Cursor only supports conversational follow-up, not full structured plan-question cards
395 379
396Today, evidence supports option 3 as the safe baseline.380- `apps/server/src/provider/Layers/ClaudeCodeAdapter.ts`
381- `apps/server/src/provider/Layers/ClaudeCodeAdapter.test.ts`
382
383Implementation requirements:
384
3851. Publish native operating mode:
386- `permissionMode: 'plan'` -> canonical mode `plan`
387- if future values map cleanly, publish `ask` or `default` accordingly
388
3892. Detect `AskUserQuestion` tool uses:
390- inspect `tool_use` blocks in assistant messages / stream events
391- when `name === 'AskUserQuestion'`, convert `input.questions[]` to canonical `user-input.requested`
392- also emit `request.opened` using a Claude-specific tool-user-input request type
393
3943. Canonical question mapping for Claude:
395- `header` -> canonical `header`
396- `question` -> canonical `label`
397- `options[].label` -> canonical option label
398- `options[].description` -> canonical option description
399- `multiSelect` -> canonical `multiSelect`
400- source = `tool-derived`
401
4024. Implement answer submission path:
403- on UI answer submit, convert canonical answers back into whatever Claude expects for the `AskUserQuestion` tool result path
404- emit `user-input.resolved`
405- emit `request.resolved`
406
4075. Detect `ExitPlanMode` tool uses:
408- when `name === 'ExitPlanMode'`, parse `input.plan`
409- emit a final plan handoff event and/or synthesize a `turn.plan.updated` snapshot if safe
410- treat this as the boundary between planning and implementation readiness
411
4126. Structured plan behavior:
413- Claude does not currently appear to expose a native `turn/plan/updated` equivalent
414- initial implementation should not over-synthesize incremental steps
415- use two-tier strategy:
416 - tier 1: preserve plan-related assistant text in `plan_text` only if clearly attributable
417 - tier 2: optionally synthesize `turn.plan.updated` from explicit structured plan strings only when parsing is robust
418
4197. Publish capabilities:
420- `supportsPlanMode = true`
421- `supportsAskMode = false` unless explicit Claude mode is observed
422- `supportsStructuredPlanUpdates = partial/false initially`
423- `supportsPlanTextStreaming = partial`
424- `supportsStructuredUserInput = true`
425- `supportsFreeformUserInput = unknown/false until answer path confirmed`
426- `supportsExitPlanMode = true`
427
428### Claude-specific tests
429
430- `system:init` and `system:status` publish plan mode metadata
431- `AskUserQuestion` tool maps into canonical prompt schema
432- multi-select and option descriptions round-trip correctly
433- answer submission resolves the pending prompt
434- `ExitPlanMode` tool emits final plan handoff state
435- ordinary tool uses do not get misclassified as prompts or plan exits
436
437### Claude-specific open question to resolve during implementation
438
439- exact runtime shape expected for the answer payload returned to `AskUserQuestion`
440
441Implementation default:
442
443- build adapter abstraction so only the final answer-serialization function is provider-specific and easily swappable once runtime shape is confirmed
397 444
398---445---
399 446
400## Cross-provider comparison447## Provider 3: Cursor ACP
401 448
402### Capability matrix449### Summary
403 450
404| Capability | Codex App Server | Claude Agent SDK | Cursor ACP |451Cursor should initially ship with degraded plan-mode support: native mode awareness, but no claim of structured prompt cards or structured plan steps until protocol evidence exists.
405| --- | --- | --- | --- |
406| Native plan mode | Yes | Yes | Yes |
407| Structured plan update event | Yes | Not obvious / likely no | Not observed |
408| Plan text delta stream | Yes | Not first-class | Not observed |
409| Native structured user question API | Yes | Tool-based, likely yes-ish | Not observed |
410| Native answer resolution lifecycle | Yes | Likely adapter-mediated | Not observed |
411| Best provider for canonical contract | Strong | Medium | Weak |
412 452
413### Core insight453### Real native evidence
414 454
415“Plan mode” means **different things** across providers:455From the ACP probe:
416 456
417- **Codex**: plan mode is a first-class runtime concept with structured plan and question surfaces.457- available modes include `agent`, `plan`, and `ask`
418- **Claude**: plan mode is a first-class permission mode, but structured plan/question UI likely needs adapter interpretation.458- observed updates include:
419- **Cursor**: plan mode is a first-class session mode, but richer plan/question structure is not currently exposed.459 - `available_commands_update`
460 - `agent_thought_chunk`
461 - `agent_message_chunk`
462 - `tool_call`
463 - `tool_call_update`
464- observed request type:
465 - `session/request_permission`
420 466
421---467Not observed:
422 468
423## Recommended provider-agnostic contract469- native structured plan update events
470- native plan text delta event distinct from normal message chunks
471- native structured ask-user-question event
424 472
425We should separate three concerns that are currently easy to conflate.473### Adapter implementation
426 474
427### 1. Session / turn mode475Files:
428 476
429A canonical mode flag:477- `apps/server/src/provider/Layers/CursorAdapter.ts`
430 478- `apps/server/src/provider/Layers/CursorAdapter.test.ts`
431- `default`479- probe scripts under `scripts/`
432- `plan`
433- `ask`
434- `execute`
435- `unknown`
436 480
437This is about **agent operating mode**, not UI widgets.481Implementation requirements:
438 482
439### 2. Structured plan state4831. Publish native operating mode:
484- map ACP `plan` mode -> canonical `plan`
485- map ACP `ask` mode -> canonical `ask`
486- map ACP `agent` mode -> canonical `default` or `execute` depending product semantics
440 487
441A canonical plan model:4882. Do not claim unsupported structured capabilities:
489- no native `turn.plan.updated`
490- no native `user-input.requested`
491- no native `ExitPlanMode` equivalent confirmed
442 492
443```ts4933. Use graceful fallback behavior:
444interface CanonicalPlanState {494- render plan mode as mode state + assistant text stream
445 explanation?: string | null;495- render ask mode as conversational mode + normal user composer
446 steps: Array<{496- do not render structured question cards for Cursor initially
447 id?: string;
448 text: string;
449 status: 'pending' | 'inProgress' | 'completed';
450 source: 'native' | 'synthesized';
451 }>;
452}
453```
454 497
455Rules:4984. Preserve room for future enrichment:
499- keep raw ACP notifications available in native event logs
500- extend the ACP probe to search for hidden or uncommon prompt/mode-change surfaces
456 501
457- Codex can populate this natively.5025. Publish capabilities:
458- Claude/Cursor may populate it only when extraction is confident.503- `supportsPlanMode = true`
459- UI should tolerate absence of structured plan state.504- `supportsAskMode = true`
505- `supportsStructuredPlanUpdates = false`
506- `supportsPlanTextStreaming = false` initially
507- `supportsStructuredUserInput = false`
508- `supportsFreeformUserInput = false` via structured prompt path
509- `supportsExitPlanMode = false`
460 510
461### 3. Structured user-input prompt511### Cursor-specific tests
462 512
463Canonical prompt should be modeled independently of approvals:513- ACP mode metadata maps correctly into canonical mode
514- `agent_thought_chunk` and `agent_message_chunk` still render normally in plan mode
515- UI does not try to open structured prompt cards for Cursor
516- fallback conversational flow remains functional
464 517
465```ts518### Cursor-specific follow-up probe work
466interface CanonicalUserInputPrompt {
467 promptId: string;
468 title?: string;
469 description?: string;
470 questions: Array<{
471 id: string;
472 label: string;
473 description?: string;
474 options: Array<{
475 id: string;
476 label: string;
477 description?: string;
478 recommended?: boolean;
479 }>;
480 allowFreeform?: boolean;
481 freeformPlaceholder?: string;
482 required?: boolean;
483 }>;
484 source: 'native' | 'tool-derived' | 'synthesized';
485}
486```
487 519
488And answers:520Add dedicated probes for:
489 521
490```ts522- mode-switching during an active session
491interface CanonicalUserInputAnswer {523- ask-mode prompt behavior
492 promptId: string;524- any request types besides `session/request_permission`
493 answers: Array<{525- whether specific built-in skills or prompt styles trigger structured question surfaces
494 questionId: string;
495 selectedOptionId?: string;
496 text?: string;
497 }>;
498}
499```
500 526
501### Why separate `request.opened` from `user-input.requested`527---
502 528
503Because they are not the same abstraction:529## Frontend implementation plan
504 530
505- approvals = “may I run/edit/do X?”531### Summary
506- user-input prompts = “which direction do you want?”
507 532
508Codex already blurs these at transport level because `item/tool/requestUserInput` is a request.533Render plan mode through a single UI model driven by canonical events and provider capabilities.
509But product and UI should keep them separate.
510 534
511Recommended rule:535### UI states to support
512 536
513- keep `request.opened/request.resolved` for transport-level correlation537- standard conversation
514- keep `user-input.requested/user-input.resolved` for product-level UX538- plan mode with structured steps
539- plan mode with text-only plan stream
540- pending structured question card
541- resolved question card in history
542- final plan handoff / completed plan artifact
515 543
516---544### Rendering rules
517 545
518## Adapter strategy by provider5461. Show operating mode indicator whenever current mode is `plan` or `ask`
5472. Show structured question card only when a pending `user-input.requested` exists
5483. Show structured plan step list when a current `turn.plan.updated` snapshot exists
5494. Append `plan_text` streaming content beneath or alongside structured steps when both exist
5505. Fall back to assistant text rendering when provider capabilities do not support structure
5516. Show completed selected answers in history after `user-input.resolved`
519 552
520### Codex adapter553### Submission behavior
521 554
522Use native primitives directly.555- option click / freeform answer submits canonical answer payload
556- disable duplicate submits while request is pending resolution
557- preserve pending-card state across reconnects if request remains open
523 558
524Implementation target:559---
525 560
526- preserve `turn.plan.updated`561## Data flow end to end
527- preserve `content.delta(plan_text)`
528- preserve `user-input.requested`
529- preserve `user-input.resolved`
530- expose raw/native metadata for future UI improvements
531 562
532### Claude adapter5631. Provider starts session and publishes capabilities + mode
5642. User starts plan-mode turn or provider enters plan mode
5653. Adapter emits structured plan and/or plan text events when available
5664. Adapter emits `user-input.requested` when provider asks a structured question
5675. Frontend renders question card or fallback conversational prompt based on capabilities
5686. User submits answer
5697. Server routes answer back to provider adapter pending request/tool context
5708. Adapter emits `user-input.resolved` and `request.resolved`
5719. Provider may continue planning, emit more plan updates, or emit final plan handoff
57210. Frontend renders final completed plan state and history
533 573
534Implement tool-aware adaptation.574---
535 575
536Implementation target:576## Test plan
537 577
538- treat `permissionMode: 'plan'` as native plan-mode state578### Contracts
539- detect `AskUserQuestion` tool uses and convert to canonical user-input prompt
540- convert question answers back into Claude-native tool response path
541- detect `ExitPlanMode` and emit a canonical mode-change / handoff event
542- optionally synthesize plan steps from assistant output in plan mode
543 579
544### Cursor adapter580Files:
545 581
546Implement conservative degraded support first.582- `packages/contracts/src/providerRuntime.test.ts`
583- related schema tests
547 584
548Implementation target:585Add coverage for:
549 586
550- expose native session mode including `plan` and `ask`587- provider capabilities schema
551- do not claim structured plan updates unless proven588- structured user-input prompt schema
552- do not claim structured user-input prompts unless proven589- structured user-input answer schema
553- optionally add a provider capability flag so UI can fall back to conversational follow-up590- mode metadata schema
554 591
555---592### Server/provider tests
556 593
557## Capability flags we should add594Files:
558 595
559To avoid overpromising in the UI, add provider runtime capabilities such as:596- `apps/server/src/provider/Layers/CodexAdapter.test.ts`
597- `apps/server/src/provider/Layers/ClaudeCodeAdapter.test.ts`
598- `apps/server/src/provider/Layers/CursorAdapter.test.ts`
599- `apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts`
560 600
561```ts601Add coverage for:
562interface ProviderInteractiveCapabilities {
563 supportsPlanMode: boolean;
564 supportsAskMode: boolean;
565 supportsStructuredPlanUpdates: boolean;
566 supportsPlanTextStreaming: boolean;
567 supportsStructuredUserInput: boolean;
568 supportsFreeformUserInput: boolean;
569 supportsExitPlanMode: boolean;
570}
571```
572 602
573Expected initial values:603- capabilities emission
604- mode propagation
605- plan update propagation
606- plan text propagation
607- structured prompt lifecycle
608- answer resolution lifecycle
609- reconnect/history replay behavior
574 610
575- Codex: all or nearly all `true`611### Web/UI tests
576- Claude: `supportsPlanMode=true`, `supportsStructuredUserInput=likely true via adapter`, `supportsStructuredPlanUpdates=partial`
577- Cursor: `supportsPlanMode=true`, `supportsAskMode=true`, most structured flags `false`
578 612
579This lets the UI:613Add coverage for:
580 614
581- render rich plan cards only when supported615- question card rendering from canonical prompt
582- fall back to plain assistant text + reply box otherwise616- question answer submission
583- avoid coupling the design to Codex-specific assumptions617- resolved question history rendering
618- structured plan step rendering
619- text-only plan rendering
620- capability-based fallback rendering for Cursor
584 621
585---622### Probe / fixture tests
586 623
587## Suggested implementation order624- preserve the Claude transcript-derived `AskUserQuestion` and `ExitPlanMode` payloads as fixtures
588 625- preserve Codex protocol fixtures for `turn/plan/updated`, `item/plan/delta`, and `item/tool/requestUserInput`
5891. **Formalize provider capability flags**626- preserve ACP probe summaries as fixtures for unsupported-capability assertions
590 - make support explicit before UI work
5912. **Finish canonical user-input contracts**
592 - question schema, answer schema, source markers
5933. **Codex end-to-end implementation first**
594 - closest match to desired UX
5954. **Claude adapter translation second**
596 - parse `AskUserQuestion` and `ExitPlanMode`
5975. **Cursor degraded plan-mode support third**
598 - mode awareness first, richer structure later
5996. **UI renders by capability, not provider name**
600 - prevents provider-specific branching from leaking upward
601 627
602---628---
603 629
604## Open questions630## Assumptions and defaults
605 631
6061. Claude runtime payloads:632- Codex is the reference provider for full structured plan/question UX.
607 - what exact `AskUserQuestion` input/output shapes arrive over the stream?633- Claude supports structured prompts via tool adaptation, but incremental structured plan updates are not assumed initially.
6082. Claude answer submission path:634- Cursor supports plan and ask modes, but not structured prompt cards or structured plan step updates initially.
609 - should answers be injected as tool results, control responses, or synthetic user turns?635- Product/UI should degrade gracefully rather than invent unsupported provider behavior.
6103. Cursor ACP:636- Raw provider payloads should always be retained where feasible to support future adapter refinement.
611 - is there an undocumented or less common interactive prompt primitive beyond `session/request_permission`?
6124. Cursor mode switching:
613 - can ACP session mode change mid-session, and is that surfaced as `session/update` or request/response only?
6145. Plan synthesis:
615 - do we want best-effort extraction of numbered/bulleted plans into `turn.plan.updated` for Claude/Cursor, or should we keep structured plans native-only at first?
616 637
617---638---
618 639
619## Immediate recommendation640## Recommended execution order
620
621Build the product contract around **capabilities**, not around the assumption that every provider has Codex-style plan/question primitives.
622
623Concretely:
624
625- Codex should drive the first full UX implementation.
626- Claude should be supported with adapter-derived question handling and partial/synthesized plan state.
627- Cursor should initially support mode-aware UX with graceful fallback to conversational replies rather than full structured question cards.
628 641
629That gives us a provider-agnostic architecture without pretending the underlying protocols are already equivalent.6421. Formalize canonical capabilities + mode + structured prompt contracts
6432. Wire capabilities/mode through orchestration and web socket projections
6443. Finish Codex end-to-end plan mode implementation first
6454. Implement Claude `AskUserQuestion` and `ExitPlanMode` adapter mapping
6465. Add Cursor degraded plan/ask mode support
6476. Build capability-driven frontend rendering and answer submission
6487. Add reconnect/history coverage and transcript/protocol fixtures