Skip to content

ByteMind Plan Mode Implementation

mickey-228 edited this page Mar 31, 2026 · 1 revision

ByteMind Plan Mode 实现文档

1. 文档目标

本文档用于指导 ByteMind 在现有 Go 架构下落地真正可用的 Plan Mode

目标不是再加一层“会说计划”的 prompt,而是把当前已经存在的:

  • Tab 模式切换
  • update_plan 工具
  • session.Plan 持久化
  • mode-plan.md / block-plan.md

升级成一个完整的、可执行的、可展示的规划工作流。

2. 当前代码现状

2.1 已有能力

当前仓库已经具备 Plan Mode 的基础骨架:

  1. internal/tui/model.go
    • Tab 可以在 build / plan 间切换。
    • submitPrompt 会把当前 mode 传给 runner.RunPrompt(...)
  2. internal/agent/prompt.go
    • 已按模式注入 mode-build.mdmode-plan.md
    • session.Plan 非空时,会注入 block-plan.md
  3. internal/tools/update_plan.go
    • 已存在 update_plan 工具。
    • 已校验 plan 非空,且最多一个 in_progress
  4. internal/agent/runner.go
    • 收到 update_plan 后会把 sess.Plan 写回,并发送 EventPlanUpdated
  5. internal/session/store.go
    • Session 已能持久化 Plan

2.2 当前缺口

当前实现还不是真正的 Plan Mode,核心问题有五个:

  1. Tab 只改了 UI 状态,没有真正做模式级行为隔离。
    • 代码中已有提示:Switched to Plan mode. UI only for now.
  2. Plan 模式下仍暴露全部工具。
    • internal/tools/registry.go 当前无模式过滤,write_filereplace_in_fileapply_patchrun_shell 都会继续暴露。
  3. session.Plan 结构过于简单。
    • 目前只有 stepstatus,不足以支撑 UI、风险、验证、文件范围、执行接管。
  4. TUI 没有独立的计划展示区。
    • m.plan 能更新,但不会在主界面形成稳定的计划面板。
  5. Build 模式和 Plan 模式之间没有“计划接管”。
    • 计划生成后,没有明确的审批、恢复、继续执行、阻塞处理策略。

3. 设计结论

3.1 Plan Mode 的产品定义

Plan Mode 应定义为“规划优先工作流模式”,不是单纯的文案模式。

它需要同时包含三层能力:

  1. 模式层
    • 用户通过 Tab 进入 plan
    • 系统切换 prompt、工具权限、UI 展示和输入预期。
  2. 状态层
    • 计划以结构化状态保存到 session。
    • 计划可以被展示、恢复、继续、修订。
  3. 执行层
    • Build 模式可以接管既有计划继续实施。
    • 执行过程持续更新计划状态。

3.2 核心原则

  1. Tab 是唯一主入口,不增加 /plan 命令作为主交互。
  2. Plan 模式必须只读,不能只靠 prompt 自律。
  3. 计划必须结构化,不能只保留自然语言段落。
  4. Build 与 Plan 共享同一 session 和同一计划对象。
  5. 当前阶段不做多 agent,不做后台任务,不做复杂审批流引擎。
  6. 实现必须完全基于 Go 现有代码组织演进。

4. 总体架构

建议引入一个新的领域包:

internal/plan

建议职责:

  1. 计划领域模型
  2. 状态校验
  3. 旧 session 计划结构兼容迁移
  4. Plan Mode 与 Build Mode 的公共判定逻辑

建议改造后的主链路:

TUI(Tab切换)
  -> runner(mode=plan/build)
    -> prompt(mode block + plan block)
    -> tool registry(按 mode 过滤)
    -> update_plan / read-only shell / repo inspection
    -> session 持久化
    -> TUI 计划面板渲染

5. 状态模型设计

5.1 不要把“UI模式”和“计划阶段”混成一个字段

建议拆成两个概念:

  1. AgentMode
    • build
    • plan
  2. PlanPhase
    • none
    • drafting
    • ready
    • approved
    • executing
    • blocked
    • completed

这样能解决一个关键问题:

  • 用户当前可能处在 build 标签页
  • 但当前计划处于 approvedexecuting

这两个状态不能互相覆盖。

5.2 推荐的数据结构

建议新增 internal/plan/types.go

package plan

import "time"

type AgentMode string

const (
    ModeBuild AgentMode = "build"
    ModePlan  AgentMode = "plan"
)

type Phase string

const (
    PhaseNone      Phase = "none"
    PhaseDrafting  Phase = "drafting"
    PhaseReady     Phase = "ready"
    PhaseApproved  Phase = "approved"
    PhaseExecuting Phase = "executing"
    PhaseBlocked   Phase = "blocked"
    PhaseCompleted Phase = "completed"
)

type StepStatus string

const (
    StepPending    StepStatus = "pending"
    StepInProgress StepStatus = "in_progress"
    StepCompleted  StepStatus = "completed"
    StepBlocked    StepStatus = "blocked"
)

type RiskLevel string

const (
    RiskLow    RiskLevel = "low"
    RiskMedium RiskLevel = "medium"
    RiskHigh   RiskLevel = "high"
)

type Step struct {
    ID          string     `json:"id"`
    Title       string     `json:"title"`
    Description string     `json:"description,omitempty"`
    Status      StepStatus `json:"status"`
    Files       []string   `json:"files,omitempty"`
    Verify      []string   `json:"verify,omitempty"`
    Risk        RiskLevel  `json:"risk,omitempty"`
}

type State struct {
    Goal        string    `json:"goal,omitempty"`
    Summary     string    `json:"summary,omitempty"`
    Phase       Phase     `json:"phase,omitempty"`
    UpdatedAt   time.Time `json:"updated_at,omitempty"`
    Steps       []Step    `json:"steps,omitempty"`
    NextAction  string    `json:"next_action,omitempty"`
    BlockReason string    `json:"block_reason,omitempty"`
}

5.3 Session 改造建议

建议把 internal/session/store.go 里的:

Plan []PlanItem

改成:

Mode plan.AgentMode `json:"mode,omitempty"`
Plan plan.State     `json:"plan,omitempty"`

同时必须做兼容迁移:

  1. 旧 session 里的 plan[]PlanItem
  2. 新 session 里的 planplan.State

建议用自定义 UnmarshalJSON 兼容旧格式:

  • plan 是数组,则自动转成:
    • PhaseReady
    • Steps = old items
  • plan 是对象,则按新结构解析

这样不会破坏已有会话恢复。

5.4 Phase 状态流转规则

PlanPhase 不能只定义枚举,不定义触发器。

建议第一版明确采用下面这套规则:

  1. 进入 Plan 模式且当前无计划时
    • Phase = drafting
  2. Agent 在 Plan 模式下成功调用 update_plan
    • Phase = ready
  3. 用户从 Plan 切回 Build,且当前计划非空
    • 不自动视为 approved
    • 只保留 Phase = ready
  4. 用户在 Build 模式下明确表达“按计划执行”“继续执行当前计划”“开始实现”
    • Phase = approved
    • 紧接着进入执行回合时切到 Phase = executing
  5. 执行过程中存在 in_progress 步骤
    • Phase = executing
  6. 执行遇到阻塞,且已记录 blocked 步骤
    • Phase = blocked
  7. 所有步骤都为 completed
    • Phase = completed

这样做的原因是:

  1. Tab 切换只是工作视图切换,不应隐式代表用户审批
  2. “是否同意执行”必须由用户明确表达
  3. Phase 的变化必须和 session 中的真实执行状态一致

建议在 internal/plan/validate.go 中提供一个显式流转校验函数:

func CanTransition(from, to Phase) bool

第一版推荐允许的流转关系:

none -> drafting
drafting -> ready
ready -> drafting
ready -> approved
approved -> executing
executing -> blocked
executing -> completed
blocked -> drafting
blocked -> executing

6. 工具权限设计

6.1 Plan 模式必须做运行时硬约束

只靠 mode-plan.md 不够,因为当前 registry 仍会把所有工具暴露给模型。

建议新增:

type ToolPolicy struct {
    Mode plan.AgentMode
}

6.2 工具暴露策略

Build 模式允许

  • list_files
  • read_file
  • search_text
  • write_file
  • replace_in_file
  • apply_patch
  • update_plan
  • run_shell

Plan 模式允许

  • list_files
  • read_file
  • search_text
  • update_plan
  • run_shell 仅限只读命令

Plan 模式禁止

  • write_file
  • replace_in_file
  • apply_patch
  • run_shell 的修改型命令

6.3 推荐实现方式

internal/tools/registry.go 增加两个能力:

  1. DefinitionsForMode(mode plan.AgentMode) []llm.ToolDefinition
  2. ExecuteForMode(ctx, mode, name, rawArgs, execCtx)

其中:

  • DefinitionsForMode 决定模型“看得到什么工具”
  • ExecuteForMode 决定即便模型越界也“跑不起来”

这是双保险设计。

6.4 run_shell 的 Plan 限制

建议在 internal/tools/run_shell.go 中增加:

func assessShellCommandForMode(mode plan.AgentMode, command string) error

规则:

  1. build:沿用现有 allow / approval / blocked
  2. plan
    • 不采用“宽松只读判断”
    • 第一版直接采用“严格白名单 + 语法拦截”
    • 任何重定向、管道、脚本执行、解释器执行都直接拒绝

推荐第一版白名单仅允许:

  • ls
  • dir
  • pwd
  • cat
  • type
  • rg
  • grep
  • find
  • tree
  • git status
  • git diff
  • git log
  • go env
  • go list

Plan 模式下额外禁止:

  • |
  • >
  • >>
  • <
  • ;
  • &&
  • ||
  • bash
  • sh
  • pwsh
  • powershell
  • python
  • python3
  • node
  • 任何 .sh / .ps1 / .bat / .cmd 脚本直接执行

也就是说,Plan 模式下的 run_shell 不是“理论上只读”即可,而是“命令文本必须落在白名单内”。

原因很简单:

  1. 真实 shell 里很难通过语义分析证明一个命令绝对只读
  2. 管道、重定向、脚本执行会迅速突破分析边界
  3. 第一版应优先保证安全边界,而不是追求 shell 灵活性

建议新增:

func isPlanSafeCommand(command string) bool

它与现有 isReadOnlyCommand(...) 分离:

  • isReadOnlyCommand(...) 继续服务 Build 模式审批判断
  • isPlanSafeCommand(...) 专门服务 Plan 模式硬拒绝

后续如果需要更强隔离,再考虑:

  1. 独立只读沙盒
  2. 更低权限的执行用户
  3. Plan 模式下完全关闭 shell,只保留文件工具

7. Prompt 与 Runner 设计

7.1 保留现有 prompt 分层

当前 internal/agent/prompt.go 的分层是合理的,不建议推翻。

建议只做增强:

  1. 保留 mode-plan.md
  2. 保留 block-plan.md
  3. 增强 block-plan.md 的渲染内容

7.2 block-plan 渲染建议

当前只渲染:

- [status] step

建议改成:

[Current Execution Plan]
Goal: ...
Summary: ...
Phase: ...

- [in_progress] 拆分 runner 的 mode-aware tool policy
  files: internal/tools/registry.go, internal/agent/runner.go
  verify: go test ./internal/tools ./internal/agent
  risk: medium

这样模型在 Build 阶段更容易:

  1. 继续执行当前步骤
  2. 不偏离文件范围
  3. 在完成后更新计划

同时要避免长计划导致 prompt 膨胀。

建议在 block-plan.md 的实际渲染层,不直接注入完整 JSON,而是做压缩视图:

  1. 已完成步骤
    • 只保留标题简述,最多展示最近 3 条
  2. 当前 in_progressblocked 步骤
    • 展示完整信息
    • 包括 files / verify / risk / block_reason
  3. 后续待办步骤
    • 只展示标题列表
  4. 总步骤数超过 8 条时
    • 自动进入压缩渲染模式

推荐渲染示例:

[Current Execution Plan]
Goal: ...
Phase: executing

Recently completed:
- 拆分 plan domain model
- 接入 mode-aware registry

Current step:
- [in_progress] 给 TUI 增加计划侧栏
  files: internal/tui/model.go, internal/tui/styles.go
  verify: go test ./internal/tui

Upcoming:
- 接入 blocked -> re-plan 流程
- 补 session 兼容迁移测试

7.3 Plan 模式的强校验

建议在 internal/agent/runner.go 增加一条软约束:

mode == plan 且本轮结束时:

  1. 如果没有 tool_calls
  2. sess.Plan.Steps 为空

则本轮不应算成功规划,可返回一段系统化提示:

Plan mode requires a structured plan before finishing. Please restate the plan using update_plan.

第一阶段可以先做“软失败”。 第二阶段再考虑自动重试一次。

7.4 Build 模式的计划接管

Build 模式不需要一个新 executor。

建议继续复用当前 runner 循环,只增加以下约束:

  1. 若 session 中存在有效计划,则 prompt 必须带上当前计划
  2. 若有 in_progress 步骤,默认围绕该步骤推进
  3. 若 scope 变化明显,则先 update_plan 再继续

这样能在不重写执行框架的前提下,把 Plan 和 Build 接起来。

7.5 计划时效性与脱步校验

Build 与 Plan 共享同一份计划状态,必须考虑计划和真实仓库状态脱步的问题。

典型脱步场景:

  1. 用户在 IDE 外部手动改了代码
  2. 用户切到别的终端执行了测试或脚本
  3. 原计划中的 filesverifysummary 已不再匹配当前仓库事实

建议在 internal/plan/validate.go 中增加两类校验:

  1. 结构校验
    • 是否只有一个 in_progress
    • blocked 步骤是否带 BlockReason
    • completed 步骤是否仍排在当前步骤之前
  2. 时效性校验
    • 当前步骤引用的文件是否仍存在
    • 计划中的目标文件是否被外部修改
    • 当前步骤的验证命令是否仍可解析

推荐增加:

type ValidationResult struct {
    OK             bool
    Warnings       []string
    RequiresReplan bool
}

func ValidateState(state State) ValidationResult
func ValidateStepFreshness(state State, workspace string) ValidationResult

其中 ValidateStepFreshness(...) 第一版不必做复杂 diff,只需做最低成本校验:

  1. 文件是否存在
  2. 文件修改时间是否晚于 state.UpdatedAt
  3. 当前 verify 命令是否为空或明显非法

如果发现明显脱步:

  • 不直接继续执行
  • 先提示 agent 更新计划
  • 必要时切回 Phase = drafting

8. TUI 设计

8.1 目标

Plan Mode 的价值不能只体现在页脚颜色变化,必须让用户“看见计划”。

建议 UI 目标:

  1. Tab 切换后,立刻能感受到进入 planning 工作流
  2. 计划生成后,主界面能稳定显示计划状态
  3. 回到 Build 模式后,计划仍然可见
  4. 窄屏时也不破版

8.2 推荐布局

宽屏

当终端宽度 >= 120

  • 左侧:聊天时间线
  • 右侧:计划侧栏

比例建议:

  • 聊天区 70%
  • 计划区 30%

窄屏

当终端宽度 < 120

  • 聊天区在上
  • 计划卡片放在输入框上方或聊天区顶部

8.3 计划面板内容

建议新增:

  1. Mode Badge
    • BUILD
    • PLAN
  2. Plan Phase
    • drafting / ready / approved / executing / blocked / completed
  3. Goal / Summary
  4. Step List
    • 状态图标
    • 标题
    • 文件范围
    • 风险级别
  5. Next Action
  6. Blocked Reason(如存在)

8.4 推荐代码拆分

internal/tui/model.go 中新增:

  1. renderPlanSidebar() string
  2. renderPlanCompactCard() string
  3. renderMainLayout() string
  4. renderPlanStep(step plan.Step) string

internal/tui/styles.go 中新增:

  1. planPanelStyle
  2. planStepPendingStyle
  3. planStepActiveStyle
  4. planStepDoneStyle
  5. planStepBlockedStyle

8.5 模式切换行为

建议把 toggleMode() 改成:

  1. 修改本地 m.mode
  2. 写回 m.sess.Mode
  3. 立即 store.Save(m.sess)
  4. 更新 statusNote

推荐状态文案:

  • 切到 Plan:Switched to Plan mode. Read-only planning is active.
  • 切到 Build:Switched to Build mode. Implementation tools are active.

8.6 保留命令面板简洁性

当前测试明确不希望 command palette 出现 /plan 命令。

这个方向是对的,建议继续保持:

  1. 主入口仍是 Tab
  2. 不引入额外的 plan slash command
  3. 让模式切换足够直觉

9. 计划生命周期

建议定义以下用户路径:

9.1 生成计划

  1. 用户按 Tab 切到 Plan
  2. 输入需求
  3. Agent 用只读工具扫描仓库
  4. Agent 调用 update_plan
  5. TUI 右侧实时显示计划步骤
  6. Agent 最终输出:
    • Plan
    • Risks
    • Verification
    • Next Action

9.2 修订计划

  1. 用户留在 Plan
  2. 输入“把第 3 步拆细”“把风险高的步骤后置”
  3. Agent 重新调用 update_plan
  4. TUI 刷新计划

9.3 切回执行

  1. 用户按 Tab 回到 Build
  2. 用户输入“按当前计划执行第一步”或“按计划继续”
  3. Agent 在 Build 模式中带着计划继续执行
  4. 每完成一步,就更新 step 状态

9.4 阻塞处理

若执行中遇到缺信息、权限、测试失败等情况:

  1. 将当前 step 标为 blocked
  2. 写入 BlockReason
  3. 最终回答只问最小必要问题

9.5 Re-plan 路径

blocked 不是终点,必须定义回退闭环。

建议第一版明确支持两条路径:

  1. 人工驱动 re-plan
    • 用户按 Tab 回到 Plan
    • 输入“基于当前阻塞重新规划”
    • Agent 更新原计划
    • Phase: blocked -> drafting -> ready
  2. Agent 驱动 re-plan
    • Build 模式下检测到当前计划明显脱步或无法继续
    • Agent 先调用 update_plan
    • 仅对当前阻塞步骤及其紧邻后续步骤做局部重排
    • 将当前阻塞步骤改写、拆分,或补入少量必要前置步骤
    • 不在 Build 模式下整份推翻并重做全局计划
    • 再向用户明确说明“已根据阻塞情况局部重排计划,请确认是否继续执行”

第一版的边界建议是:

  1. Build 模式下允许 agent 更新计划
  2. 但不允许 agent 在 blocked 后自动继续大规模实施
  3. agent 完成 re-plan 后,应该停在“等待用户确认继续执行”

换句话说:

  • blocked 后可以自动“修计划”
  • 但不能自动“继续猛做”

这里要特别区分两种场景:

  1. Build 模式下的 re-plan
    • 只做执行期的小范围修正
    • 目标是让当前任务从阻塞中恢复
  2. Plan 模式下的 re-plan
    • 可以做全局重规划
    • 适合目标变化、范围变化、架构变化等大调整

一句话定义:

  • Build 模式的 re-plan = 局部重排计划
  • Plan 模式的 re-plan = 全局重规划

推荐状态流转:

executing -> blocked -> drafting -> ready -> approved -> executing

10. 推荐实施阶段

Phase 1:把 Plan Mode 变成真的模式

目标:

  1. 模式切换持久化
  2. 工具权限按 mode 过滤
  3. Plan 面板可见
  4. session 使用新计划结构

涉及文件:

  • internal/plan/* 新增
  • internal/session/store.go
  • internal/tools/registry.go
  • internal/tools/run_shell.go
  • internal/agent/runner.go
  • internal/tui/model.go
  • internal/tui/styles.go

Phase 2:让 Build 能接管计划

目标:

  1. Build 模式按当前计划推进
  2. 当前步骤高亮
  3. step 完成后自动更新状态
  4. blocked 状态可回显
  5. blocked 后可进入 re-plan 闭环

涉及文件:

  • internal/agent/runner.go
  • internal/agent/prompt.go
  • internal/agent/prompts/block-plan.md
  • internal/tui/model.go

Phase 3:增强计划质量

目标:

  1. update_plan 支持更丰富字段
  2. plan block 输出更稳定
  3. 计划生成失败有软校验
  4. 增加计划相关测试矩阵

涉及文件:

  • internal/tools/update_plan.go
  • internal/agent/prompts/mode-plan.md
  • internal/agent/runner.go
  • internal/plan/validate.go

11. 测试方案

11.1 单元测试

新增测试建议:

  1. internal/plan
    • 计划状态合法性校验
    • 旧格式 session 迁移
  2. internal/tools
    • Plan 模式下看不到写工具
    • Plan 模式下运行修改型 shell 被拒绝
    • Build 模式工具集合不受影响
  3. internal/agent
    • Plan 模式必须产生结构化计划
    • Build 模式会携带已有计划继续执行
  4. internal/tui
    • 宽屏显示侧栏
    • 窄屏显示 compact card
    • Tab 切换后 mode 会持久化

11.2 集成测试

建议补两个端到端场景:

  1. Tab -> Plan -> 生成计划 -> 更新 session -> 恢复会话
  2. Plan 生成计划 -> Tab 回 Build -> 按计划执行 -> 更新步骤状态
  3. Build 执行中 blocked -> update_plan 重规划 -> 用户确认后继续执行
  4. 长计划压缩渲染 -> prompt 仍保持可控长度

11.3 手工验收

必须覆盖:

  1. Plan 模式下要求改代码时,模型不能真的写文件
  2. Plan 模式下可以读文件、搜文本、跑只读 shell
  3. Build 模式下仍然能正常编辑和验证
  4. 恢复旧 session 不崩溃
  5. 计划面板在 Windows 终端宽窄变化下可读
  6. Plan 模式下脚本执行、重定向、管道命令都会被拒绝
  7. 切到 Build 但未明确批准前,不会隐式开始执行计划

12. 最终推荐方案

对于当前 ByteMind,最合适的落地方式不是重写一个“planner executor 系统”,而是在现有 runner + session + TUI + prompts + tools 架构上做四个关键升级:

  1. 引入 internal/plan 作为计划领域模型
  2. Tab 切换真正驱动工具权限与 session 模式
  3. 让计划在 TUI 中稳定可见,而不是只存在于消息流里
  4. 让 Build 模式能够消费并持续更新已有计划

一句话总结:

ByteMind 的 Plan Mode 最佳实现路径,是把当前已有的“prompt层计划能力”升级成“模式切换 + 结构化状态 + 工具约束 + 可视化面板 + 执行接管”的完整 Go 工作流。

Clone this wiki locally