它为什么总想多做、少做,还自信说完成?
范围控制与功能清单,是让代理“做完一件再做下一件”的最小约束。本期把模糊需求改成机器可检查的完成定义。


Harness 从 0 到 1|第 5 期
用 feature_list.json 、单功能推进和明确验收条件,减少返工和“看起来完成”。
这套系列面向正在用 Claude Code 、 Codex 、 Cursor 、 OpenCode 或其他编程代理的中国开发者、产品经理和小团队。你不需要先学会复杂的 Agent 框架,先把自己的项目整理成代理能读、能执行、能复查的工作区。
这一期解决什么
范围控制与功能清单,是让代理“做完一件再做下一件”的最小约束。本期把模糊需求改成机器可检查的完成定义。
先记住系列路线
- 第 1 期:看见模型能力和执行可靠性之间的差距。
- 第 2 期:建立 Harness 的五子系统地图。
- 第 3~6 期:把规则、状态、范围和验证逐层放回仓库。
- 第 7 期:把一次次手动提示升级成自动循环。
- 第 8 期:当循环长成图,学习路由、并行、回退和人工审批。
下面进入本期正文。建议你打开一个自己正在维护的项目,对照着做,不要只收藏。
Agent 经常会越界,同时做太多事情。在一个典型场景中,你让它给项目加上用户认证功能,结果它同时开始改数据库 schema 、写路由、改前端组件,还顺手重构了错误处理中间件。两个小时后一看,12 个文件被修改,800 行新代码,但没有一个功能是端到端跑通的。
Agent 天生就有”多做一点”的冲动:看到相关的事情就顺手一起做了。问题是,同时做太多事情,结果往往每一件都做不好。
Anthropic 在 “Effective harnesses for long-running agents” 工程博客中明确指出:当提示太宽泛时,agent 倾向于”同时启动多件事”而非”先做完一件事”。 OpenAI 在 Codex 工程实践中也发现,没有显式范围控制的任务,完成率会暴跌。这本质上是一个 harness 设计问题:没有给 agent 划清边界。
注意力是有限的资源
这本质上是一个数学问题。假设 agent 的上下文容量为 C,同时激活 k 个任务,每个任务平均获得 C/k 的推理资源。当 C/k 低于完成单个任务所需的最小阈值时,所有任务都做不完。
Claude Code 的真实行为很说明问题。你让它”添加用户注册功能”,它很可能这样做:
- 创建 User model
- 写注册路由
- 发现需要邮箱验证,于是加邮件服务
- 看到密码需要加密,于是引入 bcrypt
- 注意到错误处理不统一,于是重构全局错误中间件
- 看到测试文件结构不清晰,于是重组目录结构
6 步之后,每一个都是半成品。没有端到端验证,代码之间耦合复杂,下一个会话来接手时会一脸懵。
Anthropic 的实验数据直接支持这一点:使用”小下一步”策略(等价于 WIP=1)的 agent,任务完成率比使用宽泛提示的 agent 高 37% 。更有意思的是,agent 生成的代码行数和实际完成的功能数量呈弱负相关,写得越多,完成得越少。贪多嚼不烂,数据为证。
WIP=1 工作流
flowchart LR
Queue["功能队列"] --> Pick["只选一个任务"]
Pick --> Active["仅允许一个 active"]
Active --> Verify["跑端到端验证"]
Verify -->|通过| Commit["提交并解锁下一个任务"]
Verify -->|失败| Active
Commit --> Queue
flowchart TB
Budget["可用推理预算 = C"] --> One["WIP = 1<br/>每个任务拿到 C / 1"]
Budget --> Many["WIP = 5<br/>每个任务只有 C / 5"]
One --> Finish["一个功能进入 passing"]
Many --> Partial["五个功能都只做了一半"]
Partial --> VCR["已验证完成率低<br/>下一会话返工高"]
核心概念
- 过度延伸(Overreach):agent 在一次会话中激活的任务数量超过最优值。这是可以量化的:同时做 5 个功能但 0 个跑通,就是 overreach 。
- 不足完成(Under-finish):已启动的任务中,通过端到端验证的比例低于阈值。写了代码但没跑通测试,就是 under-finish 。
- WIP 限制(Work-in-Progress Limit):来自 Kanban 方法论,核心思想是限制同时在进行的任务数量。对于 agent,WIP=1 是最安全的默认值,做完一个再做下一个。
- 完成证据(Completion Evidence):一个任务从”进行中”变成”已完成”必须满足的可验证条件。没有这个,agent 会用”代码看起来没问题”代替”行为通过测试”。
- 范围表面(Scope Surface):一个 DAG 结构,每个节点是一个工作单元,边是依赖关系。状态只有四种:未开始、进行中、阻塞、已通过。
- 完成压力(Completion Pressure):harness 通过 WIP 限制和完成证据要求共同产生的约束力,迫使 agent 先完成当前任务再开始新任务。
过度延伸与不足完成
这两个问题互相加剧。 overreach 导致注意力分散,注意力分散导致 under-finish,under-finish 留下的半成品代码又增加了系统复杂度,进一步导致下一个任务的 overreach,形成恶性循环。
用 Kanban 的语言说:Little 法则告诉我们 L = lambda * W 。如果在制品数量 L 过大(同时做太多事),每个任务的前置时间 W 必然增加。对 agent 来说,这意味着每个功能从开始到验证通过的时间被拉长,失败概率被放大。
这在人类世界也是老问题了。 Steve McConnell 在《 Rapid Development 》中记录,范围蔓延是项目失败的首要原因。但人类至少有”我已经做得够多了”的直觉,agent 完全没有。生成下一个想法的成本对模型来说太低了,写一行”顺便把这个也改了”几乎不消耗额外 token,但每个额外的修改都会稀释 agent 的注意力。
实施方法
1. 强制 WIP=1
这是最直接有效的方法。在你的 harness 里,明确告诉 agent:任何时刻只允许一个任务处于”进行中”状态。 在 Claude Code 的 CLAUDE.md 或 Codex 的 AGENTS.md 里写:
## 工作规则
- 每次只做一个功能点
- 当前功能点端到端验证通过后,才能开始下一个
- 不要在实现功能 A 时"顺便"重构功能 B
2. 给每个任务定义显式的完成证据
完成指的是”行为验证通过了”。在你的功能列表里,每个条目都要有验证命令:
F01: 用户注册
验证: curl -X POST /api/register -d '{"email":"test@example.com","password":"123456"}' | jq .status == 201
状态: passing
3. 把范围表面外部化
用一个机器可读的文件(JSON 或 Markdown)记录所有任务的状态。任何新会话都能直接读这个文件,知道:哪个任务在做?什么行为算完成?已经通过了什么验证?
4. 监控验证完成率
harness 应该持续跟踪 VCR(Verified Completion Rate)= 已通过验证的任务数 / 已启动的任务数。 VCR < 1.0 时,阻止新任务启动。
实际案例
一个 8 个功能点的 REST API 项目,两种策略对比:
无约束模式:agent 在第一个会话同时启动 5 个功能。产出约 800 行代码,涉及 12 个文件。端到端测试通过率只有 20%,只有用户注册跑通了。其余 4 个功能:数据库 schema 建了但缺验证逻辑,路由定义了但返回格式错误。到第 3 个会话结束,8 个功能只完成 3 个。
WIP=1 模式:agent 在第一个会话只做用户注册。产出约 200 行代码,涉及 4 个文件。端到端测试 100% 通过。提交干净的、已验证的实现。到第 4 个会话结束,8 个功能完成 7 个(第 8 个因外部依赖被阻塞)。
结果:总代码量更少(800 行 vs 1200 行),但有效代码更多。完成率 87.5% vs 37.5% 。
核心要点
- WIP=1 是 agent harness 的默认安全设置:做完一个再做下一个,不要试图并行。
- 完成证据必须是可执行的:“代码看起来没问题”不算完成,“curl 返回 201”才算。
- 范围表面必须外部化为文件:不能只在对话里说,必须在仓库里有机器可读的记录。
- overreach 和 under-finish 是共生问题:解决一个就解决了另一个。
- “少做但做完”永远优于”多做但做半”:agent 代码行数和功能完成率呈负相关,质量永远比数量重要。
延伸阅读
- Effective harnesses for long-running agents - Anthropic — Anthropic 工程博客,详细论述了”小下一步”策略
- Harness Engineering - OpenAI — OpenAI 对 harness 工程的完整论述
- Kanban: Successful Evolutionary Change - David Anderson — WIP 限制的经典来源
- Rapid Development - Steve McConnell — 范围蔓延作为项目失败首要原因的实证数据
练习
-
任务原子化练习:选一个宽泛需求(如”实现用户管理系统”),把它拆成至少 5 个原子工作单元。每个单元写清楚:(a) 单一行为描述,(b) 可执行的验证命令,(c) 依赖关系。检查是否满足 WIP=1 的约束。
-
对比实验:在同一个项目上跑两次,一次不给约束,一次强制 WIP=1 。比较验证完成率、总代码行数、有效代码比例。
-
完成证据审计:回顾一个最近的 agent 运行结果,把每个代码变更分类为”已完成行为”、“未完成行为”或”脚手架”。给每个未完成行为补充缺失的验证命令。
一个常见的场景:让 agent 做一个电商网站,跑完之后它告诉你”做完了”。但你打开代码一看,用户认证有了,但购物车的结算按钮点了没反应,支付流程根本没接上。问题的根源在于:没有告诉过它”做完”的具体标准,所以它用自己的标准来判断——“代码写了不少,看起来挺完整”。
功能清单(feature list)在很多人眼里就是个备忘录,写下来怕忘了,写完扔在一边。但在 harness 的世界里,功能清单是整个 harness 的基础结构。调度器靠它选任务,验证器靠它判完成,交接器靠它生成报告。没有它,这些组件就没有可以依赖的共识。
Anthropic 和 OpenAI 都强调**:工件必须外部化**。功能状态必须是仓库里机器可读的文件,不能是对话里的非结构化描述。
Agent 缺少明确的完成标准
Claude Code 和 Codex 都不会自动知道你心目中的”做完”是什么意思。你说”加一个购物车功能”,模型的理解可能是”写一个 Cart 组件和 addToCart 方法”。而你的意思是”用户能从浏览商品到下单支付完整走通”。
这个理解鸿沟在没有功能清单的情况下会持续存在。 agent 用自己的隐式标准判断完成,通常是”代码没有明显的语法错误”。而你需要的是端到端的行为验证。没有清单,双方对”做完”的理解始终是对不上的。
看看这种常见的进度记录:
做了用户认证、购物车基本完成了、还需要做支付
新的 agent 会话看到这个记录,能回答以下问题吗?“基本完成”意味着什么?购物车通过了哪些测试?支付的阻塞条件是什么?答案都是”不知道”。
结果是:新会话花 20 分钟推断项目状态,最终可能重复实现已完成的功能。 Anthropic 的工程实践数据表明,好的进度记录可以减少 60-80% 的会话启动诊断时间。
功能状态机
flowchart LR
Feature["一行功能项"] --> Behavior["行为<br/>例如:POST /cart/items 返回 201"]
Feature --> Check["验证命令<br/>具体要跑什么检查"]
Feature --> State["状态<br/>not_started / active / blocked / passing"]
Behavior --> Complete["三列都齐了<br/>这行功能项才能用"]
Check --> Complete
State --> Complete
flowchart LR
List["feature_list.json / features.md"] --> Scheduler["选下一个 not_started"]
Scheduler --> Agent["agent 只做这一项"]
Agent --> Verifier["跑这一项自己的验证命令"]
Verifier -->|通过| Passing["写成 passing<br/>并补上验证证据"]
Verifier -->|失败| Active["继续保持 active"]
Verifier -->|依赖问题| Blocked["标成 blocked"]
Passing --> Handoff["更新交接说明<br/>和当前进度"]
Active --> Agent
核心概念
- 功能清单是 harness 原语:它是所有 harness 组件依赖的基础数据结构。调度器、验证器、交接器都要读取它才能工作。
- 三元组结构:每个功能项包含三个要素:
(行为描述, 验证命令, 当前状态)。行为描述告诉 agent 做什么,验证命令告诉它怎么算做完,状态告诉它现在到哪了。缺了任何一项,这个功能项就不完整。 - 状态机模型:每个功能项有四种状态:
not_started、active、blocked、passing。状态转移由 harness 控制,不是 agent 想改就能改。 - 通过状态门控:功能从
active变成passing的唯一方式是验证命令执行成功。这个转移是不可逆的,passing了就不能退回去。 - 单一权威来源:项目里关于”该做什么”的所有信息,必须从一个功能清单派生。不能出现功能清单和对话记录矛盾的情况。
- 反向压力:还没通过的功能项数量就是 harness 对 agent 施加的压力。压力归零 = 项目完成。
为什么功能清单必须是原语
文档是给人看的,原语是给系统用的。文档可以被忽略,原语不能被绕过。
可以类比数据库的触发器约束和应用层的检查逻辑:前者由数据库引擎强制执行,任何 SQL 都无法跳过;后者依赖于应用代码的正确性,可能被意外绕过。功能清单作为 harness 原语,承担的就是数据库级别的约束角色,agent 不能绕过它。
具体来说,功能清单服务四个 harness 组件:
- 调度器:读状态,选下一个
not_started的功能。 - 验证器:执行验证命令,判断是否允许状态转移。
- 交接报告器:从功能清单自动生成会话交接摘要。
- 进度追踪器:统计各状态分布,提供项目健康度指标。
实施方法
1. 定义一个最小化的功能清单格式
不需要复杂的系统,一个结构化的 Markdown 或 JSON 文件就够了。关键是每个条目必须有三元组:
{
"id": "F03",
"behavior": "POST /cart/items with {product_id, quantity} returns 201",
"verification": "curl -X POST http://localhost:3000/api/cart/items -H 'Content-Type: application/json' -d '{\"product_id\":1,\"quantity\":2}' | jq .status == 201",
"state": "passing",
"evidence": "commit abc123, test output log"
}
2. 让 harness 控制状态转移
agent 不能直接把状态改成 passing。它只能提交验证请求,harness 执行验证命令,根据结果决定是否允许状态转移。这就是”通过状态门控”。
3. 在 CLAUDE.md 里写清楚规则
## 功能清单规则
- 功能清单文件: /docs/features.md
- 每次只激活一个功能项
- 功能项验证命令必须通过才能标为 passing
- 不要修改功能清单的状态,由验证脚本自动更新
4. 粒度校准
每个功能项应该是”一次会话能完成”的范围。太粗了做不完,太细了管理开销大。“用户可以添加商品到购物车”是一个好粒度,“实现购物车”太粗了,“创建 Cart 模型的 name 字段”太细了。
实际案例
一个电商平台的开发任务,10 个功能项。对比两种追踪方式:
备忘录模式:agent 用非结构化笔记记录进度。 3 个会话后,笔记变成了”做了用户认证和商品列表、购物车基本完成但还有 bug 、支付没开始”。新会话需要 20 分钟推断状态,最终重复实现了已完成的功能。
结构化模式:每个功能项有明确的状态和验证命令。新会话读取功能清单,3 分钟内知道:F01-F05 是 passing,F06 是 active(正在做),F07-F10 是 not_started。直接从 F06 继续,零重复。
定量结果:使用结构化功能清单的项目,功能完成率比自由形式高 45%,零重复实现。
核心要点
- 功能清单是 harness 的基础结构,不是给人看的备忘录。调度器、验证器、交接器都依赖它。
- 每个功能项必须有三元组:行为描述 + 验证命令 + 当前状态。缺一项就不完整。
- 状态转移由 harness 控制,agent 不能自己改状态。通过验证是唯一的升级路径。
- 功能清单是项目的单一权威来源,任何关于”该做什么”的信息都从这里派生。
- 粒度控制在”一次会话能完成”的范围。太粗做不完,太细管不过来。
延伸阅读
- Building Effective Agents - Anthropic — 明确指出功能列表是控制 agent 执行范围的”核心数据结构”
- Harness Engineering - OpenAI — 强调”将工件外部化”的原则
- Design by Contract - Bertrand Meyer — 契约式设计原则,功能列表的理论基础
- How Google Tests Software — 测试金字塔和行为规格的工程实践
练习
-
功能清单设计:定义一个最小化的功能清单 JSON schema 。包含:id 、行为描述、验证命令、当前状态、证据引用。用它描述一个包含 5 个功能的真实项目。
-
验证严格性对比:选 3 个功能,分别设计”宽松”验证(如”代码无语法错误”)和”严格”验证(如”端到端测试通过”)。对比两种验证下的假阳性率。
-
单一来源原则审查:审查一个已有的 agent 项目,检查是否存在与功能清单矛盾的范围信息(对话里的隐式需求、代码里的 TODO 注释等)。设计一个方案,把所有信息统一到功能清单中。
放到中国团队里,怎么用
国内个人开发者和小团队常见的问题很具体:项目规则散在飞书、微信群和口头约定里;测试命令没人维护;换一个人或换一次会话就要重新解释;模型调用还有额度、网络、数据合规和代码保密边界。 Harness 解决不了这些外部问题,但能把项目内部的规则、状态和验收方式固定下来。
建议先做一个低风险版本:只允许代理读取和修改一个测试仓库;所有写入动作走 Git 分支;涉及生产数据、客户代码、密钥和外部系统时,先人工确认;每次任务结束保留验证结果和变更说明。国内团队最容易忽略的不是模型选择,而是权限和数据流向。
本期动手清单
- 在自己的项目里找出一个反复返工的任务。
- 记录代理从开始到结束实际读了哪些文件、执行了哪些命令。
- 把缺失的规则、状态或验证补成项目文件。
- 用同一个任务再跑一次,只比较结果和返工,不凭感觉下结论。
- 把失败归因到 Harness 的某一层,下一期继续补。
下一期预告
下一期会把本期的概念进一步落成一套文件结构和模板。到时候我们不再讨论“应该怎样”,而是直接从项目根目录开始搭。