Plan Mode

Plan mode is logged per-agent collaboration state owned by dsh-plan-mode (ctx.planMode, PlanModeController): while active, a deployment-owned guidance section is included in each model request. Plan mode is soft guidance. Sandbox mode and approval policy enforce restrictions independently; neither reads or writes plan state, so deployments configure them separately. The package is optional, and the agent loop does not depend on it. It contributes the plan:policy prompt section and registers the exit_plan_mode tool and /plan command. The design note owns the rationale; the package README owns the model-experience and limitation detail.

Source: packages/plan/plan-mode/src/index.ts

Logged state and recovery

plan/mode ({ active: boolean }) is a log-only, whole-value-replace session event: durable and replayable, never in the model transcript. foldPlanMode(events, end?) returns the last logged value in the prefix, or false when there is none — the state in force is always a pure fold of the session log, so resume, fork, and compaction recover it with no live mirror, and UIs observe committed flips through session/event. The complete event declaration is in the persistence log event catalog.

Pending selections and the pre-step append

Because every session event is turn-enclosed, a user selection remains pending until the next accepted in-turn pre-step appends it before request derivation, in whichever turn that occurs. A selection never forces continuation, so one made after a turn's final accepted pre-step is appended in a later turn. set(agent, active) records the pending selection (a no-op when the target equals the logged-or-already-pending state), and get(agent) returns { active: boolean; pending?: boolean }: the logged state used to assemble the current step plus the selected state waiting to be appended.

The only append point while an agent is running is a prepended agent/pre-step listener. It observes every proposed request step, including turn 1 step 1 and request-recovery retries, calls downstream listeners first, and appends only after they accept the step. Prompt admission happens before a turn and cannot append plan/mode, so a selection made at the prompt is appended by the first accepted in-turn pre-step of the turn it starts. An append failure cannot block the turn, and the selection remains pending for a later accepted in-turn pre-step. An appended user selection also records one plugin-sourced user/message notice, but only when the last logged request header described the other state, so the model is told exactly when its context changed and never redundantly. A selection made after a turn's final accepted pre-step remains process-local and is lost if the process exits before another accepted in-turn pre-step (README limitation).

Configuration

/** Deployment-owned plan guidance. */
interface PlanModeConfig {
  /** Guidance rendered as the `plan:policy` prompt section while plan mode is active. */
  section: string
}

A missing, blank, or non-string section and any unknown key fail at plugin load rather than being ignored. While plan mode is active, the exact section text renders as the plan:policy system-prompt section at order 50; inactive plan mode contributes no text.

The exit tool and the /plan command

exit_plan_mode stays registered while plan mode is inactive, so entering or leaving plan mode changes only the prompt section, never the request tool catalog; execution outside plan mode fails. In plan mode it requires a complete markdown plan starting with a # heading and presents it for review through the user-questions seam. Approval returns { approved: true } and records a silent (non-narrated) pending exit that is appended at the next accepted in-turn pre-step. Plan guidance therefore remains active for the rest of the assistant's current tool batch, and the tool result itself reports the transition. Keep-planning is a failed call carrying the user's feedback, so the model revises and presents again; a missing interaction channel and a service reload during review also fail the call rather than silently leaving plan mode.

When ctx.commands is composed, the plugin registers /plan [off|message]: bare /plan selects plan mode, any other non-empty message selects it and then submits the text through agent.steer() so it becomes the next step's ordinary logged user message under plan guidance, and the exact argument off selects inactive, which also cancels a pending entry before it is appended and becomes visible to a request.

The service

ctx.planMode owns the logged plan state, applies and narrates selected state at step start, and owns the plan:policy section, the /plan command, and the stable exit tool; get/set signatures are in the generated service catalog.

Cordis API

Generated from source by scripts/gen-cordis-catalog.ts (verified fresh by pnpm run verify-cordis-catalog in doc-sync; regenerate with pnpm run gen-cordis-catalog) — this section is byte-identical in both language sides of the page. Signature blocks use a ts cordis-catalog fence and keep the original source JSDoc; dispatch modes are defined in the primer, and the framework-inherited ctx API lives in cordis-api/inherited.md.

ctx.planModePlanModeController

ctx.planMode: owns logged plan state, applies and narrates selected state at step start, the plan:policy section, the /plan command, and the stable exit tool. UIs observe committed flips through session/event; there is no live mirror.

/**
 * Read the logged plan state and any selected state awaiting the next
 * accepted in-turn pre-step.
 *
 * @param agent The agent to read.
 * @returns Current logged state plus a pending selection, when present.
 */
get(agent: Agent): { active: boolean; pending?: boolean }

/**
 * Select whether plan mode should be active. Between turns the method
 * appends the change immediately because no in-turn pre-step will run until
 * another prompt starts a turn. The open-turn fold is the idle signal:
 * agent status stays `running` through post-turn checkpointing, when no
 * further in-turn pre-step runs. During an open turn the selection remains
 * pending until the next accepted in-turn pre-step. Repeated selection of
 * the current or already-pending state is a no-op.
 *
 * @param agent The agent to switch.
 * @param active Whether plan mode should be active.
 * @returns what happened: `committed` (logged now), `queued` (awaiting the
 * next accepted in-turn pre-step), `cancelled` (an opposite pending selection
 * was cleared; the logged state already matches), or `noop` (already in that
 * state).
 */
set(agent: Agent, active: boolean): 'committed' | 'queued' | 'cancelled' | 'noop'

Types: Agent

Source: packages/plan/plan-mode/src/index.ts:184

计划模式

计划模式是 dsh-plan-mode 拥有的、记录到日志的逐 agent(智能体)协作状态(ctx.planModePlanModeController):激活期间,每个模型请求都会包含一段部署持有的指引。计划模式是软性指引沙箱模式审批策略分别强制限制;两者都不读写计划状态,因此部署需要分别配置它们。该包是可选项,agent loop(智能体循环)不依赖它。它贡献 plan:policy 提示词段落,并注册 exit_plan_mode 工具和 /plan 命令。设计说明负责决策依据;包 README负责模型体验与限制细节。

源码:packages/plan/plan-mode/src/index.ts

已记录状态与恢复

plan/mode{ active: boolean })是仅记日志、整值替换的会话事件:持久且可回放,绝不进入模型 transcript(文本记录)。foldPlanMode(events, end?) 返回前缀中最后一条已记录值,没有时返回 false:生效状态始终是会话日志的纯折叠,因此恢复、fork 与压缩(compaction)无需实时镜像即可将其复原,UI 通过 session/event 观察已提交的切换。完整事件声明见持久化日志事件目录

待生效选择与 pre-step 追加

由于每个会话事件都位于轮次之内,用户选择会保持待生效状态,直到下一个被接受的轮内 pre-step 在派生请求之前追加该选择,无论该 pre-step 位于哪个轮次。选择不会强制续行,因此在某轮最后一个被接受的 pre-step 之后作出的选择会在之后的轮次追加。set(agent, active) 记录待生效选择(目标值与已记录或已在等待的状态相同时不做任何事),get(agent) 返回 { active: boolean; pending?: boolean }:用于组装当前步骤的已记录状态,以及等待追加的已选状态。

agent 运行时,唯一的追加点是前置(prepend)注册的 agent/pre-step 监听器。它会观察每个候选请求步骤,包括第 1 轮第 1 步和请求恢复重试;它先调用下游监听器,只在下游接受该步骤后追加。提示词准入发生在轮次开启之前,无法追加 plan/mode,因此在提示词处作出的选择由它开启的轮次内第一个被接受的 pre-step 追加。追加失败不能阻塞轮次,且该选择会继续等待之后被接受的轮内 pre-step。追加用户选择时还会记录一条插件来源的 user/message 通知,但仅当最后记录的请求头描述的是另一种状态时才记录,因此模型恰好在上下文变化时收到通知,且绝不重复。在某轮最后一个被接受的 pre-step 之后作出的选择只存在于进程内;如果进程在另一个被接受的轮内 pre-step 之前退出,该选择会丢失(README 限制)。

配置

/** Deployment-owned plan guidance. */
interface PlanModeConfig {
  /** Guidance rendered as the `plan:policy` prompt section while plan mode is active. */
  section: string
}

section 缺失、为空白或不是字符串,以及任何未知键,都会在插件加载时失败,而不是被忽略。计划模式激活期间,确切的 section 文本以 order 50 渲染为 plan:policy 系统提示词段落;未激活的计划模式不贡献任何文本。

退出工具与 /plan 命令

exit_plan_mode 在计划模式未激活时仍保持注册,因此进入或离开计划模式只改变提示词段落,绝不改变请求的工具目录;在计划模式之外执行会失败。在计划模式中,它要求一份以 # 标题开头的完整 markdown 计划,并通过用户交互 seam 呈交评审。批准返回 { approved: true },并记录一个静默(不叙述)的待生效退出,由下一个被接受的轮内 pre-step 追加。因此,计划指引在 assistant 当前这批工具调用的剩余部分继续生效,而工具结果本身会报告这次转换。「继续规划」则是一次携带用户反馈的失败调用,模型据此修订并再次呈交;评审期间交互通道缺失或服务重载同样使调用失败,而不是静默离开计划模式。

ctx.commands 被组合时,插件注册 /plan [off|message]:单独的 /plan 选择计划模式;任何其他非空消息先选择计划模式,再通过 agent.steer() 提交该文本,使其在计划指引下成为下一步骤的普通已记录用户消息;确切参数 off 选择未激活,这还会在待生效条目被追加并对请求可见之前将其取消。

服务

ctx.planMode 拥有已记录的计划状态,在步骤开始时应用并叙述选中的状态,还拥有 plan:policy 段落、/plan 命令和稳定注册的退出工具;get/set 签名见生成的服务目录

Cordis API

Generated from source by scripts/gen-cordis-catalog.ts (verified fresh by pnpm run verify-cordis-catalog in doc-sync; regenerate with pnpm run gen-cordis-catalog) — this section is byte-identical in both language sides of the page. Signature blocks use a ts cordis-catalog fence and keep the original source JSDoc; dispatch modes are defined in the primer, and the framework-inherited ctx API lives in cordis-api/inherited.md.

ctx.planModePlanModeController

ctx.planMode: owns logged plan state, applies and narrates selected state at step start, the plan:policy section, the /plan command, and the stable exit tool. UIs observe committed flips through session/event; there is no live mirror.

/**
 * Read the logged plan state and any selected state awaiting the next
 * accepted in-turn pre-step.
 *
 * @param agent The agent to read.
 * @returns Current logged state plus a pending selection, when present.
 */
get(agent: Agent): { active: boolean; pending?: boolean }

/**
 * Select whether plan mode should be active. Between turns the method
 * appends the change immediately because no in-turn pre-step will run until
 * another prompt starts a turn. The open-turn fold is the idle signal:
 * agent status stays `running` through post-turn checkpointing, when no
 * further in-turn pre-step runs. During an open turn the selection remains
 * pending until the next accepted in-turn pre-step. Repeated selection of
 * the current or already-pending state is a no-op.
 *
 * @param agent The agent to switch.
 * @param active Whether plan mode should be active.
 * @returns what happened: `committed` (logged now), `queued` (awaiting the
 * next accepted in-turn pre-step), `cancelled` (an opposite pending selection
 * was cleared; the logged state already matches), or `noop` (already in that
 * state).
 */
set(agent: Agent, active: boolean): 'committed' | 'queued' | 'cancelled' | 'noop'

Types: Agent

Source: packages/plan/plan-mode/src/index.ts:184