欢迎光临
我们一直在努力

记录我重写了 Agent 的 Plan 系统:为什么 Replan 是可进化 Agent 的关键

摘要

 Agent 项目都在讲"自主规划",但落到工程上,往往是开场列一份 Todo,或者让模型临场改主意。

我最近在维护SkillLite 的时候遇到一个在更底层的事:把"重新规划"做成一个可观测、可度量、可沉淀为进化信号的系统事件。本文结合真实的 Rust 代码,聊聊为什么我最终选择了显式 replan,而不是更自然但难以计量的隐式再决策。


先说选型:各家 Agent 的 Replan 到底长什么样

在展开实现之前,我觉得更重要的是先把几条路线摆出来比较,否则很容易读了半天代码,却不知道这些取舍是在对抗什么。

如果只聚焦在 replan 机制本身,我会把主流方案分成这五类:

系统

Replan 的典型形态

一句话理解

最适合的场景

Cursor(Plan Mode)

执行前生成可编辑 Markdown 计划,用户确认后执行;执行中无自动 replan

先规划再执行,replan 靠用户发起

IDE 内编码任务、人工审核计划、改动有限的单次任务

Claude Code

模型调用 TodoWrite 持续更新 todo 列表

更新任务板

长任务推进、人机共视、状态可见

OpenClaw

观察结果后自然再决策,必要时借助 Plan Skill 重新分解

下一轮重新判断怎么做

灵活协同、复杂任务、可变规划深度

Manus

planner 在外部工作区(如 task_plan.md)持续修订路线图

持续改写任务路线图

多 agent 编排、长上下文任务、强 context engineering

SkillLite

模型显式调用 update_task_plan 替换待执行计划

一次正式的计划替换事件

单 Agent、自进化、可度量、可复盘

结论:

  • 重视人工审核计划、确保每步可控 → Cursor Plan Mode 路线很强

  • 重视任务可见性和进度维护 → Claude Code 那类 Todo 路线很强

  • 重视灵活推理和自然再决策 → OpenClaw 这种路线很强

  • 重视多 agent 编排、大任务上下文管理 → Manus 这类路线很强

  • 重视 replan 可计数、可统计、可沉淀为 evolution 信号 → SkillLite 当前这套更合适

SkillLite 选择的不是最"智能"的方案,而是当前目标下追求可衡量的"工程化"的方案。


一、问题从哪里来:隐式 Replan 的三个死角

Agent 的replan 是一个比较常见的模块,但很多系统里的 replan 本质上是隐式发生的:

  • 这轮执行失败了

  • 模型下一轮换了个思路

  • 你感觉它"好像重新规划了一下"

  • 但系统里没有任何正式的 replan 记录

  • 这种方式做 demo 够用,但真正想做自进化系统,三个缺点会很快暴露出来。

    一:没法定义"到底有没有 replan"

    失败后重试一次算不算 replan?整套计划重写算不算?系统里没有明确事件,这些边界都是模糊的。

    二:没法统计失败原因

    是任务拆错了?工具选错了?还是最初规划就偏了?如果 replan 只是模型临场换了个说法,你之后分析不出任何稳定规律。

    三:没法复现

    今天第 5 轮改主意,明天第 3 轮就换了。对 demo 来说无所谓,对工程系统来说这种行为根本没法复盘,也不可能进化。

    所以 SkillLite 从一开始就定下一条设计原则:

    replan 不能只是模型脑子里的"临时改主意",必须是系统中的正式且可记录事件。


    二、SkillLite 的做法:让 Replan 变成工具调用

    SkillLite 的 planning 结构比较直接:对话开始前,Agent 先生成一份任务列表,每个任务包含四个字段:

    {
    id: u32,
    description: String,
    tool_hint: Option<String>, // 建议用哪个工具/skill 执行
    completed: bool,
    }

    tool_hint 是和 Claude Code Todo 最大的区别。普通 Todo 只知道"要做什么",而 tool_hint 额外带了"原本打算怎么做"。这一点对 evolution 信号很关键,后面会展开。

    执行过程中,如果发现当前计划不适用,模型不是"换个说法继续答",而是显式调用一个工具:

    update_task_plan

    调用之后,系统会把这次 replan 记录为一个离散事件,replan_count 加一,新计划替换待执行部分。

    从这一刻起,replan 就是一个可计数、可追踪、可回放的事件,不再是模糊的"它好像改过主意"。


    三、核心代码解读

    3.1 handle_update_task_plan:replan 不是口头建议,而是状态修改

    代码位于 crates/skilllite-agent/src/agent_loop/helpers.rs:

    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 next_id = completed_tasks.iter().map(|t| t.id).max().unwrap_or(0) + 1;
    for (i, t) in new_tasks.iter_mut().enumerate() {
    t.id = next_id + i as u32;
    t.completed = false; // 新计划里的 completed 一律重置
    }

    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). Continue with the new plan.", new_tasks.len()),
    is_error: false,
    ..Default::default()
    }
    }

    这段代码背后有三个设计决策值得注意:

    第一,replan 是状态修改,不是文字风格变化。 系统里 planner.task_list 真实改变了,不是模型只是说了一段不一样的话。

    第二,已完成任务被保留。 新计划不会覆盖历史,只替换还没做的部分,避免"replan 一次,之前努力清零"。

    第三,新计划不是直接执行的,要先被系统接住。 sanitize_and_enhance_tasks 这层防御非常关键。

    3.2 sanitize_and_enhance_tasks:模型可以提建议,系统负责接住

    这是一个很容易被忽视但非常关键的实现,在 crates/skilllite-agent/src/task_planner.rs:

    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) {
    tracing::info!(
    "Stripped unavailable tool_hint '{}' from task {}: {}",
    hint, task.id, task.description
    );
    // 把幻觉出来的 hint 直接清掉,不让它进执行链路
    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 时同样会幻觉。

    常见的问题有:写出根本不存在的 tool_hint、漏掉关键步骤、把没完成的任务标成 completed。

    如果不做清洗,replan 看起来像纠错,实际上只是重新生成了一份新的错误计划。

    最终原则只有一句话:replan 和初始 planning,必须走同一套清洗和增强逻辑。

    3.3 软上限:允许反思,但不允许无限犹豫

    把 replan 做成显式事件之后,很快会遇到一个新问题:模型有时候会陷入"不停改计划但不执行"的循环。

    SkillLite 在 crates/skilllite-agent/src/agent_loop/execution.rs 里做了软上限:

    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 now replanned 3 time(s). \\
    Please STOP replanning and EXECUTE the current plan step by step."
    );
    }
    r
    }

    同时,在单任务工具调用过深时,系统也会明确给出两个出口,而不是只鼓励硬试:

    pub fn build_depth_limit_message(&self, max_calls: usize) -> String {
    let current_id = self.current_task().map(|t| t.id).unwrap_or(0);
    format!(
    "You have used {} tool calls for the current task. \\
    Call `complete_task(task_id={})` to record completion. \\
    If the current approach is clearly wrong, \\
    you may call `update_task_plan` with a revised task list instead.",
    max_calls, current_id
    )
    }

    这两段代码体现同一个工程判断:不硬拦,保留模型自救空间;但也不放任,防止系统陷入假忙状态。


    四、为什么没有选其他方案

    为什么不像 Cursor 那样做 Plan Mode

    Cursor 的 Plan Mode 是目前编辑器 Agent 里做得比较有特色的一套:用户按 Shift+Tab 进入规划模式,Cursor 先研究代码库、提问、生成一份带文件路径和代码引用的可编辑 Markdown 计划,用户确认后再正式执行。

    这个设计有它的优势:

    • 人机协作感很强:用户能在执行前看到完整的执行路线,可以直接改掉不对的步骤

    • 适合编码改动场景:计划里直接标注要改哪些文件,执行后有 diff 视图可回滚

    • 降低大改动的风险:高风险任务先规划、人确认,再执行

    但这套方案有一个局限:replan 是人发起的,不是 Agent 自主触发的。

    一旦进入执行阶段,Cursor 的 Agent 模式就是纯 ReAct 循环(每轮最多 25 次工具调用),没有任何任务结构,也没有 mid-execution 的自动 replan 机制。如果执行中发现计划不对,只能用户重新输入,重走一遍。

    这对"提升编码体验"来说已经够了。但对 SkillLite 想做的事,完全不够用:

    • 系统没法自主识别"当前路径不对"并触发 replan

    • replan 不计入任何指标,没有 replan_count

    • 没有 per-task 工具绑定,没有 tool_hint,不产生 evolution 所需的工具模式信号

    • 无人值守跑批时,一旦卡住就只能超时,没有自救路径

    Cursor Plan Mode 的核心价值是人类把关编码计划,而不是让 Agent 在执行中自主纠偏。两者要解决的问题根本不在同一个层面。

    4.1 为什么不直接照搬 Claude Code 的 Todo

    Claude Code 的 Todo 路线有很明显的优点:显式、可见、用户和模型都能实时感知任务进度。

    但它的核心强项是进度维护,不是执行策略学习。

    Claude Code 的 todo 项只有 content、status、activeForm,没有 per-task 的工具绑定。这意味着你知道哪些任务做完了,但你不知道"这类任务原本打算用什么工具做"。

    而 SkillLite 的 evolution 引擎需要这一层信息:

    • 哪类任务经常和哪个工具绑定

    • 哪些 tool_hint 频繁导致失败或 replan

    • 任务类型和工具模式之间有没有可学习的稳定映射

    所以 SkillLite 不能丢掉 tool_hint。没有这个字段,evolution 信号就弱了一层。

    如果说 Claude Code 的 Todo 更像"执行进度结构",那 SkillLite 的 plan/replan 更像"可进化的执行信号载体"。

    4.2 为什么不选 OpenClaw 的隐式再决策

    OpenClaw 的规划体系我很欣赏:按任务复杂度动态决定规划深度(L0 到 L4),执行中根据观察结果自然再决策,Task Router 还支持并行波次和依赖图。

    但"灵活"恰恰是它对我的最大障碍。

    如果 replan 是"模型下一轮自然改主意",那你很难定义"这次是否发生了 replan"。一旦这个定义不清晰,后面这些事都做不了:

    • 统计首次成功率

    • 计算平均 replan 次数

    • 比较"无 replan 成功"和"多次 replan 成功"案例的差异

    • 从失败轨迹里提炼可复用规则

    SkillLite 的 evolution 引擎强依赖这些可计数的信号。如果 replan 不是离散事件,整条进化链路的数据基础就不稳了。

    4.3 为什么 Manus 的路线也不是当前的参考系

    从公开资料来看,Manus 是一个强 planner + 强 context engineering + 多 agent 协作的体系:任务路线写进 task_plan.md,结合 notes.md、context.md 等外部工作区持续推进。

    这套方案对复杂开放任务非常适合,planner 可以随时改写路线,上下文外化也解决了长任务的信息压缩问题。

    但 SkillLite 当前的目标不是"做一个超级总控 agent",而是做一个可复制、可进化、可度量的单 Agent 最小闭环。

    Manus 的启发对 SkillLite 更多是间接的:外部工作区有价值、planner 和 executor 分层有价值、context 工程化很关键。但在"replan 的离散可计数性"这件事上,它没有提供直接参考。


    五、为什么 Replan 是进化系统的入口,不只是执行辅助

    做完这轮分析,我越来越确信一件事:

    SkillLite 里的 planning/replanning,已经不是执行层的小功能,而是进化系统的入口。

    Agent 想真正变好,靠的不是抽象意义上的"更聪明",而是这些更具体的能力:

    • 把任务拆对

    • 给当前任务配上合适的执行策略

    • 在失败时及时换路,不死磕

    • 把这些经验沉淀成下一次更好的决策

    这些能力必须依赖结构化信号才能沉淀下来。而结构化信号的前提,就是 replan 要是一个明确发生过的事件,可以被记录、被统计、被分析、被学习。

    从这个角度看,planning 不再只是"让对话更有条理",而是"让系统知道它到底是怎么变好的"。


    六、后续还想继续打磨的几个点

    这轮把 replan 做成离散事件的设计完成后,我觉得还有几个地方值得继续优化:

    空计划时更早退出。 如果 planning 结果是 [],说明任务可能根本不需要工具,这时继续给满额迭代预算会浪费轮数。

    规划解析失败要更可观测。 当前 fallback 到单任务是合理的,但系统最好明确打日志,便于后续分析 prompt 质量。

    在更多卡住场景提示 replan。 连续失败、无工具调用、深度用尽这些情况,应该更一致地引导模型考虑改计划,而不只是反复重试。


    七、总结

    系统里的 replan,到底是模型偷偷改主意,还是一个能被记录、度量、复用的正式事件?

    这两者的差别,比"模型用哪个版本"或者"prompt 怎么写"都要更根本。

    因为 replan 不只是"让 Agent 能纠错",它是系统能不能从每一次执行里学习的前提。

    SkillLite 当前的选择是:显式 planning + 显式 replan + tool_hint 绑定 + 软限制约束。它可能不够"像人",但在"单细胞、可复制、可进化"这个目标下,应该是目前探索下来比较稳的工程解法。


    赞(0)
    未经允许不得转载:171主机测评 » 记录我重写了 Agent 的 Plan 系统:为什么 Replan 是可进化 Agent 的关键
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址