Plan 模式与目标
Shift+Tab 与 /plan 怎样让会话进入只读规划、计划如何审批落地;/goal 设定的线程目标如何记账、判定完成,并驱动回合自动续跑。
有两类任务不适合“一问一答”:一类要先想清楚、经用户批准再动手,另一类要跨很多回合持续推进,直到能证明做完。MiniMax Code 分别用 Plan 模式和线程目标(Thread Goal)处理。两者都记在会话上,由 local-runtime-v2 的会话与回合系统承载;模型通过扩展贡献的提醒与工具感知它们。
Plan 模式:怎么用
- 在输入框按
Shift+Tab(packages/tui/src/tui/shell/keybindings.ts:406),或者用/plan on、/plan off、/plan status、/plan view(packages/tui/src/tui/commands/catalog.ts:169)。按键不会立刻切换,只是给下一条消息挂上意图,提示栏显示 “Plan Mode · next message enters Plan”(packages/tui/src/tui/controller/interaction/plan-mode-flow.ts:226),再按一次就撤销。 - 意图随消息以
plan-entry或plan-exit提交,在消息准入的数据库事务里生效(packages/local-runtime-v2/src/service/plan/questionnaire/admission.ts:99):退出直接把会话切回默认模式;进入时若还有待答的问卷就拒绝,否则先准备好新的计划草稿再切到 Plan 模式(admission.ts:171)。 - 模型也能主动申请进入,但工具
EnterPlanMode只在beta.desktopPlanModeAgentEntry为true时出现,而它默认关闭(packages/config/src/config.ts:628、packages/local-runtime-v2/src/service/plan/plan-entry-policy.ts:11)。beta.desktopPlanMode: false是新进入的急停开关,已经在 Plan 里的会话仍可退出。 - 审批计划时,TUI 给三个选项:同意并开始实施、暂不处理、补充意见后修订(
packages/tui/src/tui/features/interaction/plan-review-panel.ts:17)。
进入之后:能做什么、不能做什么
会话的 interactionMode 为 plan 时,回合预检会给装配上下文加一个 plan 字段,带上规范计划文件的路径(packages/local-runtime-v2/src/service/turn-system/agent-host/preparation/turn-preflight.ts:106)。这个文件固定在会话目录下的 artifacts/plan.md(packages/local-runtime-v2/src/service/session-system/sessions/support/plan-document.ts:12、plan-document.ts:90);每次新进入 Plan 模式,旧文件先挪成 .plan.md.plan-entry-backup,进入失败就还原(plan-document.ts:14)。
约束分软硬两层。软的一层是 planModeExtension(packages/agent-extension/src/plan-mode.ts:105)贡献的每回合提醒,优先级 1000(plan-mode.ts:126)。提醒允许一切调查手段——读仓库、只读的 Shell 命令、外部检索、浏览器、技能、委派出去的研究任务,跑已有的构建和测试也算调查——但禁止创建、修改、删除、发布、发送任何东西,唯一例外是那个计划文件(plan-mode.ts:184):
The sole mutation exception is writing or editing the canonical Plan file at
exactly this path:
${canonicalPath}
No other file may be created or modified, and Git state must not change.
If a tool is denied, switch to another read or investigation path. When two
distinct approaches to the same question have been blocked, stop pursuing
that question: converge on the evidence already available and record the
remaining uncertainty in the Plan; do not keep looping.提醒还规定计划文件的正文要以非空的 ## Summary 开头,内容按需覆盖目标与范围、现状与受影响的文件、方案与端到端流程、状态与失败恢复、测试与验收、未决问题;只在对话里写计划不算数,必须先落盘再调用 ExitPlanMode。
硬的一层是工具守卫(packages/local-runtime-v2/src/service/plan/tool-guard.ts:47):write、edit、append、apply_patch、multiedit、notebook_edit 一律拦下,只放行目标恰好是规范计划文件的 write 与 edit(tool-guard.ts:5);Plan 模式里再调 EnterPlanMode 也会被拒(tool-guard.ts:62)。守卫和目标预算守卫组合后挂进回合执行器(packages/local-runtime-v2/src/services.ts:973)。注意这份清单里没有 bash:从代码看,“Shell 只许读”只靠提醒约束,再加上常规的权限审批(见权限模式与规则)。
两个生命周期工具按场合出现:EnterPlanMode 只在默认模式、且允许模型主动进入时注册,ExitPlanMode 只在 Plan 模式的回合里注册(plan-mode.ts:124)。二者都在结果里带上 terminate: true,结束当前回合等用户决定(plan-mode.ts:256)。默认模式的系统提示词里还会追加一段 PLAN_MODE_GUIDANCE(plan-mode.ts:10),教模型判断什么时候该申请 Plan:要交付实施方案的软件任务,或用户要求先审批再执行的计划;同样只在允许主动进入时才加(plan-mode.ts:113)。
计划怎样落地与退出
模型调用 ExitPlanMode 后,exitFromAgent(packages/local-runtime-v2/src/service/plan/application.ts:256)要求会话确实在 Plan 模式、队列已清空(application.ts:270),然后读取计划文件的冻结快照(超过 512 KiB 截断并注明,application.ts:19),连同路径一起放进一份 Plan 审批问卷。用户的选择由问卷处理器分派(packages/local-runtime-v2/src/service/plan/questionnaire/questionnaire-action-handler.ts:73):
| 用户选择 | 结果 |
|---|---|
| 同意并开始实施 | approvePlan(application.ts:375)把一条实施消息插到队首,来源记为 questionnaire,用审批请求 ID 派生幂等键(application.ts:471);会话切回默认模式 |
| 暂不处理 | 什么也不改,会话留在 Plan 模式 |
| 补充意见 | 意见作为回复送回,接着开一个续跑回合,仍在 Plan 模式里修订 |
| 问卷被撤销 | 回到默认模式,不实施 |
实施消息的开头是一段固定说明,后面紧跟审批时冻结的计划正文(application.ts:20):
const APPROVED_PLAN_MARKDOWN_INSTRUCTION = `Implement the approved plan below.
Treat the Markdown after the "Approved plan:" line as the immutable plan snapshot for this implementation turn; it is authoritative.
Do not read the canonical Plan file and do not re-derive the plan from anywhere else.
If implementation reveals the plan is infeasible or materially wrong — a premise fails, a referenced component does not exist, or the work is fundamentally riskier than planned — stop and report the mismatch to the user instead of redesigning on your own.
Mechanical adjustments that keep the plan's decisions intact do not require stopping.
Approved plan:
`;实施回合看到的是审批那一刻的快照,而不是磁盘上的文件,审批之后对文件的改动不会混进来。模型主动进入的路径与此对称:EnterPlanMode 打开一份进入确认问卷(application.ts:208),用户确认后 confirmEntry 准备草稿并切到 Plan 模式(application.ts:316),拒绝则留在默认模式,两种情况都会接着开一个续跑回合(questionnaire-action-handler.ts:42)。用户任何时候都能用 Shift+Tab 直接退出,这条路不触发实施。
目标:怎么用
/goal(catalog.ts:154)的用法写在帮助文本里(packages/tui/src/application/thread-goal-command.ts:19):
| 命令 | 作用 |
|---|---|
/goal | 查看当前目标及其状态 |
/goal <objective> | 设定一个跨回合持续推进的目标 |
/goal <objective> budget=50K | 同时设定 token 预算 |
/goal budget=50K、/goal budget=clear | 修改或取消预算 |
/goal edit、/goal pause、/goal resume、/goal clear | 编辑目标、暂停续跑、恢复、删除 |
一个会话同时只有一个目标。功能总开关是 beta.threadGoal:注释说外部正式版默认关闭(config.ts:481),但特性表把它的默认可见范围定为 online(config.ts:641),按 resolveBetaFeature 的规则,所有渠道都默认开启,以代码为准。config.yaml 里的 goal 一段(packages/config/src/goal-config.ts:62)可以调:verification(验证方式,没有默认值)、budget.defaultTokens、budget.defaultMainTurns、budget.defaultActiveSeconds(可选的全局上限)、budget.graceSteps(预算用尽后给几次“立刻总结”的提示,默认 1,最多 3)、breaker.repeatedReplyLimit(默认 3)、verifier.repeatedNotMetLimit(默认 5)等。
模型一侧有三个工具(packages/agent-modules/goal/src/tool-defs.ts:22、tool-defs.ts:48、tool-defs.ts:153):create_goal 只在用户明确要求时使用;get_goal 读取当前目标;update_goal 有两种模式,status 提议 complete 或 blocked,token_budget 修改预算。后者只能出现在用户亲自发起的回合里,自动续跑、定时任务、子 Agent 调用都会被守卫拦下(packages/local-runtime-v2/src/application/agent/goal-budget-tool-policy.ts:6)。
目标的状态
六种状态定义在 THREAD_GOAL_STATUSES(packages/agent-modules/goal/src/types.ts:40),只有 active 会继续(types.ts:177);complete 是终局,因 token 超限进入的 budget_limited 只能靠用户显式调高或取消 token 预算重新激活,回合数和时长超限则不行(types.ts:222)。每次转换都带一个原因,取自封闭的原因表(types.ts:85),例如 complete(verifier_met)、paused(no_progress)、budget_limited(main_turn)。用户在回合进行中按停止,活跃的目标会被记为 paused(user_requested)(packages/local-runtime-v2/src/application/conversation/conversation-application.ts:356)。
何时判定达成
模型觉得做完了,就调用 update_goal,以 status 模式提议 complete。工具只把提议记成回合内的信号,持久状态一个字都不改,并在结果里请求结束回合(packages/agent-modules/goal/src/tool-impls.ts:216、tool-impls.ts:281)。真正的裁决发生在回合结算:GoalSettlement 的十段决策流水线(packages/local-runtime/src/thread-goal/settlement.ts:209)依次做用量记账(token、回合数、活跃秒数,超预算即转 budget_limited)、陈旧检查(目标在回合期间被改过就作废)、异常分类(出错、被安全审查撤回、被中止)、预算收尾、依赖检查(有待答问卷、待批权限、进行中的后台任务或自动化归属冲突就先挂起)、验证、熔断、提交提议,最后安排续跑。
验证方式由 resolveMode 决定(packages/local-runtime/src/thread-goal/verification-dispatch.ts:156):配置里写了 goal.verification 就用它,否则看这一回合实际走的模型路由(packages/agent-modules/goal/src/verification/verification-policy.ts:18)。注释给了理由:
BYOK routes (
custom_provider,configured_provider) spend the user's own quota, so we accept the worker's completion proposal instead of silently doubling their bill.
none(自带 key 的路由):直接接受提议,记为complete(worker_proposal)(verification-dispatch.ts:349)。subagent(MiniMax 计费的managed_token_plan、minimax_api_key路由):派一个只读的验证子 Agent,给它目标、证据摘要和模型的完成声明,要求回一行VERDICT:加PASS、FAIL或PARTIAL(packages/agent-modules/goal/src/verification/subagent.ts:187)。PASS记为complete(verifier_met);FAIL附带缺口清单,目标保持active,清单写进下一轮续跑提示(packages/agent-modules/goal/src/continuation.ts:200);PARTIAL与各种验证故障都转paused。同一份缺口清单连续出现 5 次,目标以paused(no_progress)停下(packages/local-runtime/src/thread-goal/store-verification.ts:32)。验证子 Agent 花的 token 也计入目标预算。- 另有一个
evaluator后端(同路由上的小模型一次性评审),只能通过配置选用。
模型提议 blocked 则不经验证,直接记为 blocked(worker_reported)。
达成与否怎样影响回合继续
结算走到最后一段,enqueueActive(packages/local-runtime/src/thread-goal/continuation.ts:283)往会话队列里放一条宿主生成的续跑消息,正文包在 <archon_internal_context source="goal"> 里(packages/agent-modules/goal/src/internal-context-fragment.ts:46)。完整的目标契约只在第一轮给一次(DEFAULT_GOAL_CONTINUATION_TEMPLATE,goal/src/continuation.ts:12),之后每轮只附一句短提示(goal/src/continuation.ts:75),让历史前缀保持稳定、便于缓存。挑哪种提示由 selectPrompt 决定(thread-goal/continuation.ts:424):目标刚被编辑就换成“目标已更新”,被撤回后恢复时要求先 get_goal 核对,每满 5 轮做一次状态审计(packages/local-runtime/src/thread-goal/reminder-policy.ts:3),熔断计数不为零时附一段“不要原地踏步”的催促。
续跑消息出队前还要过 finalRecheck(packages/local-runtime/src/thread-goal/gate.ts:96):目标变了就取消;预算已尽就转 budget_limited;有待答问卷、待批权限、未决的 Plan、进行中的后台任务或自动化归属冲突就等着;用户排了自己的消息,就取消这条续跑,让用户的消息先走(gate.ts:138)。
回合进行中,每次工具调用前都会查一次预算(packages/local-runtime/src/thread-goal/admission-budget.ts:29),由 local-v1-goal-budget-guard 扩展在 before_tool_call 上执行(packages/local-runtime-v2/src/compat/v1/agent-host.ts:299)。一旦用尽,这次调用被拦下,拒绝理由要求模型别再调工具、立刻给出简短总结;这样的提示最多给 graceSteps 次,之后的调用只会收到“工具已禁用”(admission-budget.ts:79)。目标因预算停下后,宿主再安排一个专门的收尾回合,由 local-goal-budget-summary 扩展注入提醒,要求模型总结已完成与未完成的部分、说明可以调高预算,并且本回合不许调工具(packages/local-runtime-v2/src/application/agent/goal-budget-summary-reminder.ts:10)。
最后一道保险是熔断(packages/local-runtime/src/thread-goal/breaker.ts:73):最终回复与之前一模一样,或者整回合一次工具都没用,各自计数;第 2 次在续跑提示里催促,达到上限(默认 3)就以 paused(no_progress) 停下,如果重复就发生在一次完成声明之后,原因记为 paused(no_progress_after_completion_claim)(packages/local-runtime/src/thread-goal/store-breaker.ts:20)。用户亲自发一条消息会清零熔断计数,并负责把续跑重新接上。
packages/local-runtime/src/thread-goal/orchestrator.ts 里还有一个“跑完一轮读一次状态、active 就接着跑”的循环,注释说明它是旧路径,只观察持久状态,提议的结算归 v2 负责(orchestrator.ts:36);公开代码里没有调用它的地方。
下一篇:子 Agent 与后台任务——任务怎样派给子 Agent 或放到后台执行,结果又怎样回到父会话。