Problem
ACP clients (session/set_mode) can only switch a session to auto or ask today — handleSetMode in internal/acp/agent.go explicitly rejects any other mode ID, and modeState() only ever advertises those two in availableModes. Zero already has a fully-built "Plan Mode" equivalent, PermissionModeSpecDraft (internal/agent/types.go), which genuinely restricts the tools the model can see (toolAdvertisedInSpecDraft in internal/agent/loop.go — read-only tools + ask_user + submit_spec only) and ends the turn with a clean review gate (submit_spec saves a markdown spec under .zero/specs/ and returns Meta["control"]="spec_review_required"). This mode is fully wired into the TUI (internal/tui/spec_mode.go) but is completely unreachable from ACP, so external ACP integrations (editor plugins, third-party desktop apps built on zero acp) have no way to offer the same "research → propose a plan → user approves → then implement" workflow that Zero's own terminal UI already has.
Requested behavior
Expose spec-draft as a settable/advertised ACP session mode, wiring it through exactly the same way internal/tui/spec_mode.go's handleSpecCommand already drives it for the TUI:
handleSetMode accepts modeId: "spec-draft" alongside the existing auto/ask cases.
modeState() advertises spec-draft as a third entry in availableModes.
runTurn registers submit_spec (specmode.RegisterDraftTools) and sets agent.Options.SystemPrompt = specmode.DraftSystemPrompt when the session's current mode is spec-draft — without touching the existing tool-visibility gate (toolAdvertisedInSpecDraft), which stays the single source of truth for what the model can call.
- A new
session/update notification kind — following the existing precedent of the plan and current_mode_update kinds already defined in internal/acp/types.go — e.g. sessionUpdate: "_zero/spec_review_required", emitted from the turn's OnToolResult callback when submit_spec's result carries Meta["control"]=="spec_review_required". This mirrors the special-case that already exists there for update_plan (internal/acp/agent.go, ~L245-250) and would carry specId/specTitle/specFilePath/relativePath so the client can read the saved spec and render a review UI.
Explicitly out of scope: changing the closed ACP StopReason enum, or adding a new RPC method for "resume with spec." A client can read the saved spec file directly (it already knows the workspace root) and, once the user approves, just call session/set_mode back to ask/auto and send a normal session/prompt with the spec content as the user message — the same thing approveSpecReview already does client-side in the TUI.
Why it belongs in Zero
This is a small, additive, backwards-compatible change: a new mode option plus a new session/update kind that ACP clients unaware of it will simply ignore (per ACP's forward-compatible update-kind design — ZERO's own zero-desktop client already no-ops on unrecognized sessionUpdate kinds). It brings the ACP surface to parity with the TUI for a capability Zero has already fully built and ships today, letting any ACP-based integration offer the same plan-before-you-build workflow without reimplementing zero's planning/spec logic client-side.
Alternatives or workarounds considered
A client could emulate a soft "plan mode" entirely on its own side: stay in ask mode and auto-deny every write/execute permission request while a local toggle is on. That works with zero changes to this repo, but the model still sees and attempts write/shell tools and gets denied every time (wastes a turn, can confuse a weaker model) instead of those tools simply not being advertised at all, which spec-draft already does correctly server-side. It also can't reuse zero's own saved-spec-file review flow, so the "plan" would just be prose in a chat message instead of a structured, persisted, reviewable spec document.
Examples, screenshots, or prior discussion
N/A — first-time filing. Happy to open a PR implementing exactly this once the issue has been reviewed; the change would be scoped to internal/acp/agent.go, internal/acp/translate.go, and internal/acp/types.go, plus new/extended tests in internal/acp/agent_test.go and internal/acp/translate_test.go (following the existing TestACPSetModeUpdatesSession / TestACPEndToEndPrompt patterns).
Problem
ACP clients (
session/set_mode) can only switch a session toautoorasktoday —handleSetModeininternal/acp/agent.goexplicitly rejects any other mode ID, andmodeState()only ever advertises those two inavailableModes. Zero already has a fully-built "Plan Mode" equivalent,PermissionModeSpecDraft(internal/agent/types.go), which genuinely restricts the tools the model can see (toolAdvertisedInSpecDraftininternal/agent/loop.go— read-only tools +ask_user+submit_speconly) and ends the turn with a clean review gate (submit_specsaves a markdown spec under.zero/specs/and returnsMeta["control"]="spec_review_required"). This mode is fully wired into the TUI (internal/tui/spec_mode.go) but is completely unreachable from ACP, so external ACP integrations (editor plugins, third-party desktop apps built onzero acp) have no way to offer the same "research → propose a plan → user approves → then implement" workflow that Zero's own terminal UI already has.Requested behavior
Expose
spec-draftas a settable/advertised ACP session mode, wiring it through exactly the same wayinternal/tui/spec_mode.go'shandleSpecCommandalready drives it for the TUI:handleSetModeacceptsmodeId: "spec-draft"alongside the existingauto/askcases.modeState()advertisesspec-draftas a third entry inavailableModes.runTurnregisterssubmit_spec(specmode.RegisterDraftTools) and setsagent.Options.SystemPrompt = specmode.DraftSystemPromptwhen the session's current mode isspec-draft— without touching the existing tool-visibility gate (toolAdvertisedInSpecDraft), which stays the single source of truth for what the model can call.session/updatenotification kind — following the existing precedent of theplanandcurrent_mode_updatekinds already defined ininternal/acp/types.go— e.g.sessionUpdate: "_zero/spec_review_required", emitted from the turn'sOnToolResultcallback whensubmit_spec's result carriesMeta["control"]=="spec_review_required". This mirrors the special-case that already exists there forupdate_plan(internal/acp/agent.go, ~L245-250) and would carryspecId/specTitle/specFilePath/relativePathso the client can read the saved spec and render a review UI.Explicitly out of scope: changing the closed ACP
StopReasonenum, or adding a new RPC method for "resume with spec." A client can read the saved spec file directly (it already knows the workspace root) and, once the user approves, just callsession/set_modeback toask/autoand send a normalsession/promptwith the spec content as the user message — the same thingapproveSpecReviewalready does client-side in the TUI.Why it belongs in Zero
This is a small, additive, backwards-compatible change: a new mode option plus a new
session/updatekind that ACP clients unaware of it will simply ignore (per ACP's forward-compatible update-kind design — ZERO's ownzero-desktopclient already no-ops on unrecognizedsessionUpdatekinds). It brings the ACP surface to parity with the TUI for a capability Zero has already fully built and ships today, letting any ACP-based integration offer the same plan-before-you-build workflow without reimplementing zero's planning/spec logic client-side.Alternatives or workarounds considered
A client could emulate a soft "plan mode" entirely on its own side: stay in
askmode and auto-deny every write/execute permission request while a local toggle is on. That works with zero changes to this repo, but the model still sees and attempts write/shell tools and gets denied every time (wastes a turn, can confuse a weaker model) instead of those tools simply not being advertised at all, whichspec-draftalready does correctly server-side. It also can't reuse zero's own saved-spec-file review flow, so the "plan" would just be prose in a chat message instead of a structured, persisted, reviewable spec document.Examples, screenshots, or prior discussion
N/A — first-time filing. Happy to open a PR implementing exactly this once the issue has been reviewed; the change would be scoped to
internal/acp/agent.go,internal/acp/translate.go, andinternal/acp/types.go, plus new/extended tests ininternal/acp/agent_test.goandinternal/acp/translate_test.go(following the existingTestACPSetModeUpdatesSession/TestACPEndToEndPromptpatterns).