一、背景与目标
1.1 现状问题
当前 NeoCode 只有一个"Build"模式:Agent 自主决定是分析代码、制定方案还是直接写文件。这种单模式存在以下问题:
- 缺乏规划可见性:用户无法预先看到 Agent 的执行计划,只能在执行后才知道它做了什么
- 上下文浪费:Plan 阶段的分析对话混入 Build 阶段的执行对话,加速上下文饱和
- 偏离风险:Agent 可能在执行中偏离最初方案,用户难以控制
- 审批粒度粗:只能在每次写操作时审批,无法对整体方案进行审批
1.2 目标
引入 Plan 模式(只读分析 + 生成计划)和 Build 模式(按批准的计划执行),实现:
- 用户可先审阅计划,再决定是否执行
- Plan 和 Build 的上下文可隔离,减少噪声
- Agent 执行时有明确的计划约束,减少偏离
二、核心设计原则
- 主流程不变:
TUI → Gateway → Runtime → Provider → Tools 主链路保持可用
- 权限硬约束:Plan 模式不是"提示词建议不写",而是"工具层面真的不能写"
- 显式 Handoff:Plan 产物必须显式传递,不能依赖上下文记忆
- 状态集中:模式状态、计划内容由
session 统一管理,不分散到 UI
三、方案总览
┌─────────────────────────────────────────────────────────────────────────────┐
│ 双模式架构总览 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 用户输入 │
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ /plan │ │ 计划审阅 │ │ /build │ │
│ │ 命令/按钮 │───→│ 批准/修改 │───→│ 命令/按钮 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ TUI / Gateway │ │
│ │ • 模式切换 UI │ │
│ │ • 计划展示面板 │ │
│ │ • 审批交互(批准/修改/拒绝) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Runtime │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Plan Run │ │ 模式切换 │ │ Build Run │ │ │
│ │ │ • 只读工具 │───→│ • 保存计划 │───→│ • 全量工具 │ │ │
│ │ │ • 生成计划 │ │ • 切换提示词 │ │ • 执行计划 │ │ │
│ │ │ • 无verify │ │ │ │ • 有verify │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Tools Manager │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ Registry(全量工具,Bootstrap 注册,永不改变) │ │ │
│ │ │ • filesystem_read/write/edit/grep/glob │ │ │
│ │ │ • bash, webfetch, todo_write, spawn_subagent │ │ │
│ │ │ • memo_*, mcp.* │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ DefaultManager(动态过滤,运行时切换) │ │ │
│ │ │ • Plan 模式: ListAvailableSpecs() 过滤掉写工具 │ │ │
│ │ │ • Build 模式: ListAvailableSpecs() 返回全部工具 │ │ │
│ │ │ • Execute() 保持原有权限引擎检查 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Session Store │ │
│ │ • AgentMode: "plan" | "build" │ │
│ │ • CurrentPlanID → PlanArtifact │ │
│ │ • Plans[]: 计划历史(可选) │ │
│ │ • Messages: 对话历史(Plan 和 Build 共享) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
四、关键问题与可选方案
问题 1:工具注册与模式切换时工具可用性
问题描述
Bootstrap 时一次性注册所有工具到 Registry。Plan 模式需要隐藏写工具,Build 模式需要恢复。如何在运行时动态控制模型"看到"哪些工具?
可选方案
方案 A:Manager 层动态过滤
设计:Registry 保持全量注册不动,在 DefaultManager.ListAvailableSpecs() 中根据当前模式过滤。
type DefaultManager struct {
// ... 现有字段
modeMu sync.RWMutex
mode AgentMode // "plan" | "build"
}
func (m *DefaultManager) SetMode(mode AgentMode) {
m.modeMu.Lock()
defer m.modeMu.Unlock()
m.mode = mode
}
func (m *DefaultManager) ListAvailableSpecs(ctx context.Context, input SpecListInput) ([]providertypes.ToolSpec, error) {
allSpecs, err := m.executor.ListAvailableSpecs(ctx, input)
if err != nil { return nil, err }
m.modeMu.RLock()
mode := m.mode
m.modeMu.RUnlock()
if mode == AgentModePlan {
return filterPlanModeSpecs(allSpecs), nil // 过滤掉写工具
}
return allSpecs, nil // Build 模式返回全部
}
Plan 模式白名单():
filesystem_read_file ✅
filesystem_grep ✅
filesystem_glob ✅
webfetch ✅
memo_recall ✅
memo_list ✅
filesystem_write_file ❌
filesystem_edit ❌
bash ❌
todo_write ❌
spawn_subagent ❌
memo_remember ❌
memo_remove ❌
缺点:
- 如果绕过 Manager 直接调用 Registry,过滤失效(但现有代码不走这个路径)
- 这里还有个问题,就是如果调用mcp工具,有些是write类型的,有些是read类型的,但是采取以上方案,现在的mcp是没有专门一个标记类别的字段?之前在遇到一些这个问题时,解决的方案好像都是临时打补丁区分类型,没有真正解决这个问题。
方案 B:Registry 层支持多视图
设计:Registry 内部维护多个工具子集,切换模式时切换视图。
type Registry struct {
tools map[string]Tool
views map[AgentMode]map[string]Tool // 预构建的视图
}
func (r *Registry) BuildViews() {
r.views[AgentModePlan] = filterTools(r.tools, planModeFilter)
r.views[AgentModeBuild] = r.tools // 全量
}
func (r *Registry) GetSpecsForMode(mode AgentMode) []providertypes.ToolSpec {
view := r.views[mode]
// ... 返回 view 的 specs
}
缺点:
- Registry 职责变重(存储 + 视图管理)
- 需要改动 Registry 核心结构
- 与现有 MCP 动态工具集成更复杂
方案 C:运行时重新注册工具
设计:切换模式时,销毁旧 Registry,创建新 Registry 重新注册对应工具。
func switchToPlanMode() {
oldRegistry := toolManager.GetRegistry()
newRegistry := tools.NewRegistry()
newRegistry.Register(filesystem.NewRead(cfg.Workdir))
newRegistry.Register(filesystem.NewGrep(cfg.Workdir))
// ... 只注册读工具
toolManager.SetRegistry(newRegistry)
}
缺点:
- 需要销毁和重建 Registry,有状态丢失风险
- MCP 工具需要重新连接
- 与现有依赖注入架构冲突(Registry 通过构造函数注入)
- 性能差,切换模式有延迟
问题 2:Plan 模式的主流程改动
问题描述
当前主流程是 plan → execute → verify 循环。Plan 模式是否还需要 execute?如果不需要,主流程怎么改?
分析
Plan 模式仍然需要 execute,但 execute 的内容不同:
| 阶段 |
Build 模式 |
Plan 模式 |
| Plan(模型思考) |
模型自主决定读/写/执行 |
模型只能决定读 |
| Execute(工具执行) |
执行写文件、bash、edit |
执行 read_file、grep、glob |
| Verify(结果验证) |
acceptance 检查、测试 |
不需要 |
| 终止条件 |
任务完成/失败/用户停止 |
计划生成完成 |
可选方案
方案 A:统一 ReAct 循环,模式分支在终止条件
设计:Run() 方法的整体循环结构不变,只在两个地方做模式分支:
- 工具暴露:
prepareTurnBudgetSnapshot() 中 ListAvailableSpecs() 已按模式过滤
- 终止条件:无 tool calls 时,Plan 模式直接返回计划,Build 模式进入 verify
func (s *Service) Run(ctx context.Context, input UserInput) error {
// ... 原有初始化
mode := resolveAgentMode(input, state.session)
for turn := 0; ; turn++ {
// ... 原有预算、provider 调用(完全不变)
if !hasToolCalls {
// ========== 模式分支点 1:终止条件 ==========
if mode == AgentModePlan {
// Plan 模式:提取计划,保存,结束
planContent := extractPlanFromResponse(turnOutput.assistant)
if planContent != "" {
savePlan(state.session, planContent)
emitEvent(EventPlanCompleted, planContent)
return nil // Plan 模式结束,不 verify
}
// 计划不完整,继续循环
continue
}
// Build 模式:原有 verify/acceptance 逻辑(完全不变)
acceptanceDecision, err := s.beforeAcceptFinal(...)
// ...
}
// ========== Execute(两种模式都需要)==========
// 但 Plan 模式下,模型只能调用读工具(已被过滤)
summary, err := s.executeAssistantToolCalls(ctx, &state, snapshot, turnOutput.assistant)
// ... 原有 progress 评估(完全不变)
}
}
优点:
- 主流程改动最小,只有 2 个分支点
- Plan 模式仍然利用现有的 execute 并发、错误处理、事件发射
- 代码复用最高
缺点:
- Plan 模式会走一遍完整的 tool execution 流程(虽然只执行读工具)
- 需要确保 Plan 模式的 progress 评估不会误判"无进展"
方案 B:Plan 模式独立循环(不经过 execute)
设计:Plan 模式完全绕过 executeAssistantToolCalls(),自己实现一个简化的读工具循环。
func (s *Service) RunPlanMode(ctx context.Context, input UserInput) error {
for turn := 0; ; turn++ {
// 调用 provider(只传入读工具 specs)
response := s.callProvider(ctx, readOnlyTools)
if !hasToolCalls {
// 检查是否生成了计划
if isPlanComplete(response) {
savePlan(response)
return nil
}
continue
}
// 简化的读工具执行(不经过完整的 executeAssistantToolCalls)
for _, call := range response.ToolCalls {
result := s.executeReadTool(ctx, call) // 直接执行,不经过权限审批
appendToMessages(result)
}
}
}
优点:
- Plan 模式代码完全独立,不会意外执行写工具
- 可以针对 Plan 模式优化(如不需要权限审批、不需要并发控制)
缺点:
- 代码重复:两个独立的 ReAct 循环
- 维护成本高:改一个逻辑要改两处
- 与现有事件系统、progress 系统需要重新集成
方案 C:Plan 模式作为特殊 Tool
设计:不区分 Plan/Build 模式,而是提供一个 generate_plan 工具。Agent 在 Build 模式中可以调用这个工具来生成计划。
// Agent 调用 generate_plan 工具
// 工具内部:只读分析 → 生成计划 → 返回计划内容
// 用户审批后,计划作为消息注入,Agent 继续执行
优点:
- 完全不需要模式概念,架构最简单
- 计划生成是 Agent 自主决定的行为
缺点:
- 无法强制"先计划后执行"的工作流
- 用户无法预先知道 Agent 会生成计划
- 计划审批流程难以集成(工具调用是自动的,没有用户暂停点)
问题 3:会话状态改动
问题描述
模式状态、计划内容放在哪里?runState(单次运行临时状态)还是 Session(持久化状态)?
分析
| 状态 |
生命周期 |
存放位置 |
AgentMode |
跨 Run 持久化 |
Session |
PlanContent |
跨 Run 持久化 |
Session |
PlanApproved |
跨 Run 持久化 |
Session |
CurrentPlanID |
跨 Run 持久化 |
Session |
原因:用户可能在 Plan 模式生成计划后,关闭 TUI,明天再打开进入 Build 模式。这些状态必须持久化。
可选方案
方案 A:扩展 Session 结构
设计:在 Session 和 SessionHead 中增加模式相关字段。
// internal/session/store.go
type AgentMode string
const (
AgentModePlan AgentMode = "plan"
AgentModeBuild AgentMode = "build"
)
type PlanStatus string
const (
PlanStatusDraft PlanStatus = "draft"
PlanStatusApproved PlanStatus = "approved"
PlanStatusRejected PlanStatus = "rejected"
PlanStatusExecuting PlanStatus = "executing"
PlanStatusCompleted PlanStatus = "completed"
)
type PlanArtifact struct {
ID string
Version int
Title string
Content string // 完整计划(Markdown)
Summary string // 精简摘要(用于上下文注入)
Status PlanStatus
CreatedAt time.Time
ApprovedAt *time.Time
ExecutedAt *time.Time
CompletedAt *time.Time
}
type Session struct {
ID string
Title string
Provider string
Model string
CreatedAt time.Time
UpdatedAt time.Time
Workdir string
TaskState TaskState
ActivatedSkills []SkillActivation
TodoVersion int
Todos []TodoItem
Messages []providertypes.Message
TokenInputTotal int
TokenOutputTotal int
HasUnknownUsage bool
// 新增字段
AgentMode AgentMode // 当前模式
CurrentPlanID string // 当前活跃计划 ID
Plans []PlanArtifact // 计划历史(可选,MVP 可只存 CurrentPlan)
}
type SessionHead struct {
// ... 原有字段
AgentMode AgentMode
CurrentPlanID string
Plans []PlanArtifact
}
数据库迁移:SQLite schema 从 v2 升级到 v3,新增 agent_mode、current_plan_id、plans 字段。
优点:
- 状态集中,符合 NeoCode "状态由 runtime/session 统一管理"的原则
- 持久化,支持跨会话的模式切换
- 计划历史可追溯
缺点:
- 需要数据库 schema 迁移
- Session 结构变重
方案 B:独立 Plan Store
设计:计划不放在 Session 中,而是独立的存储。
type PlanStore interface {
SavePlan(ctx context.Context, sessionID string, plan PlanArtifact) error
GetPlan(ctx context.Context, planID string) (PlanArtifact, error)
ListSessionPlans(ctx context.Context, sessionID string) ([]PlanArtifact, error)
}
Session 只存 CurrentPlanID,计划内容在独立 Store 中。
优点:
- Session 结构不变轻量
- 计划可以独立管理(版本、搜索、导出)
缺点:
- 引入新的存储层,复杂度增加
- Plan 和 Session 的一致性需要维护
- 与现有 Session 事务机制需要集成
方案 C:计划作为特殊消息
设计:不改动 Session 结构,计划内容作为一条特殊消息存入 Session.Messages。
// Plan 完成时,插入一条特殊消息
planMessage := providertypes.Message{
Role: providertypes.RoleSystem,
Parts: []providertypes.ContentPart{
providertypes.NewTextPart("[PLAN]\n" + planContent + "\n[/PLAN]"),
},
}
// Build 模式启动时,扫描 Messages 找到最新的 [PLAN] 标记
优点:
缺点:
- 计划被上下文压缩影响(compact 时可能被摘要或删除)
- 难以支持计划版本、状态管理
- 解析计划需要扫描全部消息,效率低
问题 4:计划文件的必要性与实现
问题描述
计划内容必须持久化吗?其他 Coding Agent 怎么做?计划如何在多轮对话后保持可识别?
调研结论
| 工具 |
计划形式 |
持久化 |
handoff 方式 |
| Claude Code |
Markdown 文件(.claude/plans/) |
✅ 文件系统 |
文件路径引用 |
| Cline |
内联对话中的结构化计划 |
❌ 仅上下文 |
上下文记忆 |
| Roo Code |
Todo 列表 + 对话 |
⚠️ 半持久化 |
Todo 状态 |
| VS Code Copilot |
内联对话 |
❌ 仅上下文 |
上下文记忆 |
| OpenCode (apiad) |
结构化文档(.playground/) |
✅ 文件系统 |
文件路径引用 |
| Codex (OpenAI) |
内联对话 |
❌ 仅上下文 |
上下文记忆 |
核心问题:上下文饱和
如果计划只在上下文中:
- Plan 阶段的分析对话占用大量 token
- 进入 Build 后,这些对话成为噪声
- 模型可能遗忘计划细节,执行偏离
- 用户无法编辑计划
可选方案
方案 A:Session 内 Plan Artifact + System Prompt 注入
设计:
- 计划内容保存在
Session.Plans[] 中(持久化)
- Build 模式启动时,将计划内容直接注入 system prompt
- 模型始终通过 system prompt "看到"计划,不受上下文饱和影响
// Build 模式启动时,构建请求
func buildBuildModeRequest(state *runState, plan PlanArtifact) providertypes.GenerateRequest {
systemPrompt := buildBaseSystemPrompt(state)
// 注入计划到 system prompt(最高优先级)
systemPrompt += fmt.Sprintf(`
## Approved Plan(MUST FOLLOW)
%s
## Execution Rules
1. Execute the plan step by step
2. Do not deviate without user approval
3. Report progress after each step
`, plan.Content)
return providertypes.GenerateRequest{
Model: state.session.Model,
SystemPrompt: systemPrompt,
Messages: state.session.Messages,
Tools: allToolSpecs, // Build 模式:全部工具
}
}
Plan 在 Session 中的存储:
type Session struct {
// ...
AgentMode AgentMode
CurrentPlanID string
Plans []PlanArtifact
}
type PlanArtifact struct {
ID string
Content string // 完整计划
Summary string // 精简摘要(用于快速展示)
Status PlanStatus
CreatedAt time.Time
ApprovedAt *time.Time
}
优点:
- 计划通过 system prompt 注入,最高优先级,模型不会遗忘
- 持久化在 Session 中,跨 Run 可用
- 支持计划版本历史
- 不依赖文件系统,与现有 SQLite 存储一致
缺点:
- 长计划会占用 system prompt 的 token(但 plan 通常比分析对话短)
方案 B:文件系统 Plan + 路径引用
设计:计划保存为文件(如 .neocode/plans/{session_id}/{plan_id}.md),Build 时通过工具让模型读取。
// Plan 完成时保存到文件
func savePlanToFile(sessionID string, plan PlanArtifact) (string, error) {
path := filepath.Join(workdir, ".neocode", "plans", sessionID, plan.ID+".md")
// 写入文件
return path, nil
}
// Build 模式 system prompt 中注入文件路径
systemPrompt += `
## Approved Plan
Read the plan from: .neocode/plans/{session_id}/{plan_id}.md
You MUST follow this plan.
`
优点:
- 用户可以直接编辑文件
- 计划可以版本控制(git)
- 不占用上下文 token
缺点:
- 模型需要额外调用
read_file 读取计划,增加一轮工具调用
- 文件可能被用户误删/修改
- 需要管理文件生命周期(清理过期计划)
- 与现有纯 SQLite 存储架构不一致
方案 C:上下文内联 + 定期提醒
设计:计划不单独存储,而是作为对话的一部分。每隔几轮,插入一条提醒消息。
// 每 5 轮 Build 执行后,插入计划提醒
if turn%5 == 0 {
reminder := providertypes.Message{
Role: providertypes.RoleSystem,
Parts: []providertypes.ContentPart{
providertypes.NewTextPart("Reminder: Current plan is to..."),
},
}
appendToMessages(reminder)
}
优点:
缺点:
- 计划内容易丢失(compact 时)
- 需要频繁提醒,浪费 token
- 模型仍可能偏离
五、完整数据流
5.1 Plan 模式数据流
用户: "/plan 实现用户登录功能"
│
▼
TUI: 发送 PrepareInput{Text: "实现用户登录功能", SessionID: "sess_123"}
│
▼
Gateway: → Runtime.Submit()
│
▼
Runtime:
1. 检查 Session.AgentMode,当前为 ""(默认 Build)
2. 用户输入以 "/plan" 开头,切换模式
3. Session.AgentMode = "plan"
4. toolManager.SetMode(AgentModePlan) // 过滤写工具
5. 调用 Run()
│
▼
Run() ReAct 循环:
Turn 0:
- prepareTurnBudgetSnapshot(): ListAvailableSpecs() 返回只读工具
- callProvider(): 模型收到只读工具 specs
- 模型调用: read_file("auth.go"), grep("login"), glob("*.go")
- executeAssistantToolCalls(): 执行读工具
- 结果回灌 Messages
Turn 1-3:
- 模型继续分析代码结构
- 调用更多读工具
Turn 4:
- 模型不再调用工具,返回计划文本
- extractPlanFromResponse(): 提取计划内容
- savePlan(): 保存到 Session.CurrentPlan
- emit EventPlanCompleted
- return nil // Plan 模式结束
│
▼
Runtime: Session 持久化到 SQLite
│
▼
TUI: 展示计划内容,等待用户审批
5.2 模式切换数据流
用户: 点击"批准计划"按钮
│
▼
TUI: 发送 ModeSwitchInput{SessionID: "sess_123", Action: "approve_plan"}
│
▼
Gateway: → Runtime.ApprovePlan()
│
▼
Runtime:
1. 加载 Session
2. Session.CurrentPlan.Status = "approved"
3. Session.CurrentPlan.ApprovedAt = now()
4. Session.AgentMode = "build" // 切换模式
5. toolManager.SetMode(AgentModeBuild) // 恢复全部工具
6. 可选:自动触发 Build Run
│
▼
Session 持久化
5.3 Build 模式数据流
用户: "/build" 或自动触发
│
▼
Runtime.Run():
1. 检查 Session.AgentMode = "build"
2. 检查 Session.CurrentPlan.Status = "approved"
3. prepareTurnBudgetSnapshot():
- ListAvailableSpecs() 返回全部工具
- systemPrompt 注入计划内容
4. callProvider():
- 模型收到全部工具 + 计划 system prompt
5. ReAct 循环执行计划...
6. 最终 verify/acceptance
│
▼
Session.CurrentPlan.Status = "completed"
Session.AgentMode = ""(重置为默认)
一、背景与目标
1.1 现状问题
当前 NeoCode 只有一个"Build"模式:Agent 自主决定是分析代码、制定方案还是直接写文件。这种单模式存在以下问题:
1.2 目标
引入 Plan 模式(只读分析 + 生成计划)和 Build 模式(按批准的计划执行),实现:
二、核心设计原则
TUI → Gateway → Runtime → Provider → Tools主链路保持可用session统一管理,不分散到 UI三、方案总览
四、关键问题与可选方案
问题 1:工具注册与模式切换时工具可用性
问题描述
Bootstrap 时一次性注册所有工具到 Registry。Plan 模式需要隐藏写工具,Build 模式需要恢复。如何在运行时动态控制模型"看到"哪些工具?
可选方案
方案 A:Manager 层动态过滤
设计:Registry 保持全量注册不动,在
DefaultManager.ListAvailableSpecs()中根据当前模式过滤。Plan 模式白名单():
filesystem_read_file✅filesystem_grep✅filesystem_glob✅webfetch✅memo_recall✅memo_list✅filesystem_write_file❌filesystem_edit❌bash❌todo_write❌spawn_subagent❌memo_remember❌memo_remove❌缺点:
方案 B:Registry 层支持多视图
设计:Registry 内部维护多个工具子集,切换模式时切换视图。
缺点:
方案 C:运行时重新注册工具
设计:切换模式时,销毁旧 Registry,创建新 Registry 重新注册对应工具。
缺点:
问题 2:Plan 模式的主流程改动
问题描述
当前主流程是
plan → execute → verify循环。Plan 模式是否还需要 execute?如果不需要,主流程怎么改?分析
Plan 模式仍然需要 execute,但 execute 的内容不同:
可选方案
方案 A:统一 ReAct 循环,模式分支在终止条件
设计:
Run()方法的整体循环结构不变,只在两个地方做模式分支:prepareTurnBudgetSnapshot()中ListAvailableSpecs()已按模式过滤优点:
缺点:
方案 B:Plan 模式独立循环(不经过 execute)
设计:Plan 模式完全绕过
executeAssistantToolCalls(),自己实现一个简化的读工具循环。优点:
缺点:
方案 C:Plan 模式作为特殊 Tool
设计:不区分 Plan/Build 模式,而是提供一个
generate_plan工具。Agent 在 Build 模式中可以调用这个工具来生成计划。优点:
缺点:
问题 3:会话状态改动
问题描述
模式状态、计划内容放在哪里?
runState(单次运行临时状态)还是Session(持久化状态)?分析
AgentModeSessionPlanContentSessionPlanApprovedSessionCurrentPlanIDSession原因:用户可能在 Plan 模式生成计划后,关闭 TUI,明天再打开进入 Build 模式。这些状态必须持久化。
可选方案
方案 A:扩展 Session 结构
设计:在
Session和SessionHead中增加模式相关字段。数据库迁移:SQLite schema 从 v2 升级到 v3,新增
agent_mode、current_plan_id、plans字段。优点:
缺点:
方案 B:独立 Plan Store
设计:计划不放在 Session 中,而是独立的存储。
Session 只存
CurrentPlanID,计划内容在独立 Store 中。优点:
缺点:
方案 C:计划作为特殊消息
设计:不改动 Session 结构,计划内容作为一条特殊消息存入
Session.Messages。优点:
缺点:
问题 4:计划文件的必要性与实现
问题描述
计划内容必须持久化吗?其他 Coding Agent 怎么做?计划如何在多轮对话后保持可识别?
调研结论
.claude/plans/).playground/)核心问题:上下文饱和
如果计划只在上下文中:
可选方案
方案 A:Session 内 Plan Artifact + System Prompt 注入
设计:
Session.Plans[]中(持久化)Plan 在 Session 中的存储:
优点:
缺点:
方案 B:文件系统 Plan + 路径引用
设计:计划保存为文件(如
.neocode/plans/{session_id}/{plan_id}.md),Build 时通过工具让模型读取。优点:
缺点:
read_file读取计划,增加一轮工具调用方案 C:上下文内联 + 定期提醒
设计:计划不单独存储,而是作为对话的一部分。每隔几轮,插入一条提醒消息。
优点:
缺点:
五、完整数据流
5.1 Plan 模式数据流
5.2 模式切换数据流
5.3 Build 模式数据流