让Harness替用户说话:自动化Agent进度报告的设计

Harness可在四个时机自动生成用户席位消息,替代人类重复记账,一次运行生成607条。
文章介绍Favur的Harness如何在用户席位自动生成消息,替代人类在长对话中大量的重复性"记账"输入。这类消息(如"继续""测试失败了")只是复述程序本可从自身记录读到的状态,可被自动生成。Harness选择把它们作为追加的用户消息而非system prompt中的状态,以保留对话历史并避免破坏prefix caching。Harness只在四个时机发言:模型纯文本应答、跨越工作单元接缝、委派Agent返回、外部带消息。一次真实运行中自动生成607条用户消息,其中396条是进度报告。通过轮数计数器触发停滞通知,21个停滞Agent全部完成步骤,无Agent被终止;通知聚集在无自然终点的审查类步骤。每份报告写入时即打标签,供上下文压缩器决定如何处理。
编排Agent团队的Harness正在接管人类在用户席位上的大部分重复劳动。Favur公开的一次运行数据显示,其Harness自动生成了607条本应由用户发送的消息,其中396条是进度报告。
用户消息里,大部分只是账目

打开一段与Coding Agent的长对话,只看用户发的消息,它们会分成两堆。小的一堆是真正需要人做的决策,比如调整范围或在两个设计之间做选择。大的一堆是记账:"继续""那部分完成了,去做解析器""调用完成前跑一下linter""你还有两个失败的测试"。
大堆的每一行都在复述程序能从自身记录里读到的东西。任务列表知道什么完成了,测试运行器知道什么失败了,工作流知道下一步是什么、必须产出什么。
大堆还有语气。在会话后期,记账会变得简短,"你已经检查过了"仍然是记账——它报告一个关于会话的事实:Agent在同一个地方待了太久。这个程序也能数出来。
为什么不把记账塞进system prompt
最明显的做法是把它写进system prompt,但这里有两个问题。
带状态的system prompt每个请求都要重建。Agent从第三步走到第四步时,关于第三步的文本被替换掉了,模型能看到自己在哪,却失去了怎么走过来的记录。
每轮都变的长prompt还会破坏prefix caching。前缀缓存自上而下匹配、在第一个差异处停止,放在prompt顶部的状态会丢掉它下面几乎全部可复用内容。
用户消息没有这两个问题。它被追加且保留,一连串的用户消息读起来就是一份关于工作如何推进的日志。system prompt则可以只保留整个任务固定不变的部分,比如角色、标准和流程大纲,整个对话期间很少改动。
Prefix Caching(前缀缓存) 是大模型推理服务中一项关键的性能优化。由于Transformer对输入的处理是逐token依序注意力计算,一段长prompt中前面部分若与历史请求完全一致,系统可以复用其已算出的键值缓存(KV cache),不必重复计算。但缓存匹配是自上而下的:从prompt第一个token起逐位比对,一旦某个位置出现差异,差异点之后的所有内容都无法复用。这意味着如果把每轮都在变化的状态放在prompt最前面,差异会出现在极靠前的位置,其下方几乎全部内容(包括固定的角色、标准、流程大纲)都要重新计算,缓存命中率骤降,延迟和成本随之上升。反之,将动态状态以用户消息追加到对话末尾,prompt的稳定前缀得以保持完整,缓存大部分命中,同时消息天然按时间顺序保留,形成一份可追溯的工作日志。
四个发言时机
在错误的时机生成回复就是噪音。Favur的Harness只在四个时刻写一条进度报告:
- 模型用纯文本回答、没有调用任何工具。这就是最上面说的"停下来等"的情况。
- Agent跨过了自己工作里的一个接缝,也就是开始或完成了一个工作单元。
- 它委派的某个Agent带着结果回来了。
- 对话之外有东西给它带消息,比如监督Agent给出的评分变化、停滞通知,或来自人的备注。
普通的工具调用之后Harness保持沉默。读文件和跑shell命令只返回结果,模型直接从结果走向下一步动作。
一份真实的进度报告长什么样
下面这份报告发给顶层Agent,当时它进入工作流的第四步。内容经过简化、内部工具和字段名换成了普通词,但结构和指令保持原样:
**ent(entry criteria)与exit criteria(进入条件与退出条件)** 是工作流步骤两个互补的契约。进入条件规定"要开始做这一步,必须先满足什么",例如"架构已批准";退出条件规定"这一步算完成,必须交付什么",例如"已创建六份上下文文档"。把它们在步骤开始时一次性交给Agent,等于提前告诉它验收标准,让它在动手前就知道会被检查哪些证据、以什么形式提交。这种设计把"是否完成"从主观判断变成可校验清单,既避免Agent过早声称完成,也避免它在不确定里无休止打磨。与之配套的"步骤完成工具"要求Agent在调用时附上证据(如文档列表),空列表会校验失败——用程序化的门槛把意图落实为动作,减少了仅凭自然语言"我完成了"带来的模糊空间。
# Progress Report
## Current Step: 4 - Context Init
Entry criteria: Architecture approved
Exit criteria: Six context documents created
Create the six documents. Read all six back to confirm non-empty.
### How to Advance This Step
When you have completed all exit criteria above, call the
step-completion tool with your evidence.
If you cannot proceed, call it with success set to false instead.
Do not call it speculatively before exit criteria are met.
### Step Requirements
A list of the documents created (required).
Omitting it or passing an empty list will fail validation.
Agent的下一条消息只有一行:"六个上下文文档已创建并通过模板校验。正在推进。"
退出条件在步骤开始时送达,Agent在动手之前就知道会被检查什么。报告点名了检查将要索取的证据,并说明空答案会失败。它还总是留一扇后门:一种"我做不完"的上报方式,算作合法动作。
其他段落只在有话要说时出现。错误段落只在有错误时显示,悬而未决的问题和常驻指令同理。
有些段落属于特定类型的Agent。几个Agent维护故障计数器,每个计数器统计某一种失败,比如某次委派出错;计数器达到上限就要求Agent把问题上交,而不是继续重试。报告会打印这些计数器。测试Agent的报告会重申它自己的工作规则:不能改代码,三次相同失败必须升级。一个深陷长任务的模型可能忘了很久以前读到的规则,而印在最新消息里的规则是它刚刚读过的。
Harness替你失去耐心
当Agent原地打转时,第二扇门最重要。一个反复重读相同文件的Agent并没有停下等待,所以前三个时机都不会触发。人看着会介入。
Harness改用计数。在一个步骤内循环十五轮仍未完成,Agent会收到三段式通知。第一段说明计数并点名步骤。第二段是本文开头引用的那两句话,接着是这一步仍然欠交的证据。第三段再次提供第二扇门:真的做不完的Agent要带着已有发现上报失败,通知以"不要继续探索了"收尾。
没有任何程序去读transcript判断Agent是否看起来迷路了。触发器是轮数计数,阈值是普通配置。计数器分不清一个迷路的Agent和一个离完成只差两轮的Agent,所以措辞必须对两者都安全。它指向出口,索取Agent手里已有的证据。
再五轮之后是更直白的一条。开头说Agent在二十轮后仍停在该步骤,下一句是"之前的警告被忽略了"。它要求Agent在下一轮就调用步骤完成工具,并以后果收尾:继续探索"将导致该Agent被终止"。
在这次运行中,21个Agent各收到一次第一类通知。其中17个从未见到更严厉的那条。4个见到了,其中2个收到了第二次。
上下文压缩器(context compactor) 是长任务Agent系统中对抗上下文窗口溢出的组件。当累积的对话逼近模型窗口上限时,压缩器会缩减旧消息,为最新内容腾出空间。压缩通常分两级:轻度压缩保留细节、只精简冗余;重度压缩在窗口几乎填满时把大段历史折叠成摘要或直接丢弃。问题是进度报告这类追加消息既多又长,长时间运行会堆到数百份,其中大量对应早已完成的步骤。如果压缩器一视同仁,重要规则可能被和过时报告一起折叠。因此每份报告在写入时就被打上标签,标签说明优先级与可丢弃性,让压缩器在需要时精准地放过轻度、折叠重度,既保住当前步骤的关键指令,又腾出足够的窗口。
通知之后,Agent做了什么
回头看存储的对话,追踪这21个Agent从收到通知到步骤结束的每一步。
全部21个都关闭了步骤,工作流继续推进。4个在下一轮就完成。14个在三轮内完成,最慢的用了十三轮。
一个Coding Agent一直在最终审查里打转。收到通知后的第一轮就带着被索取的证据提交了完成,下一条消息写着:"工作流处于done状态,所有gate已通过。正在完成。"
收到更严厉警告的4个也完成了,时间点在首次通知之后六到十二轮之间。存储记录里没有任何Agent因停滞被关闭。
通知还出现了聚集。21条中有14条落在同一类步骤上:审查Agent在写结论之前收集证据的那一步。这一步没有自然的终点,因为永远还有一份文件可以打开,而仔细的审查者会一直打开下去。其余七条落在最终审查、规划步骤和一个实现步骤。
Favur的解读是,计数器主要抓住的是彻底性。这些Agent已经拿到了步骤需要的东西,只是没人告诉它们"够了"。计数无法证明通知导致了完成,因为有些Agent可能本来离完成只差一轮。
更小的通知,同一套构建
停滞通知还有更小的亲戚,构建方式相同。
这次运行里两次出现Agent连续三次发出相同或空参数的工具调用。Harness每次都在用户席位回话:"你之前的调用被拒绝或没有效果",告诉Agent先构造合法参数再试,并指明正确参数列在它最新报告当前步骤段落里。
有一次,一个Coding Agent尝试开启一个工作单元,请求在Harness内部失败了。回复如实说明,并承认故障可能出在Harness自己。它随后说明一个合法请求需要什么,并告诉Agent下一轮别碰哪个工具。
这些消息都陈述发生了什么、给出下一步动作、排除错误选项。刚刚失败的模型倾向于尝试相邻操作,而相邻操作往往比单纯重试更糟。
日志有代价
日志会增长,长时间运行会堆积数百份这样的报告,旧的描述的是早已完成的步骤。
所以每份报告在写下时就被打上标签,标签告诉上下文压缩器——Harness里在窗口填满时缩减旧对话的部分——该如何对待它。轻度压缩放过这些报告。重度压缩,也就是窗口几乎填满时运行的那种,可以折叠它们。
人仍然可以介入
转向能力依然保留,因为人的备注走同一条路。它出现在下一份报告的顶部,下面照常是惯用大纲,Agent在一条消息里同时拿到指令和方位。
一次运行的数字
这些数字背后的运行是用TypeScript根据一份简短工作说明构建一个小型桌面应用。其公开运行页记录了174分钟工作和1594次模型请求。
该运行保留了110个Agent对话,其中用户席位上有607条消息,396条是进度报告。另有67条开启Agent工作的新阶段,36条带回委派Agent的结果,30条投递Agent索取的截图。停滞通知及其升级共27条。剩下的大部分是每个Agent开始时带的目标。没有一条是人打的。
Favur没有跑过关掉报告的同任务对照,所以607应被理解为Harness发言的次数。在信任这个想法之前,先在一个任务上跑自己的对比。
Agent等待听到的大部分内容是记账,而记账的程序可以替你说出来。
相关推荐

uGAP:统一100+非洲地理数据集的AI平台
uGAP是开普敦大学团队开发的非洲地理空间AI平台,统一120余个非洲数据集并提供Python SDK,支持数据发现、处理与模型训练一站式工作流,已用于气旋洪水制图等实战场景。

Cursor能控制你的电脑了:AI代理可驱动桌面应用测试
Cursor 新增桌面控制能力,AI 代理可驱动 iOS 模拟器等桌面应用测试自己构建的功能。本文分析这一更新对开发工作流的影响、技术实现可能路径及可靠性考量。

Vibecoding自研推理引擎:理想丰满,现实骨感
Reddit开发者分享Vibecoding自研推理引擎的真实经历:用WebGL2和GLSL在浏览器跑GGUF Q4模型,却陷入tokenizer调试地狱。本文分析浏览器端LLM推理的工程难点,以及为何直接clone llama.cpp是更理性的选择。