第10章 工作图——从计划到验收的闭环
2026.07.29大多数 agent 系统用 todo list 来管理任务——但工程任务很少是线性的。
模式层
10.1 工作图 vs todo list
Todo list 是最直观的任务管理方式。你列出要做的事情,按顺序排好,然后一个个完成。它简单、易懂、适合日常工作。
但工程任务很少是线性的。一个典型的重构任务可能包含这样的依赖关系:
理解模块 A 的结构 → 提取接口
↓
理解模块 B 的结构 → 提取接口 → 合并接口 → 测试 → 提交
↑
理解模块 C 的结构 → 提取接口
任务 D(合并接口)需要 A 和 B 都完成,而 C 可以和 A 并行。在 todo list 中,你只能排序——把 D 放在 A 和 B 之后,C 放在 A 之后——但这样丢失了"C 和 A 可以并行"的信息。
工作图(Work Graph)用 DAG(有向无环图)来表达依赖关系。每个节点是一个里程碑,每条边是一个依赖。Agent 通过图来理解"什么可以做"(依赖全部完成)、"什么在等待"(依赖尚未完成)、"什么是阻塞点"(多个里程碑依赖它)。
工作图不是 todo list 的"升级版"——它们是两种不同的数据结构。Todo list 是线性的,适合顺序明确的任务。工作图是 DAG,适合有依赖关系和多条路径的任务。对于简单的三步任务(编译 → 测试 → 部署),todo list 就够了。对于多模块、多分支的重构任务,工作图才是正确的工具。
10.2 事前图 vs 事后树
工作图与 Session 的分离(思想卷篇 5 的主题)在这里从工程实现的角度重新审视:
工作图是"事前构造之图"——计划载体。 它的结构在开始执行前确定。里程碑的增加、依赖关系的调整、验收条件的修改——都在规划阶段完成。执行阶段不能修改图的结构,只能修改里程碑的状态(pending → in_progress → done / needs_fix)。
Session 是"事后记录之树"——事实载体。 它的结构随执行不断增长。每个工具调用、每条消息、每个子代理结果——都在执行过程中被追加到 Session 树中。Session 不在规划阶段写,而是在执行阶段写。
两者分离的工程理由:
- 生命周期不同:工作图有终态(所有里程碑 done → 归档),Session 没有终态(只要 agent 在运行,Session 就在增长)
- 访问模式不同:工作图被 milestone 工具频繁读写(状态变更),Session 被写入一次后几乎不修改(除非压缩)
- 压缩策略不同:工作图不需要压缩(节点数有限),Session 需要两级压缩(tier-1 + tier-2)
10.3 里程碑粒度
多大的里程碑是一个"好的里程碑"?太粗或太细都有问题。
太粗的里程碑:一个里程碑包含"重构模块 A"——但重构模块 A 可能涉及 10 个文件的修改、接口变更、测试更新。如果里程碑太粗,验收时无法准确判断"这个里程碑到底做完了没有"——"重构模块 A"在什么条件下算完成?如果只改了主要文件但漏了测试——算 done 还是 needs_fix?
太细的里程碑:每个小步骤一个里程碑——"修改文件 A"、"修改文件 B"、"修改文件 C"——每个里程碑的验收条件不过是"文件已修改"。这种粒度下,管理工作量(创建里程碑、更新状态、验收)超过了执行工作量。
粒度判断标准:一个里程碑是否可以被独立验收?
如果一个里程碑完成后,可以独立检查它的输出是否符合预期——不需要看其他里程碑的完成情况——那么这个里程碑的粒度是合适的。如果验收时需要看"这个里程碑和另一个里程碑的完成情况一起来判断"——说明粒度太细,应该合并。
实际经验规则:一个里程碑对应一个 git commit 的规模。 如果 agent 完成一个里程碑后生成的工作量足够做一个独立的 commit(有明确的变更范围、有配套的测试、有清晰的 commit message),那么这个里程碑的粒度是合适的。
案例层
10.4 milestone 工具的六种操作
milestone 工具支持六种操作:
enum MilestoneAction {
Add {
name: String,
deps: Vec<String>, // 依赖的里程碑名称
acceptance: String, // 验收条件描述
command: Option<String>, // 验收命令门(可选)
},
Start { name: String },
Done {
name: String,
verdict: Option<String>, // agent 自评
},
NeedsFix {
name: String,
reason: String, // 需要修复的原因
},
Next,
List,
}
- add:增加一个里程碑,声明它的依赖和验收条件。依赖必须在创建时指定,创建后不能修改
- start:标记一个里程碑为"进行中"——表示 agent 正在执行这个里程碑的工作
- done:标记为"完成",可选附上 agent 自评。如果里程碑配置了
command验收门,done不会立即生效——需要等待验收门通过 - needs_fix:标记为"需要修复"——验收不通过或被用户打回时的唯一状态
- next:查询"下一个可以做的是什么"——返回依赖全部 done 且为 pending 的最低 id 里程碑
- list:列出全部里程碑及其当前状态
10.5 next_ready() 调度逻辑
next_ready() 是工作图的核心调度函数:
fn next_ready(graph: &WorkGraph) -> Option<&Milestone> {
graph.nodes
.iter()
.filter(|m| matches!(m.status, MilestoneStatus::Pending))
.filter(|m| m.deps.iter().all(|dep| {
graph.nodes.iter().any(|n| n.name == *dep && matches!(n.status, MilestoneStatus::Done))
}))
.min_by_key(|m| m.id) // 最低 id 优先
}
调度逻辑的顺序:
- 只考虑
Pending状态的里程碑 - 检查依赖:所有依赖必须全部是
Done状态 - 从满足条件的里程碑中选择
id最小的(id 反映创建顺序,最小的最早创建)
min_by_key(|m| m.id) 的选择不是随机的——它确保了"先创建的里程碑优先执行"。如果两个里程碑的依赖都满足,先创建的那个先执行。这避免了"后创建的里程碑因为依赖简单而插队"的问题。
10.6 drive_workgraph 自动推进
drive_workgraph 将 next_ready() 调度与 milestone 状态变更结合起来,形成一个自动推进循环:
fn drive_workgraph(graph: &mut WorkGraph, context: &Context) -> Result<()> {
loop {
// 1. 找下一个 ready 的里程碑
let next = next_ready(graph);
// 2. 如果没有更多里程碑,完成
let milestone = match next {
Some(m) => m.clone(),
None => break,
};
// 3. 标记为进行中
milestone.start(context);
// 4. 执行里程碑的 command(如果有)
if let Some(cmd) = &milestone.command {
let result = context.execute_command(cmd)?;
if !result.success {
milestone.needs_fix("command failed", context);
continue;
}
}
// 5. 标记为完成
milestone.done(context);
// 6. 循环——找下一个 ready 里程碑
}
}
drive_workgraph 的关键设计点:
- 一次只推进一个里程碑:找到下一个 ready 里程碑 → 执行 → 标记完成 → 再找下一个。不并行推进多个里程碑——即使它们的依赖条件都满足
- command 失败 → needs_fix → 继续循环:command 失败后,当前里程碑进入 needs_fix 状态,循环继续寻找下一个 ready 里程碑。不会阻塞整个图的推进
- 循环终止条件:没有更多 ready 里程碑,或者所有里程碑都已 done / needs_fix
10.7 NodeStatus 状态机
NodeStatus 定义了里程碑的状态转换:
enum NodeStatus {
Pending, // 初始状态,等待执行
InProgress, // 正在执行
Done, // 完成(验收通过)
NeedsFix, // 验收不通过,需要修复
// 诊断扩展预留:
Hypothesis, // 假设性里程碑(未确认是否要做)
Locked, // 被锁定(等待外部条件)
}
状态转换规则:
Pending → InProgress : start 操作
InProgress → Done : done 操作(验收通过)
InProgress → NeedsFix : needs_fix 操作(验收不通过)
NeedsFix → InProgress : start 操作(重新执行)
Done → NeedsFix : needs_fix 操作(用户或二次验证发现的问题)
完整的里程碑状态机流程如下:
stateDiagram-v2
[*] --> Pending: add milestone
Pending --> InProgress: start
Pending --> InProgress: next_ready() 调度
InProgress --> Done: command 通过验收
InProgress --> NeedsFix: command 验收失败
NeedsFix --> InProgress: 自恢复循环<br/>(有界重试)
NeedsFix --> InProgress: 用户手动重置
Done --> NeedsFix: 二次验证发现问题
NeedsFix --> Stuck: 重试预算耗尽
Stuck --> [*]: 人工介入
Pending --> Blocked: 依赖未满足
Blocked --> Pending: 依赖完成
state Pending {
[*] --> Ready: 依赖全部 Done
Ready --> Wait: 前置里程碑未完成
}
state NeedsFix {
[*] --> Readying: 注入修复 prompt
Readying --> Retrying: 重新执行
Retrying --> Verify: 重新验收
Verify --> Pass: 验收通过
Verify --> Fail: 验收不通过
Pass --> [*]: done
Fail --> [*]: 重试计数
}
note right of NeedsFix: 检查门和审查门<br/>失败时进入此状态
Hypothesis 和 Locked 状态是对诊断扩展的预留。Hypothesis 用于"不确定是否要做但先标记"的里程碑——agent 可以创建假设性里程碑,在后续验证后通过 add 或 start 转换为正式里程碑。Locked 用于"等待外部条件"的里程碑——比如等待另一个服务部署完成。
10.8 完整里程碑示例
以下是一个典型的里程碑定义:
{
"id": 3,
"name": "extract-interface",
"deps": ["analyze-structure", "identify-dependencies"],
"acceptance": "模块 A 的公共接口已提取到独立的 trait 中,原有模块只依赖 trait 不依赖实现",
"command": "cargo check --lib && cargo test --lib"
}
name:extract-interface——简短、描述做什么deps:依赖两个前序里程碑——analyze-structure和identify-dependenciesacceptance:验收条件——描述"做完了应该满足什么"command:验收命令门——cargo check --lib确保编译通过,cargo test --lib确保测试通过
command 和 acceptance 的不同角色:command 是编译时检查(确定性),acceptance 是人工审查时参考的描述(非确定性)。两者不重复——command 检查"能不能编译",acceptance 判断"是否做对了"。
ADR 深度阅读
从扁平 todo 到 Work Graph
CodeCoder 最初的 task 管理是一个简单的 todos.json 文件:
{
"tasks": [
{ "id": 1, "title": "分析模块 A", "done": false },
{ "id": 2, "title": "提取接口", "done": false },
{ "id": 3, "title": "测试", "done": false }
]
}
问题是:todos.json 只能表达顺序,不能表达依赖。提取接口需要分析模块 A 完成——但 agent 可能先去做"测试"(因为排在第三位,但 agent 认为"先看看测试环境"也是合理的)。
更严重的问题:todos.json 没有"验收"的概念。Agent 标记 task 为 done 后,没有机制验证它是否真的做完了。用户需要手动检查——但用户可能不在线。
Work Graph 的引入解决了这两个问题:
- 依赖关系通过
deps字段显式表达——agent 不能跳过未完成的依赖 - 验收条件通过
acceptance和command字段隐式附加——标记 done 前需要验证
引入 Work Graph 后,todos.json 被废弃。但迁移过程不是简单的文件格式替换——todos.json 的扁平结构使得 agent 和使用者都忽略了"任务之间的依赖关系"这个维度。迁移到 Work Graph 后,agent 第一次遇到"依赖不满足"的阻塞状态——这是它之前从未经历过的。从"总是可以做下一件事"到"有些事需要等待其他事完成"——对 agent 的规划能力是一个质的变化。
下一章,验收门的三级递进:命令门、检查门、审查门——以及如何用它们构建信任。