Skip to content

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

Open

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).

Activity

  1. added
    issue-approvedReviewed and approved by the core team; community PRs may implement this issue.
    on Jul 25, 2026
  2. Vasanthdev2004 commented on Aug 5, 2026

    @Vasanthdev2004
    Collaborator

    @pdl-clay sorry this sat for three weeks. The analysis is right: PermissionModeSpecDraft still exists on main and handleSetMode still refuses anything but auto and ask.

    There is a collision to settle before anyone writes this. PRs #853 and #854 are adding PermissionModePlan, a separate read-only interactive planning mode, which would leave Zero with two plan-shaped modes: spec-draft, which ends the turn at a submit_spec review gate, and plan, which stays interactive. Exposing spec-draft over ACP now would ship the ACP surface against the one that may not be the survivor.

    So the order is: decide whether those two modes merge or stay distinct, then wire ACP to whichever it is. @anandh8x you are assigned here and @euxaristia owns #853, so between you please settle that first. I would rather have one mode with a flag for whether it gates at the end than two modes a client has to choose between.

  3. FabioLeitao commented on Oct 13, 2026

    @FabioLeitao
    Contributor

    Hi, and thanks for the thorough write-up and the earlier triage.

    Checking current main, this has moved since the August discussion: since #853 (0176ee49), handleSetMode accepts auto, ask and plan, and modeState() advertises all three, so Plan Mode is now settable over ACP. spec-draft is still not accepted (it falls through to unknown mode: spec-draft).

    We run a small local bridge that drives zero acp over stdio for a browser client, so we are a concrete consumer of session/set_mode and current_mode_update. I want to be upfront that we have only read this on main and have not yet exercised plan mode end to end from a client.

    It may be worth clarifying what this issue still covers now that plan is exposed: is the goal still to expose spec-draft (or a gated variant of plan), including a signal for the submit_spec review gate? If so, we would be glad to test any change from a client and report back. We are not asking to take this over, since it is assigned.

  4. Vasanthdev2004 commented on Oct 16, 2026

    @Vasanthdev2004
    Collaborator

    Good catch, Fabio. The plan half is done: since #853, session/set_mode accepts plan and modeState() advertises it. What this issue still covers is spec-draft, and that's more than adding it to the accepted list. It drafts in a separate session and ends at the submit_spec review gate, and ACP has no standard way to tell a client "a spec is waiting for review". So the open part is mostly that signal: whether it's a stopReason, a session/update carrying the spec, or something else a client can act on.

    @anandh8x, are you still planning to take this? If so, Fabio's offer to test it from a real client is worth taking up. If not, say so and it goes back to the pool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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