最近在做 SkillLite,一个用 Rust 写的自进化 Agent 引擎。做到规划系统这块时,我意识到一件事:
大多数 Agent 系统里,replan 不是一个"事件",它只是模型在下一轮换了个说法。
这对 demo 来说完全够用。但对一个想做自进化的系统来说,这是个根本性的问题------你没法统计、没法复盘、没法让进化引擎从它身上学到任何东西。
所以我把 replan 做成了显式工具调用。我把中间遇到的问题和坑,选型思考,以及和 Claude Code、OpenClaw、Manus 相比,这种方案解决了什么、牺牲了什么写成一个文章总结和大家交流,另外一方面也是做一个记录方便后期复盘。
选型分析:各家的 Replan 到底长什么样
如果只聚焦在 replan 机制本身,我会这样分类:
| 系统 | Replan 触发方式 | Replan 的表示形式 | 任务和工具显式绑定? | 核心优势 | 主要局限 |
|---|---|---|---|---|---|
Cursor(Plan Mode) |
执行前生成 Markdown 计划,用户确认才执行;执行中 replan 需用户重新输入 | 执行前的人工确认计划,执行后无自动 replan | 无 per-task 工具绑定 | 人机协作、计划可编辑、适合 IDE 内编码改动 | replan 不自主触发、不计数、执行中完全无规划结构 |
SkillLite |
模型显式调用 update_task_plan,失败 / 深度用尽 / 结果无效时系统提示考虑 replan |
独立工具调用事件,可计数、可记录 | 是,每个任务可带 tool_hint |
可观测、可度量、适合 evolution 信号沉淀 | 需要额外清洗逻辑,看起来没有"自然重规划"丝滑 |
Claude Code |
模型调用 TodoWrite 更新 todo,系统通过提醒促使更新 |
Todo 列表更新,也是显式结构 | 弱绑定,todo 只描述"做什么" | 任务进度可见、人机协作感强、适合长任务推进 | 对"任务类型 → 工具模式"的沉淀弱,replan 更像进度维护 |
OpenClaw |
根据观察结果自然再决策,必要时借助 Plan Skill / Task Router 重新分解 | 更偏隐式再决策,无统一 replan 事件 | 不做 per-task 工具绑定,由执行层和 skill 体系决定 | 灵活、规划深度可调(L0-L4)、适合复杂协同 | replan 很难被定义为离散事件,不利于统计和 evolution |
Manus |
planner 在外部工作区(如 task_plan.md)持续修订路线图(基于公开资料) |
工作区中的计划重写,无统一公开 replan API | 更像 agent 分工和子任务路由 | 强 context engineering、适配多 agent、擅长复杂开放任务 | 对单 Agent 的离散 replan 统计不友好 |
把五种方案压缩成一句话各自的核心:
Cursor的 replan → 人工重新发消息或编辑计划Claude Code的 replan → 更新任务板OpenClaw的 replan → 下一轮重新判断怎么做Manus的 replan → planner 持续改写外部路线图SkillLite的 replan → 一次可计数、可审计、可进化的计划替换事件
SkillLite 当前选择的不是最"智能感"的方案,而是对自进化系统的可记录,可追溯的设计。
一、隐式 Replan 的根本问题:你没法学
很多 Agent 系统里 replan 的实际过程是这样的:
- 这轮执行失败了
- 模型下一轮换了个思路
- 你感觉它"好像重新规划了一下"
- 但系统里什么记录都没有
这在 demo 里看起来很自然,甚至更聪明。但只要你想让系统真的从执行历史里学习,三个问题会立刻变得很严重。
问题一:你没法定义 replan 是否发生过
失败后重试一次算不算 replan?换了工具算不算?整套计划重写算不算?如果系统里没有明确事件,这些边界全是模糊的。
问题二:你没法统计
- 这类任务平均需要 replan 几次?
- 首次成功率和多次 replan 后成功率的差异是多少?
- 哪些工具序列最容易导致 replan?
没有离散信号,这些问题都只能靠感觉回答。
问题三:你没法复盘,也就没法进化
进化系统最需要的是可比较的历史轨迹。今天第 5 轮改主意,明天第 3 轮就换了------这种行为根本不能作为学习的稳定输入。
所以 SkillLite 的核心设计原则只有一条:
replan 必须是系统层面的正式动作,不能只是模型的思维变化。
二、SkillLite 的实现:显式调用 update_task_plan
SkillLite 的 planning 结构很轻:对话开始前生成一份任务列表,每个任务四个字段:
bash
{ id, description, tool_hint, completed }
tool_hint 是和 Claude Code Todo 最大的差异------它不只告诉系统"要做什么",还保留了"原本打算怎么做"的信息。这对 evolution 信号至关重要,后面展开。
执行中,如果发现当前计划失效,模型不是随便换个说法,而是调用:
**update_task_plan**
提交一份新任务数组,替换待执行部分。从这一刻起,replan 变成一个系统里真实存在的事件:replan_count 加一,历史已完成任务保留,新计划经过清洗后落地。
三、代码拆解:三段关键实现
3.1 handle_update_task_plan:replan 是状态修改,不是文字风格变化
代码在 crates/skilllite-agent/src/agent_loop/helpers.rs:
rust
pub(super) fn handle_update_task_plan(
arguments: &str,
planner: &mut TaskPlanner,
skills: &[LoadedSkill],
event_sink: &mut dyn EventSink,
) -> ToolResult {
// 1. 解析 LLM 提交的新任务列表
// 2. 校验 tasks 不能为空
// 新计划先过清洗 + 增强,和初始 planning 走同一套逻辑
planner.sanitize_and_enhance_tasks(&mut new_tasks, skills);
// 保留已完成任务,只替换待执行部分
let completed_tasks: Vec<Task> = planner
.task_list
.iter()
.filter(|t| t.completed)
.cloned()
.collect();
let mut merged = completed_tasks;
merged.extend(new_tasks.clone());
planner.task_list = merged;
// 通知事件系统,这次 replan 变成可记录的离散事件
event_sink.on_task_plan(&planner.task_list);
ToolResult {
content: format!("Task plan updated ({} tasks).", new_tasks.len()),
is_error: false,
..Default::default()
}
}
三个设计决策,我觉得值得注意:
- replan 是状态修改 ,
planner.task_list真实改变,不是回复风格变化。 - 已完成任务被保留,新计划只替换还没做的部分,历史不会清零。
- 新计划不直接执行 ,必须先过
sanitize_and_enhance_tasks这层防御。
3.2 sanitize_and_enhance_tasks:模型可以提建议,系统负责接住
代码在 crates/skilllite-agent/src/task_planner.rs:
rust
fn sanitize_task_hints(tasks: &mut [Task], skills: &[LoadedSkill]) {
for task in tasks.iter_mut() {
if let Some(ref hint) = task.tool_hint {
if !Self::is_hint_available(hint, skills) {
// 模型幻觉出来的 hint,直接剥掉,不让它进入执行链路
tracing::info!("Stripped unavailable tool_hint '{}' from task {}", hint, task.id);
task.tool_hint = None;
}
}
}
}
pub fn sanitize_and_enhance_tasks(&self, tasks: &mut Vec<Task>, skills: &[LoadedSkill]) {
Self::sanitize_task_hints(tasks, skills);
self.auto_enhance_tasks(tasks); // 检测缺失步骤并自动补齐
}
这层的存在原因很现实:模型在 replan 时和初始 planning 一样会幻觉。它会写出不存在的 tool_hint,漏掉关键步骤,或者把没完成的任务标成 completed。
如果不加清洗,replan 看起来像纠错,实际上只是重新生成了一份新的错误计划。
核心原则:replan 和初始 planning 必须走同一套清洗和增强逻辑。 这两件事在 SkillLite 里不允许有双重标准。
3.3 软上限:Agent 可以反思,但不能无限犹豫
显式 replan 带来一个副作用:模型有时会陷入"不停改计划但不执行"的循环。
SkillLite 在 crates/skilllite-agent/src/agent_loop/execution.rs 里做了软限制:
rust
const MAX_REPLANS_PER_SESSION: usize = 3;
if is_replan {
state.replan_count += 1;
let mut r = handle_update_task_plan(arguments, planner, skills, event_sink);
if !r.is_error && state.replan_count >= MAX_REPLANS_PER_SESSION {
r.content.push_str(
"\n\n⚠️ You have replanned 3 time(s). \
STOP replanning and EXECUTE the current plan step by step."
);
}
r
}
同时,单任务工具调用过深时,系统也给出两条明确出路,而不是只鼓励死磕:
rust
pub fn build_depth_limit_message(&self, max_calls: usize) -> String {
format!(
"You have used {} tool calls for the current task. \
Call `complete_task(task_id={})` to record completion, \
or call `update_task_plan` if the approach is clearly wrong.",
max_calls, self.current_task().map(|t| t.id).unwrap_or(0)
)
}
设计逻辑:不硬拦,保留模型自救空间;不放任,防止系统陷入假忙状态。
四、为什么不选其他几条路
为什么不像 Cursor 那样做 Plan Mode
Cursor 的 Plan Mode 是我认为目前编辑器 Agent 里规划做得比较有想法的一套。它的流程是:Shift+Tab 进入规划模式 → 研究代码库 → 提问澄清 → 生成带文件路径的可编辑 Markdown 计划 → 用户确认 → 执行。
这套流程对开发者来说体验很好:计划可见可改,执行有 diff 视图,高风险改动先过人工眼。
但它有一个根本特征:执行阶段就是纯粹的 Agent 工具循环,没有任何 replan 结构。
一旦用户点确认开始执行,Cursor 就进入 Agent Mode------纯 ReAct 循环,每轮最多 25 次工具调用,没有任务列表,没有 tool_hint,没有 mid-execution 的自动 replan 机制。如果中途发现方向不对,唯一的办法是用户重新输入一条消息。
这对编码体验足够了,但对 SkillLite 要做的事不够用:
- 系统无法在执行中自主识别"计划失效"并触发 replan
- 没有
replan_count,没有任何 replan 事件记录 - 没有工具模式信号,evolution 引擎拿不到学习材料
- 无人值守跑批时,卡住就只能超时,没有 Agent 自救路径
Cursor Plan Mode 解决的是"人怎么更好地把关 Agent 的计划",SkillLite 解决的是"Agent 怎么在执行中自主纠偏并沉淀经验"。 这是两个不同层面的问题。
为什么不直接照搬 Claude Code 的 Todo
Claude Code Todo 的优点很明确:显式、可见、进度实时同步。
但它的 todo 项只有 content/status/activeForm,没有 per-task 工具绑定。
这意味着你知道哪些任务完成了,但系统学不到:
- 这类任务原本打算用什么工具做
- 为什么这个工具路径失败了
- 哪类 task/hint 组合更容易导致 replan
SkillLite 的 evolution 引擎需要这一层信号来学习"任务类型 → 工具选择"的映射。没有 tool_hint,这条学习路径就断了。
Claude Code 的 Todo 更像执行进度结构 ,SkillLite 的 plan/replan 更像可进化的执行信号载体。
为什么不选 OpenClaw 的隐式再决策
OpenClaw 的规划体系很有意思:按任务复杂度动态决定规划深度(L0 无计划 → L4 需人工确认),执行中根据观察结果自然再决策,Task Router 还支持并行波次和依赖图。
但"灵活"正是它对 SkillLite 的最大障碍。
如果 replan 是"模型下一轮自然改主意",你就没法稳定定义"这次是否发生了 replan"。一旦这个定义模糊,后面这些事都会变很难:
- 统计首次成功率 vs. 多次 replan 后成功率
- 比较不同任务类型的平均 replan 次数
- 从失败案例里抽取可复用的规则
SkillLite 的 evolution 引擎强依赖这些可计数信号。replan 不是离散事件,整条进化链路的数据基础就不稳了。
OpenClaw 很适合"更强、更灵活的协同智能体",但不适合"单细胞 + evolution 信号提纯"这个目标。
为什么 Manus 的路线也不是当前参考系
从公开资料看,Manus 是强 planner + 强 context engineering + 多 agent 协作的体系。任务路线写进 task_plan.md,结合 notes.md、context.md 等外部工作区持续推进,planner 随时改写路线。
这套方案对复杂开放任务非常合适,但有一个前提:你需要的是一个能调度多 agent 的总控系统。
SkillLite 当前想做的刚好相反:一个可复制、可进化、可度量的单 Agent 最小闭环。
Manus 对我的启发更多是间接的------外部工作区有价值、context 工程化很重要、planner 和 executor 分层有意义。但在"replan 的离散可计数性"这件事上,它没有提供直接的参考模板。
五、Planning 不只是执行辅助,它是进化系统的接口
做完这轮设计之后,我对 planning 的理解变了。
以前会把它当成"让 Agent 少走弯路"的辅助功能。现在更认同另一种理解:
Planning / Replanning 是执行系统和进化系统之间的接口。
Agent 真正变强,靠的不是抽象意义上的"更聪明",而是:
- 更会把任务拆对
- 更会给当前任务配合适的工具
- 更会在错误路径上及时止损
- 更会把这次经验迁移到下一次
这些能力必须依赖结构化信号才能沉淀下来。而结构化信号的前提,是 replan 要是一个明确发生过的事件。
从这个角度看,planning 的意义已经不只是"让执行更顺",而是"让系统知道自己是怎么变好的"。
最后
如果你现在也在做 Agent,有一个问题可能需要思考一下:
你系统里的 replan,到底是一个正式动作,还是模型在上下文里的情绪变化?
前者看起来笨一点,但能积累经验。后者看起来更自然,但很难沉淀成系统能力。
至少对 SkillLite 来说,我越来越确定:
让 Agent 显式地改计划,比让它偷偷改主意,更适合做一个真正能进化的工程系统。
欢迎在评论区聊聊你们在做 Agent planning 时踩过的坑,或者对这几种方案有什么不同的判断。
欢迎star:github.com/EXboys/skil...