Skip to content

Expose spec-draft (Plan Mode) as a settable session mode over ACP #664

Description

@pdl-clay

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:

  1. handleSetMode accepts modeId: "spec-draft" alongside the existing auto/ask cases.
  2. modeState() advertises spec-draft as a third entry in availableModes.
  3. 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.
  4. 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).

Metadata

Metadata

Assignees

Labels

issue-approvedReviewed and approved by the core team; community PRs may implement this issue.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions