长时自主Agent,先解决这8个Harness核心问题
本质上,所有 Harness 设计都是在对抗两类问题:agent 要么开始偷懒、走捷径,要么开始迷糊、犯蠢。有些问题比另一些更难修,但一个写得好的 Harness,确实能解决很多事。
agent 会怎么犯蠢
任务前:上下文没吃够 (Pre-Task)
Agent 在任务开始前没有拿到足够上下文,于是还没正式开工,就已经建立在错误信息或缺失信息上行动了。
要解决这个问题,你需要在任务开始前,系统性检查信息是否不完整、是否互相矛盾。因为一旦开工,这些问题只会一路传染下去。
规划阶段:上下文不完整 (Planning — Incomplete Context)
这一步是 agent 决定用什么路径解决问题的时候。这里最大的风险,是它选错了攻击路径,最后做出来的东西从根上就是错的。
现在单纯因为“蠢”而选错路径,其实已经不算常见了。更常见的是对齐出了问题,也就是它误解了用户到底要什么。
要解决这个问题,你得确保 agent 在开始规划前,已经把所有相关文件都覆盖到了。这里还有个关键前提:你的仓库里不能存在互相冲突的信息。
规划阶段:短期思维 (Planning — Short Term Thinking)
Agent 不会承担那些短期、速成方案带来的后果。这就像雇了廉价软件劳动力,你可能拿到一个“能跑”的东西,但技术债会越滚越大。
解决方法,是在规划阶段反复提醒 agent :它要做的是一个能扩展、能融入整体、易维护、并且尊重良好软件工程范式的方案。说白了,你希望它像创始人一样思考,而不是像一个只想赶紧交差的兼职工程师。
你还可以让 agent 先产出 N 个不同方案,比如 N=5,再交给另一个 agent 去选那个更易维护、在 clean code 原则上得分更高的方案。
任务执行阶段的陷阱
任务阶段:上下文焦虑 (Task — Context Anxiety)
这一步是 agent 真正开始动手解决问题的时候。到目前为止,最大的问题,远远是上下文耗尽。
如果规划足够好、上下文也给对了,现在几乎所有前沿模型 agent 都能以接近 one-shot 的能力,完成足够小的任务。真正出问题的场景,通常是那种复杂、多会话、会消耗数百万 token 的任务。
要确保 handoff prompt 里有足够多的细节,让新会话里的新 agent 能以高信息密度的方式,接住所有继续推进任务所需的内容。要明白,你这里做的,本质上就是一种压缩。之所以有理由相信你能做得比模型提供方原生压缩更好,是因为你比基础模型提供方更了解自己的仓库结构。
任务阶段:偏离计划 (Task — Planning Deviations)
除了上下文膨胀,第二大的问题,是我称之为 planning stickiness 的东西。也就是,agent 偏离了原计划,最后基本在做自己想做的事。
最常见的表现是:你让它做 A,A 很长、很痛苦,也很复杂。结果它做了 A'。它会觉得 A' 和 A 已经挺接近了,但实际上,A' 根本不可能把你带到目标位置。
这不只是结果不好,更麻烦的是,软件本身是可组合的。于是所有依赖 A 的下游代码,现在都会开始围着 A' 接线。那从 A' 往后长出来的一整串东西,本质上都会是错的。
要解决这个问题,你得尽早验证,而且要频繁验证,确认任务方案是不是被正确实现了,是否符合你的预期。这样可以挡住任务列表里的级联故障。
任务阶段:对复杂度的恐惧 (Task — Complexity Fear)
Agent 对复杂度有很深的恐惧。
你让它写一个 5 行函数,通常没什么问题。但如果它觉得自己将要写的是一个 5 万行的类,它就会开始想办法滑过去。
最糟糕的表现,一种是随手写几个 stub 就算交差。更糟的是,直接宣布这件事超出本次会话范围,然后结束。
我最好的猜测是,在 RL 过程中,agent 学会了这样一件事:处理高度复杂的任务时,它往往会犯很多错,而且会因此受到很重的惩罚,所以它会激进地回避这类任务。
讽刺的是,人类也有这个问题。大多数人一想到一个项目有多大工作量,就会无限拖延。生产力教练通常会说,解决办法是从这个任务最简单的版本开始,也就是先做第一步。这样启动能量几乎为零,你会立刻从“准备开始”切到“已经在做了”。
Agent 也差不多。你需要把复杂问题拆成很多看起来没那么吓人的子任务,让每个任务都控制在百行以内,然后把 500 个这样的小任务串起来。
一个很有意思的副作用是,如果你研究过生产力方法,也真的实践过它们,你会发现这些方法拿来管理 agent 一样有效。这会让人非常清楚地意识到,agent 心理其实就是照着我们自己的样子长出来的。
任务完成后的问题
任务后:验证偷懒 (Post-Task — Verification Laziness)
Agent 会选最短的路径做验证。它会写一些很弱的测试,看着这些测试通过,然后以此宣布任务成功。
上下文压力越大,这种“验证一下就行”的行为就会越离谱。最糟糕的情况是,假设你有个函数应该实现行为 A,agent 却给行为 A' 写了测试,看见测试通过,就宣布这个函数工作正常。
缓解方式,是确保负责写验证测试的那个 agent ,拥有尽可能新鲜的上下文,而且它本身就是一个专门规划验证方案的独立 agent 。然后更重要的是,你要验证的,必须是你真正想上线到生产环境里的那种精确行为。
这意味着,如果你在测试前端某个按钮是否能工作,不要去测试“一个通用按钮是不是能工作”。你要想的是,到底怎样才算真的知道这个按钮是好的:
- 你需要看到一张截图,确认这个按钮真的在页面上。
- 你需要真的去模拟点击这个按钮。
- 这个按钮需要触发后端,发出它本来就该发出的 payload。
在你能证明某个东西真的工作之前,它就不算工作。相信我,我和 agent 打交道已经够久了,这句话我是很有把握才会说。
任务后:熵最大化 (Post-Task — Entropy Maximization)
就目前来看,agent 写出的代码通常只是勉强能接受,而且不会主动帮你降低仓库里的熵。
常见情况是,它把函数 X 从行为 A 改成了行为 B,但所有文档还在写行为 A。你的 agent 不会顺手把这些东西一起修掉。
这种情况重复 100 次以后,你就会得到一个完全不可维护的仓库。然后你的 agent 会一直被搞糊涂,并不断做出糟糕决策。
最好的解决办法,是在每次长会话结束后,专门分配一些 token 给一个拥有新鲜上下文的 agent ,让它负责清理状态、消除矛盾、处理 merge conflict、删除死代码和过时文档,等等。
为什么要自己做 Harness
在原生 Harness 里,能用来解决上面这些问题的工具,其实很有限。比如 Codex*()*,连 hooks 都没有。
更重要的是,如果你让 Claude 在你的 Harness 里充当 orchestrator*()*,它自己会被大量与实际任务列表无关的编排上下文撑得很臃肿。你真正想要的,是把 agent 编排层放在任务列表之上,而不是混在任务执行里面。
比如,你可以有一个 agent ,它唯一的职责,就是确保每个 session 都带着一份必须被满足的 algorithmic contract 才能结束。这个 agent 会持续监控 contract 的状态,提醒其他 agent 别偏离 contract,还会额外拉起拥有新鲜上下文的独立 agent ,去判断当前工作质量,并独立验证任务是否真的 done。
当你拥有自己的独立 Harness 时,你就可以设计出专门针对“agent 会怎么犯蠢”这些问题的工作流。
如果你发现,基于你的工作类型,agent 经常因为复杂度而退缩,那你就可以加一个 agent ,专门判断当前 prompt 会落成简单项目,还是高复杂度项目。如果判断为高复杂度,再拉起另一个 agent ,把项目尽可能拆成一口一口能吃掉的小任务,直到最终产出仍然对齐原始 prompt。
如果你发现,agent 没有把仓库维护在一个好状态里,那你就可以在每次 session 结束时,再拉起一些 agent ,去分析本次改动的 blast radius,并确保这次改动触及到的所有内容都没有自相矛盾,也足够干净。至于“干净”怎么定义,可以由你自己定。
最后,也是最重要的一点,你要把 agent 编排层接收和产出的所有内容都做详细遥测,包括 prompts、traces、outcomes,然后设计 rubric 去评估你的 Harness 质量。迭代才是王道,这会让你一点一点逼近更好的 Harness。
OpenForage Harness
顺带一提,我们自己做了一套非常强主张的 Harness,用一种明确而主观的方式去解决上面所有问题。我们对它很自豪,它也已经成了我们日常编码的主力工具。经过很多年的迭代后,等它准备到适合公开使用的时候,我们会把它开源。
结论
对绝大多数人来说,从原生功能出发,先做一套朴素配置,其实已经够用了。
但如果你需要从 agent 那里榨出更强的火力,这篇文章想做的,就是把你在长时运行的自主工程项目里大概率会遇到的问题,尽量提前说清楚。