The automation-only rewrite edited many implemented Agent Notes; several edits replaced still-live or historical rationale instead of reframing: - llm-model-catalog: restore the prompt/request consistency section and selection-ownership alternatives — installAgentLlmTarget and the TUI /model selector still ship that design; only the ACP wire is gone. - plan-specific-collaboration-state, acp-multi-session, todo-write, ask-user-question: link the superseding automation-only note instead of silently rewriting the original decision or motivation; drop a paragraph duplicating the Web-provider facts stated two paragraphs up. - sandbox: stop claiming unit coverage for turn-enclosed config writes (that mechanism left with the bridge) and retitle the commit-boundary paragraph accordingly. - Fix the missing blank line before '## Consequences' in the plugin-command-registration pair, the JSON-RPC/Web render-intent consumer misattribution (the second consumer is the host/client runtime), stale bash_output/bash_kill names, and 'optional goals' in architecture.md. - examples/acp-agent/README.md: point at the package contract instead of restating it; packages/ui/permission and plan-mode READMEs record the consumer-less preset service and the exit_plan_mode coverage gap under Known Limitations. - 2026-06-19-acp-snapshot-tests: the new note defers the corpus migration rather than committing to it; say so. Re-record the touched bilingual pairs.
5.0 KiB
Agent Note: Collapse named session modes into plan mode
Status: implemented
English | 中文
Problem
The first plan-mode implementation introduced a generic named-mode registry even though the product shipped only plan. ModeConfig.modes, definition-name validation, ctx.modes.list(), retired-definition fallback, and a synthetic review mode in tests existed only to support hypothetical future collaboration modes. The production-specific behavior—plan guidance, /plan, and exit_plan_mode—still lived in the same package, so the generic API did not isolate a reusable mechanism from plan policy.
The word “mode” also spans unrelated domains. Sandbox mode is an enforcing policy owned by ctx.sandboxPolicy and logged as sandbox/mode; plan mode is a collaboration stance that contributes guidance and a reviewed exit. Treating both as instances of one named-mode abstraction would obscure their independent ownership. A transport's generic vocabulary is not evidence that the harness needs a generic mode domain.
Decision
Plan mode owns a plan-specific product package: @deepseek-ai/dsh-plan-mode at packages/plan/plan-mode/. The durable fact is plan/mode: { active: boolean }, folded by foldPlanMode(events) with false as the empty-log value. ctx.planMode.get(agent) returns { active, pending? }, and set(agent, active) records the boundary-applied selection. The existing prompt-submit, continuation, retry, append-failure, and disposal fences remain unchanged in meaning.
Configuration is exactly { section: string }. The package registers the fixed plan:policy section, /plan [message], the exact /plan off direct-exit form, and exit_plan_mode itself. Bare /plan selects active; another non-empty argument selects it first and then sends the trimmed text through agent.steer(), making the text an ordinary logged user message in the affected step. /plan off selects inactive without model input and can cancel an entry that is still pending at the boundary. The exit tool remains registered while plan mode is inactive so the request tool catalog stays stable.
Human-facing compositions own plan selection and review. This note originally kept ACP's protocol-level default/plan picker as an adapter over the boolean service; ACP as an automation-only protocol supersedes that wire projection, so the ACP composition now mounts neither plan mode nor a mode-selection protocol.
Sandbox mode and approval policy remain separate enforcement axes. Plan mode neither reads nor writes them, and the simplification introduces no shared base type, registry, or preset abstraction across those concepts.
Deleted surface
- The arbitrary definition map, mode-name regular expression, reserved-name rules, and per-definition command loop.
ModeDefinition, the resolved definition map,ctx.modes.list(), string-valued get/set state, and unknown or retired mode handling.- Test-only
reviewmode cases and claims that additional modes can be added through configuration. - Generic
mode/setandmode:policynames; the plan package now ownsplan/modeandplan:policy.
Alternatives considered
Keep a private generic registry and expose only plan today. Rejected because the unused name/config machinery would still be maintained and tested without a second production consumer. A future collaboration state can establish the right shared seam from two concrete cases.
Fold sandbox mode into the same service. Rejected because collaboration guidance and execution confinement have different owners, lifecycle semantics, and consumers. Their shared English noun is not a domain relationship.
Let one presentation transport own plan state. Rejected because TUI, Web, resume, fork, prompt assembly, and the exit tool need the same logged fact independently of any one transport. Presentation adapters own only their projections.
Verification
- Package tests retain boundary ordering, retry, append-failure, HMR disposal, prompt assembly, stable native and Code Mode schemas, review outcomes, and invariant coverage through the boolean service.
- Command tests cover bare
/plan,/plan <message>, active/plan off, pending-entry cancellation, inactive idempotence, absence of/modeand/review, and effect-scoped removal. - The keyless TUI scenarios enter through
/plan <message>, leave through/plan off, and prove that each committedplan/modeprecedes the request header it changes, the entry message is logged under plan guidance, and the post-exit request omits that guidance.
Consequences
The implementation has one vocabulary for one shipped feature. Adding another collaboration stance is an explicit design decision instead of a config entry, and automation clients do not acquire human mode controls through ACP. The migration intentionally rejects old mode/set logs and old modes.plan.section configuration under the repository's pre-release format policy.