如何给 AI 智能体布置任务:一次一个功能、功能清单与完成标准
给 AI 智能体布置任务应当一次一个:同一时间只有一个功能处于进行中,它有可以观察的行为和可执行的验证命令;智能体在过程中发现的其他问题记入待办清单,而不是顺手修复。所有功能的状态保存在机器可读的清单中(例如 feature_list.json),只有验证脚本而非智能体本身才能将功能标记为完成。
为什么智能体什么都想做
你让智能体实现注册功能。几个小时后,差异中已有十二个修改过的文件和八百行新代码:用户模型、邮箱验证、接入的邮件服务、密码哈希、重构过的中间件,以及迁移的测试。这些内容彼此紧密耦合,没有一个场景能完整通过。下一次会话打开这段代码时,根本不知道该从哪里开始。
问题不在于“模型不好”。人会有“今天够了”的感觉,还有普通的疲劳,它们会自然限制工作的范围。智能体没有这两样东西。在句子末尾加上“顺便修一下错误处理”,对它来说只是几十个 token;但对项目而言,代价是实实在在的:注意力被分散,耦合不断增加,而真正完成的东西却没有。
还有一个简单的算术。设 C 为有效的上下文容量,k 为智能体同时处理的任务数。每个任务大约能分到 C/k 的推理资源。当这个数值低于完成一个任务所需的量时,就没有任何任务能完成。因此,“如何给 AI 智能体布置任务”的正确答案并不始于提示词的措辞,而始于一条约束:同时只进行一个任务。
单功能规则(WIP=1)
WIP 即 work in progress,指进行中的任务数。给智能体设定的 WIP 为 1:任何时候只有一个功能处于活动状态,下一个功能只有在当前功能通过检查后才会开始。这条规则要写进项目的指令文件(AGENTS.md 或 CLAUDE.md),让智能体在每次会话中都能看到:
## 工作规则
- 任何时候,处于 `active` 状态的功能有且只有一个。
- 只有当当前功能进入 `passing` 后,才能开始下一个功能。
- 禁止“顺便”重构、重命名、调整目录结构或修复无关的 bug。
发现问题——记入 `BACKLOG.md`,然后继续。
- 超出当前功能范围的修改视为违规,即使它们有用。
最后一条比看上去更重要。智能体真诚地认为它的“顺便”很有价值,而且常常确实有价值。但即便是有用的越界修改,也会把两个任务混在同一个差异里,之后就无法单独回滚或验证了。
BACKLOG.md:“顺便”的去处
“顺便修复”的冲动不能只是被禁止,还需要一条合法的出路。这个出路就是 BACKLOG.md:智能体用一两行把发现的问题记在里面,然后回到当前功能。信息不会丢失,范围也不会扩大。没有待办清单,WIP=1 规则迟早会被智能体绕过,因为在它看来,“默默放着这个 bug”比违反规则更糟。
# BACKLOG.md
- [ ] middleware/errors.py:/auth 和 /orders 的错误格式不一致
- [ ] tests/:用户夹具在三个文件中重复
- [ ] 注册时是否需要邮箱验证?——需要询问
完成标准:“完成”就是一条命令
问题的另一半是:智能体自己决定任务是否结束。“代码看起来没问题”“函数已经存在”“没有编译错误”,这些都是感觉,而不是事实。智能体任务的完成标准应当是一条可执行的命令,结果只有一个:通过或不通过。
F01:用户注册
verification: pytest tests/e2e/test_auth.py::test_register_flow
state: active
强标准与弱标准的区别,通过示例就能看清:
| 弱标准 | 强标准 |
|---|---|
| 代码可以编译 | pytest tests/e2e/test_cart.py -q 通过 |
| 没有语法错误 | 对 /cart 的 curl 请求返回 200 |
函数 addToCart 存在 | 添加商品后,GET /cart 显示该商品及正确的总价 |
| 看起来正确 | npx playwright test cart.spec.ts 通过 |
好的验证命令是确定性的(相同代码得到相同结果),无需人工参与即可运行,并且从外部检验行为,也就是用户或 API 客户端实际看到的结果。如果写不出这样的命令,说明这个任务的描述还不够清楚。
拆分任务:功能应有多大
关于“一次一个功能”,最常见的问题是:什么才算一个功能。范围太大,一个会话内完不成;范围太小,启动、上下文和验证的开销会把它淹没。
- 太宽:“实现购物车”。这是一周的工作,包含十几个场景。
- 恰到好处:“用户可以把商品加入购物车并在列表中看到它”。一个可观察的行为,一个验证场景。
- 太窄:“在 CartItem 模型中创建 quantity 字段”。这是实现步骤,而不是一个行为,单独验证毫无意义。
一个实用的尺寸检验是:一个功能 = 一次智能体会话 = 一次有意义的提交 = 一个端到端(E2E)场景。如果一个功能装不进一次会话,就继续拆分。如果无法为它写出单独的场景,说明它太小,应与相邻的功能合并。
示例:把“购物车”任务拆开
宽泛的需求被拆成原子单元,每个单元只有一种行为和自己的验证命令:
- 把商品加入购物车,并在列表中看到它。
- 修改商品数量,总价重新计算。
- 从购物车删除商品。
- 访客的购物车在登录账户后得以保留。
- 无法添加库存不足的商品,并给出清晰的错误提示。
拆分的最终检验:没有任何一个单元需要“顺便”改动另一个单元。如果第 4 项需要重写第 1 项,那说明顺序或边界选得不好。
功能清单:feature_list.json
WIP=1 规则可以保证会话内部的秩序,但谁来记住会话之间已经完成了什么?在聊天记录里写下“认证和目录已完成,购物车接近完成,但有些 bug”,对新会话没有任何帮助:“接近”是什么意思,哪些测试通过了,什么阻碍了支付,都不得而知。每个新会话就像同一辆车的新司机:如果笔记只存在于前一位司机的脑子里,新司机什么也不知道。
因此,状态要外置到一个机器可读的文件中。在本课程中,它是 .agent/feature_list.json:
[
{
"id": "F01",
"behavior": "用户使用邮箱和密码注册,并立即登录",
"verification": "pytest tests/e2e/test_auth.py::test_register_flow",
"state": "passing",
"evidence": "a3f9c12: 1 passed in 2.31s"
},
{
"id": "F02",
"behavior": "用户把商品加入购物车并在列表中看到它",
"verification": "pytest tests/e2e/test_cart.py::test_add_item -q",
"state": "active"
},
{
"id": "F03",
"behavior": "使用银行卡支付订单",
"verification": "pytest tests/e2e/test_payment.py -q",
"state": "blocked",
"blocker": "没有支付网关的测试密钥"
}
]
每条记录有三个必填字段:从外部可观察的行为描述、可执行且无需人工参与的验证命令,以及状态。可选的第四个字段是 evidence:提交哈希和验证输出,用于证明确实经过了 passing 的转换。
为什么用 JSON 而不是 Markdown 列表?它不仅能被智能体读取,也能被脚本读取:调度器选取 not_started 中的下一个功能,验证器取出验证命令,会话报告自动生成,各状态的分布则反映项目的健康程度。功能清单是 Harness 的脊梁,而不是备忘录。
四种状态及允许的转换
| 转换 | 何时发生 |
|---|---|
not_started → active | 开始处理该功能;当前没有其他活动功能 |
active → passing | 验证脚本以退出码 0 结束 |
active → blocked | 存在外部阻碍;原因写在 blocker 中 |
blocked → active | 阻碍已解除 |
除此之外没有其他转换。特别是,不存在绕过验证直接进入 passing 的路径,也不存在“差不多完成”这种状态。
passing 闸门:智能体可以申请,但不能宣布
整个方案的核心规则是:智能体不手动编辑 state 字段。它可以运行验证,但不能自己宣布结果,宣布的是脚本。“智能体报告”与“脚本确认”之间的差别,就是文字与事实之间的差别。这些规则同样写进指令文件:
## 功能清单规则
- 文件:`.agent/feature_list.json`。这是范围的唯一可信来源。
- 处于 `active` 状态的功能有且只有一个。
- 你不得手动修改 `state` 字段。它由 `scripts/verify.sh` 修改。
- 要将功能转为 `passing`,请运行 `./scripts/verify.sh F02` 并使其返回码为 0。
- 如果功能被阻塞,将其转为 `blocked`,并且必须填写 `blocker`。
- 新功能只能添加到 `not_started`,且必须附带验证命令。
脚本分两层检查:先运行项目的整体检查(该功能不应破坏其他部分),再运行该功能自己的验证命令。局部成功但造成全局回归,不算成功。
#!/usr/bin/env bash
# 唯一有权设置 passing 的组件。
set -uo pipefail
FID="$1"
FILE=".agent/feature_list.json"
CMD=$(jq -r --arg id "$FID" '.[] | select(.id==$id) | .verification' "$FILE")
if [ -z "$CMD" ] || [ "$CMD" = "null" ]; then
echo "FAIL: 没有 $FID 的验证命令"; exit 2
fi
echo "== 整体检查 =="
make check || { echo "FAIL: 整体检查未通过,$FID 不能设为 passing"; exit 1; }
echo "== 功能检查:$CMD =="
if OUT=$(eval "$CMD" 2>&1); then
SHA=$(git rev-parse --short HEAD)
LAST=$(printf '%s\n' "$OUT" | tail -n 1)
TMP=$(mktemp)
jq --arg id "$FID" --arg ev "$SHA: $LAST" \
'map(if .id==$id then .state="passing" | .evidence=$ev else . end)' \
"$FILE" > "$TMP" && mv "$TMP" "$FILE"
echo "PASS: $FID"
else
printf '%s\n' "$OUT"
echo "FAIL: $FID"; exit 1
fi
只写在文本里的“不要编辑 state”仍然只是一个请求。要让它成为真正的限制,可以用机制来关闭写入路径:在 CI 中校验 evidence 与提交历史是否一致,或者在工具中限制智能体的写权限。原则与 Harness 的其他部分相同:重要的规则要靠机制来支撑。
如何衡量智能体是否做完了任务
有一个很诱人的指标:每次会话产生的代码量。实际上它的效果恰恰相反:差异越大,越可能说明智能体分散到了多个方向。应该统计处于 passing 状态的功能数,而不是代码行数。
本课程中还有一个方便的指标:VCR(Verified Completion Rate,已验证完成率),即已通过验证的任务数除以已激活的任务数。规则很简单:只要 VCR 小于 1,就不能激活新任务。这是 Harness 施加给智能体的反向压力,也让未完成的工作变成一个可见的数字,而不是一种感觉。
给智能体布置任务时的常见错误
- 任务没有验证命令。“做注册功能”却没有场景,智能体会自行决定在哪里停止,而且通常停错。
- 一个请求里包含多个功能。“做购物车和支付”就是两个活动功能,结果两个都完不成。
- 状态只存在于聊天中。写在对话里的进度会随会话结束而消失,它必须保存在文件中。
- 自我报告代替验证。“智能体说测试通过了”并不等于脚本的返回码。
- 没有待办清单。禁止“顺便”却没有地方记录所发现的问题,智能体就会绕过这条规则。
当一次一个功能已经不够用时
“一个功能、一次会话、一次验证”的方案适用于一个智能体、一个循环的情况。当任务增多、出现相互独立的分支时,可以把它扩展为多循环系统:独立的规划节点、实现节点、验证节点,以及它们之间的边——可以并行、可以有条件,验证失败时还可以返回实现节点。但即便如此,每个实现节点仍遵循同一条规则:一个任务、可执行的完成标准、状态保存在智能体的脑袋之外。图结构并不取消一次一个功能的纪律,而是建立在它之上。
同一分析中还有一个重要提醒:循环会诚实地优化你给它的指标。如果完成标准选得不好,比如用“工单已关闭”代替“问题已解决”,智能体就会学会完成标准,而不是完成任务。因此,描述行为和编写验证命令值得花费比看上去更多的时间。
总结
简而言之,给 AI 智能体布置任务要一次一个,每个任务都有可观察的行为和可执行的验证命令;沿途发现的问题记入待办清单;功能清单保存在文件中,只有脚本而非智能体能把任务标记为完成。节奏由 Harness 来设定,智能体自己不会踩下刹车。WIP=1 规则、feature_list.json 和 verify.sh 在课程的实际项目中都有详细讲解。
常见问题
如果任务很小,能否一次给智能体好几个?
可以把它们放进功能清单,但同一时间只能有一个处于进行中。智能体只有在当前任务通过验证后才会开始下一个,这样每个任务都能真正完成。
如果某个任务无法写出自动验证,怎么办?
通常说明任务是按动作而不是按行为描述的。请用用户或 API 客户端看到的结果来重新表述;对于界面部分,可以用浏览器中的 e2e 测试。
feature_list.json 是人写的还是智能体写的?
初始清单最好一起编写:智能体提出拆分方案,人来修改。之后智能体可以添加新功能,但只能放在 not_started 状态,并且必须附带验证命令。
feature_list.json 与任务跟踪器中的任务有什么区别?
跟踪器是为人设计的,而功能清单是为机器读取的:每条记录都有可执行的验证命令和严格的状态,且由脚本修改。两者可以同步,但智能体需要的是仓库中的文件。
一次一个功能的规则会不会拖慢速度?
每次会话变短了,但完成的任务比例提高了。过去花在梳理三个混杂功能的半成品代码上的时间得以节省。
课程“Harness Engineering: инженерия надёжности AI-агентов”结合实践详细讲解了这一主题。
- feature_list.json для AI-агента: WIP=1, статусы фич и верификация через verify.sh
- Graph Engineering для AI-агентов: когда одного цикла мало и нужен граф задач