我是安徽最忧郁程序员无隅
引言
给 Agent 一个任务:"修复知识库里长文档问答为空的问题。"它可能检查分块逻辑,也可能开始改检索排序、重写问答组件,甚至顺手补一套聊天历史。修改越来越多,最初那个问题却未必已经解决。
这一阶段要解决的,就是如何让一次任务围绕一个明确结果推进:先确定这次完成什么,再写清怎样算通过,遇到失败后用具体反馈决定下一步。
本文对应 L07"划清任务边界"、L08"功能列表作为 Harness 基本单元"和 P04"用运行反馈纠正代理行为"。我会沿用本地知识库的长文档索引问题,解释这三个机制怎样接起来。文中的 P04 缺陷来自源码只读核对,功能清单与执行流程是教学设计,不代表已经完成修复实验。
一、任务边界应该围绕一个完整行为划分
从"问答有问题"缩小到一个可验收的目标
"优化知识库""把问答修好"都可以表达方向,但还不足以让执行者判断工作范围。问答依赖文档读取、文本分块、索引保存、检索和界面展示;任何一处都有可能影响结果。没有当前目标,Agent 很容易把"可能相关"理解为"这次都要做"。
更合适的做法,是先取得必要证据,再收敛任务。比如已经确认长文档在索引阶段丢失内容,就可以把当前项定义为:修复长文档分块内容丢失,验证保存后的块保留原文关键内容,并检查既有问答路径能否使用这些内容。
这里有一个先后关系:不知道根因时先调查,得到证据后再确定修复边界。 不能因为课程提前告诉了我们缺陷位置,就假装实际诊断也可以跳过调查。
在这个目标下,修改分块逻辑、补充针对该缺陷的回归用例、增加必要诊断信号,都直接服务于当前任务。新增完整聊天历史则有另一个独立的用户目标,应先留在待办队列。
WIP=1 限制的是在做的功能数
WIP 是 Work in Progress,即正在推进的工作。WIP=1 在这里表示:当前只激活一个功能项,先让它达到验收条件,再选择下一项。
它限制的是同时打开多少条工作线。一个功能本身可能跨越多个模块,因此不能简单把它解释成"只能修改一个文件"。
下面这张图可以用来判断一项修改是否属于当前范围:看它是否直接服务于这一行为的实现或验证。

如果修复索引需要调整服务层逻辑,并补充测试,那么两处修改都可能合理;如果已经有足够证据确认故障在分块层,却开始统一整个应用的按钮样式,这项工作就没有帮助当前行为通过验收。
范围也不宜切得过碎。"创建一个字段""改一行代码"可以是实施步骤,但通常不足以独立回答用户现在能做什么。一个合适的功能项应当能描述输入、可观察结果和通过条件;若大到一次有界工作无法验收,就继续拆分。
限制并行工作的直接价值,是减少同时悬而未决的改动,让问题更容易归到当前项。Anthropic 的长期 Agent 工程实践也采用一次推进一个功能的方式,处理尝试一次完成过多工作的问题;这是该实践的设计经验,不能直接换算成当前项目的效率提升比例。来源:Effective harnesses for long-running agents
任务选好了,仍有一个空缺:什么时候才允许说"这一项完成了"?这就是功能清单需要解决的问题。
二、功能清单把"做完"变成可以核对的约定
一条功能项至少回答三个问题
"索引基本完成,问答还要看看"适合随手记,却不足以指导下一次工作。接手者不知道"基本完成"包含哪些行为,也不知道先查索引还是先改界面。
L08 给出的基本结构是:行为描述、验证方式、当前状态。实际使用时,再增加稳定的编号和证据位置,才更容易追溯某个结论。
下面是一条为本文设计的示例。字段是自定义约定,不是某个工具安装后就自动生效的配置;它也没有冒充已经取得的验证结果。
json
{
"id": "index-long-document",
"behavior": "长文档索引保留关键内容,既有问答路径可使用这些内容",
"state": "not_started",
"verification": [
"对超过 1000 字符、包含多个段落的固定文档执行索引",
"确认保存的文本块非空,并保留原文开头、中部和末尾的标记句",
"确认短文档原有索引行为仍然正常",
"在真实界面沿既有问答路径提问,检查结果和引用"
],
"evidence": []
}
这里每个字段都改变了接手者能作出的判断:behavior 约束目标,verification 固定检查方法,state 表示当前进展,evidence 指向真实结果。空证据列表就是没有记录证据,不能因为检查步骤写得完整而当成已经执行。
实际准备样例时,可以在文档开头、中部和末尾各放一句独特的标记句。验证保存后的文本块是否保留这些句子,比只看"产生了几个块"更能发现内容丢失。不过,标记句检查仍是抽样;如果需求要求全文无损,还要定义分块拼接、段落分隔等规则,并检查完整内容。
课程配套清单使用了 passes: false。这个布尔值可以表示尚未通过,却不能单独区分"没开始""正在修"和"环境阻塞"。当这些差异影响后续调度时,就值得引入更明确的状态。
清单为什么是 Harness 的基本单元?
这里的"基本单元"可以理解为:选任务、执行验证和生成交接,都围绕同一条功能记录工作。
选任务时,读取哪一项尚未开始、依赖是否满足;验证时,读取这一项规定的检查;交接时,引用同一项的状态与证据。这样就不需要在聊天摘要、进度笔记和验收报告里各写一套"完成了什么"。
但 JSON 只是存储格式。如果执行流程从来不读取这份清单,或者允许没有证据就把它写成通过,它依然只是备忘录。 真正的约束来自使用方式:当前选中了哪一项,哪些检查是必需的,什么结果允许状态变化。
同样,不能为了让当前实现通过,悄悄删除验收项或降低标准。需求确实改变时,应先记录变化,再按新的约定验收;完成状态必须对应一个明确版本的需求和实现。
三、状态变化必须由证据驱动
功能清单提供共同记录,状态转换规定什么时候可以继续。一个最小模型可以使用四种状态:
| 状态 | 含义 | 应留下的信息 |
|---|---|---|
not_started |
尚未开始 | 目标、检查方式和依赖 |
active |
正在实现或验证 | 当前修改、失败结果与下一步 |
blocked |
暂时不能继续 | 阻塞原因、恢复条件 |
passing |
当前版本满足必需检查 | 对应版本、检查结果与证据位置 |
下面是本文建议的状态模型。图中的"通过"有明确前提;"阻塞"也有解除条件。

类型检查失败时,当前项继续处于 active,先处理失败原因;真实界面无法启动,而且原因是当前环境缺失必要条件时,则记录 blocked。两者都不能标为通过,但下一步动作不同:一个需要修复实现,另一个需要恢复执行条件。
返工还应有轮数或时间上限。达到上限之后,停止继续消耗,保留失败结果和下一步。本文的四态模型把这种"需要外部决定才能继续"的情况也记为 blocked,并要求原因写明"达到上限";实际系统也可以单设停止状态,关键是不能把它混同于成功。
在实现"通过门控"时,可以让验证脚本根据检查结果更新状态,也可以先由操作者核对证据后更新。前者更容易机械执行,后者适合暂时依赖人工界面验收的项目。无论采用哪一种,检查未覆盖的部分都要保留下来。
这里还需要修正一个容易过度简化的理解:passing 不应被视为永久有效。如果后续改动影响了已验证路径,或者验收需求变了,旧证据就未必适用于当前版本,需要重新验证。保留历史通过记录,与承认当前状态需要重验,可以同时成立。
当某项被阻塞时,也不能把它伪装成已完成,再悄悄打开其他任务。如果确实需要切换,应明确记录阻塞项的保存状态,再选择依赖独立的工作,保证当前真正推进的功能仍然只有一项。
到这里,我们已经知道做什么、如何验收和如何记状态。接下来还差一件事:失败之后,Agent 凭什么决定改哪里?
四、运行反馈要能缩小排查范围
"问答为空"为什么还不是足够的修复依据?
问答为空是用户能观察到的现象,但它没有直接说明故障在哪一层。文件没有读入、分块丢失内容、检索没有命中,或界面没有展示,都可能导致类似结果。
P04 的练习因此包含两类反馈:运行时信号用来理解程序实际发生了什么,架构检查用来发现修改是否跨越了既定边界。前者帮助定位问题,后者限制修复方式。
先看运行信号。应用在关键阶段记录输入长度、输出块数、空块数,可以帮助我们判断内容在哪一段链路发生变化。用同一个操作编号关联这些记录,就能沿一次索引过程向前追查,避免把不同操作的日志拼成错误结论。
字段不是越多越好。当前任务关心内容丢失,那么"生成了多少块"之外,就要有能区分"块存在"和"块内容有效"的信号。
P04 的具体缺陷:块对象存在,内容却被清空
只读核对 starter 时,可以看到两处生成块内容的逻辑使用了相同条件:
ts
const chunkContent = content.length > 1000 ? '' : buffer.trim();
content 是整篇原始文档,buffer 是当前累积的分块文本。因此,条件判断的是整篇文档是否超过 1000 字符。对于会产生文本块的长文档,这段逻辑把块内容设置为空字符串,随后仍创建块对象并加入数组。源码依据:P04 indexing-service.ts
于是两个结果可以同时成立:日志显示输出块数大于零,但块里已经没有可供后续使用的文本。只检查数量,就会漏掉真正影响问答的内容问题。
下面的图展示如何把模糊症状逐步转成定向修复的依据。它是源码推导与建议观察步骤,不是已经执行的诊断记录。

这也解释了为什么"补一条失败日志"不一定够。有效反馈应该同时说明:原本预期什么、实际观察到什么、两者差在哪里。
例如,在真正取得结果后,一条有用的反馈可以表达为:"固定长文档预期保留三个位置的标记句;当前保存的块内容为空;应优先检查分块生成与保存路径。"它把下一步指向相关环节,同时没有把尚未证实的假设写成结论。
修复后还要防止一个假成功:把空块过滤掉,可能让空块数归零,却也把文档内容一起丢掉了。所以需要同时检查块内容非空、原文内容保留,以及既有使用路径可用。每个指标只能回答它实际覆盖的问题。
修复路径也需要边界
假如 Agent 为了让界面暂时显示答案,在渲染层另写一套文件读取和索引逻辑,它可能绕过服务层故障,也可能制造第二套相互不一致的实现。
对于当前课程应用,界面、跨进程通信和服务层各有职责。架构检查的作用,是把违反这些职责的改动报告出来。反馈最好包含违规位置、触犯的规则,以及允许的调用方向,这样 Agent 才能据此调整。
规则写进文档,提供的是指导;检查脚本发现违规,提供的是可执行反馈。是否阻止交付,还取决于验收流程是否把这个检查列为必需项。P04 starter 缺少架构检查脚本,因此不能把"应该检查"写成"现有系统已经强制检查"。
运行反馈与架构约束在这里相互补充:一个帮助找到问题,一个帮助确认修复没有引入新的边界破坏。
五、把三个机制接成一次有界的工作过程
把前面的内容放回长文档问题,一轮工作可以这样组织。以下是实施建议,本文没有启动新的产品实验。
开始前,先固定目标和样例。 当前只处理长文档索引内容丢失;准备一份固定长文档与一份短文档,把需要保留的内容和原有问答行为写入检查项。其他独立功能留在队列中。
调查时,先记录可观察事实。 如果只知道问答为空,就补足相关阶段信号;如果源码已经显示清空内容的条件,也要区分源码推导和真实运行结果。一次假设失败后,保留反证,再选择下一步。
实施时,围绕当前项修改。 修复相关逻辑,补充能覆盖这次故障的用例,检查改动是否越过架构边界。如果发现新的独立需求,记录下来,避免把当前任务扩展成另一项产品建设。
验收时,按类型检查 → 自动测试 → 真实 UI 流程递进。 本项目中的类型检查只能证明静态检查成立;自动测试证明用例覆盖的行为;真实界面流程再确认用户能否完成对应操作。低层通过不能替代高层证据。还要检查保存的块是否真正保留内容,防止只消除了表面错误。
结束时,让状态与证据一致。 必需项通过才记录 passing;仍有实现问题就保留当前项;环境或执行上限阻止继续时,记录阻塞原因和恢复动作。证据至少应能定位对应代码版本、样例、检查结果和未验证项。
这一阶段我形成的最有用的判断,是把三件事连起来:任务边界决定这次解决哪个问题,功能清单定义怎样才算解决,运行反馈帮助失败后的修改继续朝这个目标靠近。
学习中已经完成的,是范围辨析、状态规则理解,以及 P04 缺陷的只读分析。WIP 对照、真实日志诊断、修复耗时与架构违规对比,还需要独立实验取证。没有这些数据,就不把"规则解释得通"写成"效果已经测出来"。
下一阶段再进一步讨论:这些验证是否充分,以及怎样识别 Agent 提前宣布完成。阶段 4 先建立一个基础------当前工作有边界,每次状态变化有依据,失败反馈能够指导下一次具体行动。
参考材料
文中的状态扩展、样例检查和范围划分是学习后的工程整理;没有照搬讲义中未经本次核实的效率百分比,也没有把永久通过、JSON 自动强制约束当作已有能力。