Skip to Content

Harness design for long-running application development

论文链接:https://www.anthropic.com/engineering/harness-design-long-running-apps 

代码链接:

摘要

在智能体编码的前沿领域,框架设计是提升性能的关键。以下是我们如何推动 Claude 在前端设计和长期自主软件工程方面取得更大进步的案例。

介绍

过去几个月,我一直在研究两个相互关联的问题:一是让 Claude 生成高质量的前端设计,二是让它无需人工干预即可构建完整的应用程序。这项工作源于我们之前对前端设计 Skill 和长期运行的编码 Agent 框架的改进。当时,我和同事们通过快速的工程设计和框架优化,显著提升了 Claude 的性能,使其远超基准水平——但最终都遇到了瓶颈。

为了取得突破,我寻求能够跨越两个截然不同的领域的新型人工智能工程方法:一个领域以主观喜好为标准,另一个领域则以可验证的正确性和可用性为标准。我从生成对抗网络(GAN)中汲取灵感,设计了一个包含生成器和评估器的多智能体结构。构建一个能够可靠且有品位地对输出进行评分的评估器,意味着首先要制定一套标准,将“这个设计好不好?”之类的主观判断转化为具体的、可评分的指标。

随后,我将这些技术应用于长时间自主编码,并借鉴了我们之前框架搭建工作中的两个经验:将构建过程分解成易于处理的小块,以及使用结构化工件在不同会话之间传递上下文。最终成果是一个由三个智能体组成的架构——plannergeneratorevaluator ——能够在长达数小时的自主编码会话中生成功能丰富的全栈应用程序。

Why naive implementations fall short

我们之前已经证明,框架设计对长时间运行的智能体编码的效率有着显著影响。在早期的实验中,我们使用了一个 initializer agent 将产品规格分解成任务列表,并使用一个 coding agent 逐项实现这些任务、一次完成一个功能,并在不同会话之间通过交接产物(artifacts)来传递上下文。更广泛的开发者社区也逐渐形成了类似的认识,例如“Ralph Wiggum”方法就利用钩子或脚本来保持智能体持续迭代。

但有些问题依然存在。对于更复杂的任务,智能体仍然容易随着时间的推移而偏离轨道。在分析这个问题时,我们观察到智能体在执行此类任务时存在两种常见的故障模式

首先,随着上下文窗口逐渐填满,模型在执行长时间任务时往往会失去连贯性(参见我们关于上下文工程的文章)。一些模型还会表现出“上下文焦虑”,即当它们接近自身认为的上下文极限时,就会过早地结束工作。上下文重置——完全清除上下文窗口并启动一个新的智能体,同时配合结构化的交接机制(该机制会传递前一个智能体的状态和后续步骤)——可以解决这两个问题。

这与压缩不同,压缩会将对话的早期部分进行原地概括,以便同一 Agent 能够继续处理缩短后的历史记录。虽然压缩保持了连续性,但它并不能让 Agent 从头开始,这意味着上下文焦虑仍然存在。重置可以提供一个全新的状态,但代价是交接工件会保留足够的状态,以便下一个 Agent 能够顺利地接手工作。在我们之前的测试中,我们发现 Claude Sonnet 4.5 的上下文焦虑非常严重,仅靠压缩不足以实现出色的长时间任务性能,因此上下文重置成为框架设计中必不可少的环节。这解决了核心问题,但增加了编排的复杂性、token 开销和每次框架运行的延迟。

第二个问题,也是我们之前没有讨论过的,是自我评价。当被要求评价自己完成的工作时,员工往往会自信地称赞自己的作品——即使在人类观察者看来,作品的质量明显平庸。这个问题在设计这类主观性较强的任务中尤为突出,因为这类任务没有像软件测试那样的二元评判标准。一个布局是精致还是平庸完全取决于主观判断,而员工在评价自己的作品时往往会倾向于给出更高的评价。

然而,即使在那些结果可验证的任务中,智能体有时仍然会表现出判断失误,从而影响其完成任务的表现。将执行任务的智能体与评估任务的智能体分开,被证明是解决这一问题的有效方法。这种分离本身并不能立即消除这种宽容;evaluator 仍然是一个 LLM,它倾向于对 LLM 生成的输出给予较高的评价。但是,调整一个独立的 evaluator 使其保持怀疑态度,远比让 generator 对其自身的工作进行批判性思考要容易得多。一旦有了这种外部反馈,generator 就有了可以迭代的具体依据。

Frontend design: making subjective quality gradable

我首先从前端设计入手进行实验,因为自我评价的问题在这里最为明显。如果没有人干预,Claude 通常会倾向于安全、可预测的布局,这些布局技术上功能齐全,但视觉上却平淡无奇。

我构建前端设计框架时,有两个洞见。首先,虽然美学无法完全用分数来衡量——而且个人品味总是因人而异——但我们可以通过编码设计原则和偏好的评分标准来改进设计。“这个设计美观吗?”很难给出一致的答案,但“它是否符合我们对优秀设计的原则?”则为 Claude 提供了一个具体的评分标准。其次,通过将前端生成与前端评分分离,我们可以创建一个反馈循环,从而驱动 generator 输出更优质的内容。

基于此,我编写了四项评分标准,并在提示中将其提供给了 generator 和 evaluator:

  • Design quality:设计是否感觉像是一个连贯的整体,而不是各个部分的简单堆砌?优秀的设计意味着色彩、字体、布局、图像和其他细节相互融合,共同营造出一种独特的氛围和品牌形象。
  • Originality:是否存在自定义决策的痕迹?还是模板布局、库默认设置或人工智能生成的图案?人类设计师应该能够识别出精心设计的创意。未经修改的现成组件——或者像白色卡片上的紫色渐变这样的人工智能生成特征——在这里都无法被识别。
  • Craft:技术执行:字体层级、间距一致性、色彩和谐、对比度。这考察的是技术能力,而非创意。大多数合理的实现方式在这方面都做得很好;失败则意味着基本功有问题。
  • Functionality:可用性与美观无关。用户能否理解界面功能、找到主要操作并完成任务而无需猜测?

我更注重设计质量和原创性,而非工艺和功能性。Claude 在工艺和功能性方面本身就表现出色,因为所需的技术能力对这个模型来说往往是与生俱来的。但在设计和原创性方面,Claude 的作品充其量只能算平庸。评分标准明确惩罚了过于通用的“人工智能粗糙”模式,并通过提高设计和原创性的权重,促使模型在美学上进行更多冒险尝试

我使用少量样本并附有详细的分数细分来校准 evaluator。这确保了 evaluator 的判断与我的偏好一致,并减少了迭代过程中分数的偏差。

我基于 Claude Agent SDK 构建了循环,这使得流程编排非常简洁。首先,generator Agent 会根据用户提示创建一个 HTML/CSS/JS 前端。我为 evaluator 提供了 Playwright MCP,使其能够直接与实时页面交互,然后对每个标准进行评分并撰写详细的评论。实际上,evaluator 会自行浏览页面,截屏并仔细研究实现方式,然后再进行评估。这些反馈会作为输入返回给 generator,用于下一次迭代。每次生成运行 5 到 15 次迭代,每次迭代通常会根据 evaluator 的评论,引导 generator 朝着更明确的方向发展。由于 evaluator 是主动浏览页面而不是对静态截图进行评分,因此每个循环都需要实际运行时间。完整的运行时间最长可达四个小时。我还指示 generator 在每次评估后做出战略决策:如果评分趋势良好,则优化当前方向;如果方法无效,则转向完全不同的美学风格。

在多次迭代中,evaluator 的评价逐渐提高,最终趋于稳定,但仍有提升空间。有些版本是逐步改进的,而另一些版本则在迭代之间出现了显著的审美转变。

这些评判标准的措辞以我未曾完全预料的方式引导了 generator。例如,诸如“the best designs are museum quality”之类的表述,促使设计作品朝着特定的视觉方向发展,这表明与这些标准相关的提示直接塑造了输出结果的特征。

虽然分数通常会随着迭代次数的增加而提高,但这种提升并非总是呈现清晰的线性趋势。后期的实现整体上往往更好,但我经常会遇到这样的情况:我更喜欢中间的迭代版本而不是最后一次迭代。实现的复杂度也往往会随着迭代次数的增加而增加,generator 会根据 evaluator 的反馈寻求更具挑战性的解决方案。即使在第一次迭代中,输出结果也明显优于完全没有提示的基准版本,这表明在 evaluator 的反馈促使模型进一步改进之前,评估标准及其相关语言本身就已经引导模型摆脱了通用的默认设置。

举个例子,我让模型为一家荷兰艺术博物馆设计网站。到了第九次迭代,它生成了一个简洁的深色主题首页,页面内容是虚构的博物馆。页面视觉效果不错,但基本符合我的预期。然而,到了第十次迭代,它彻底放弃了之前的设计思路,将网站重新构想成一个空间体验:一个3D房间,房间内铺设着用CSS透视渲染的方格地板,艺术品以自由形式悬挂在墙上,展厅之间的导航不再是滚动或点击,而是通过门来切换。这种创造性的飞跃,是我之前从未在单次迭代的模型中见过的。

Scaling to full-stack coding

基于这些发现,我将这种受生成对抗网络(GAN)启发的模式应用于全栈开发。generator-evaluator 循环自然地映射到软件开发生命周期中,其中代码审查和质量保证(QA)与设计 evaluator 发挥着相同的结构性作用。

The architecture

在我们之前长期运行的测试框架中,我们通过 initializer agent 、一次处理一个功能的 coding agent 以及会话间的上下文重置来解决多会话编码的一致性问题。上下文重置是关键所在:该测试框架使用了 Sonnet 4.5,而 Sonnet 4.5 存在之前提到的“上下文焦虑”问题。创建一个能够在上下文重置后良好运行的测试框架是确保模型持续执行任务的关键。Opus 4.5 在很大程度上消除了这种行为,因此我能够完全从该测试框架中移除上下文重置。在整个构建过程中,所有 Agent 都作为一个连续的会话运行,Claude Agent SDK 的自动压缩功能则负责处理过程中不断增长的上下文

这项工作是在原有框架的基础上,构建了一个三智能体系统,每个智能体都针对我在之前的运行中观察到的特定缺陷进行改进。该系统包含以下智能体角色:

  • Planner。我们之前长期使用的工具需要用户预先提供详细的规格说明。为了实现这一步骤的自动化,我创建了一个 Planner Agent,它能够接收 1-4 句话的简单提示,并将其扩展为完整的产品规格说明。我要求它在范围上设定得更远大一些,并专注于产品背景和高层技术设计,而不是具体的实现细节。之所以这样强调,是因为我担心如果 Planner Agent 试图预先指定细粒度的技术细节,一旦出错,规格说明中的错误就会波及到后续的实现。因此,将 Agent 的工作范围限定在需要交付的成果上,并让它们在工作中逐步摸索,似乎更为明智。我还要求 Planner Agent 寻找机会,将 AI 功能融入到产品规格说明中。(参见文末附录中的示例。)
  • Generator。之前框架中采用的“一次只开发一个功能”的方法在范围管理方面效果很好。我在这里也采用了类似的模型,指示 generator 以迭代的方式工作,每次从规范中选取一个功能。每个迭代都使用 React、Vite、FastAPI 和 SQLite(后来是 PostgreSQL)技术栈来实现应用程序,并指示 generator 在每个迭代结束时进行自我评估,然后再移交给 QA 团队。它还使用了 Git 进行版本控制。
  • Evaluator。早期框架中的应用程序看起来往往令人印象深刻,但实际使用时却存在诸多缺陷。为了发现这些缺陷,evaluator 使用 Playwright MCP 模拟用户操作,逐个点击运行中的应用程序,测试 UI 功能、API 端点和数据库状态。然后,evaluator 根据发现的缺陷以及一套基于前端实验构建的标准(此处进行了调整,涵盖产品深度、功能、视觉设计和代码质量)对每个迭代进行评分。每个标准都设定了严格的阈值,如果任何一项低于该阈值,则该迭代失败,generator 会收到关于失败原因的详细反馈。

每次迭代开始前,generator 和 evaluator 都会协商一份迭代契约:在编写任何代码之前,就该部分工作的“完成”标准达成一致。之所以这样做,是因为产品规格说明有意写得比较概括,而我希望通过这一步骤来弥合用户故事和可测试实现之间的差距。generator 提出要构建的内容以及如何验证成功,evaluator 则审查该提案,以确保 generator 构建的内容是正确的。双方反复沟通,直到达成一致。

通信通过文件进行:一个 Agent 写入一个文件,另一个 Agent 读取该文件并做出响应,响应内容可以是写入该文件本身,也可以是写入一个新文件,供之前的 Agent 读取。generator 随后根据约定的协议进行构建,最后将工作成果交付给质量保证团队。这样既保证了工作成果忠实于规范,又避免了过早地过度定义实现细节。

Running the harness

在这个测试框架的第一个版本中,我使用了 Claude Opus 4.5,并针对完整的测试框架和单 Agent 系统运行用户提示以进行比较。之所以选择 Opus 4.5,是因为在开始这些实验时,这是我们当时最好的编码模型。

我编写了以下提示,以生成一个复古视频游戏制作工具:

Create a 2D retro game maker with features including a level editor, sprite editor, entity behaviors, and a playable test mode.

下表显示了框架类型、运行长度和总成本。

虽然框架的价格贵了 20 多倍,但输出质量的差异立竿见影。

我原本以为会看到一个可以构建关卡及其组成部分(精灵、实体、图块布局)的界面,然后点击播放按钮即可实际游玩该关卡。我首先打开了单 Agent 游戏的输出文件,初始界面看起来与我的预期相符。

然而,随着我不断点击操作,问题开始显现。布局浪费空间,固定高度的面板导致视口大部分区域空空如也。工作流程也十分僵化。尝试填充关卡时,系统提示我先创建精灵和实体,但用户界面没有任何提示引导我完成这些步骤。更重要的是,游戏本身存在问题。我的实体出现在屏幕上,但对任何输入都没有反应。深入研究代码后发现,实体定义和游戏运行时之间的连接出现了故障,但表面上却找不到任何线索。

在评估完单 Agent 后,我将注意力转向了框架 Agent。这次测试同样始于一句提示,但 planner 将该提示扩展为一个包含 16 项功能的规范,并分十个迭代周期完成。这远远超出了单 Agent 的尝试范围。除了核心编辑器和游戏模式之外,规范还要求包含精灵动画系统、行为模板、音效和音乐、AI 辅助的精灵生成器和关卡设计器,以及带有可分享链接的游戏导出功能。我授予了 planner 访问我们前端设计 Skill 的权限,它读取并使用该 Skill 为应用程序创建了一套视觉设计语言,作为规范的一部分。在每个迭代周期,generator 和 evaluator 都会协商一份合同,明确该迭代周期的具体实现细节,以及用于验证完成情况的可测试行为。

与单 Agent 版本相比,该应用程序立即展现出更高的完善度和流畅度。画布充分利用了视口,面板尺寸合理,界面视觉风格一致,与规范中的设计方向相符。我在单 Agent 版本中发现的一些笨拙之处仍然存在——工作流程仍然没有明确提示用户在尝试填充关卡之前应该先创建精灵和实体,我不得不摸索一番才能弄明白。这与其说是该框架旨在解决的问题,不如说是基础模型产品直觉上的缺陷,尽管这也表明框架内部的针对性迭代可以进一步提升输出质量。

使用编辑器时,框架 Agent 相比单 Agent 的优势愈发明显。精灵编辑器功能更丰富、更完善,工具面板更简洁,颜色选择器更出色,缩放控制也更易用。

由于我要求 planner 在其规格中融入人工智能功能,该应用程序还内置了 Claude 集成,让我能够通过提示生成游戏的不同部分。这极大地提高了工作效率。

最大的区别在于游戏模式。我终于可以控制角色移动并进行游戏了。物理引擎还有一些瑕疵——我的角色跳上平台后却和平台重叠了,这感觉很不自然——但核心功能是正常的,而单 Agent 模式却没能做到这一点。稍微探索了一番之后,我发现 AI 在关卡设计上存在一些局限性。有一堵高墙我无法跳过,所以被困住了。这表明该应用还有一些常识性的改进空间,并且可以通过一些特殊情况的处理来进一步完善。

阅读日志后可以明显看出,evaluator 严格按照规范执行了开发工作。每个迭代周期,evaluator 都会逐一检查迭代合同中的测试标准,并通过 Playwright 对运行中的应用程序进行测试,并对任何偏离预期行为的情况提交错误报告。合同非常细致——仅第三个迭代周期就包含了 27 条涵盖关卡编辑器的标准——evaluator 的发现也足够具体,无需额​​外调查即可采取行动。下表列出了评估人员发现的一些问题示例:

要让 evaluator 达到这个水平需要付出很多努力。Claude 本身并不是一个合格的质量保证 Agent。在早期运行中,我发现它虽然识别出了合理的问题,但却会自我安慰,认为这些问题无关紧要,最终还是批准了项目。此外,它的测试也往往流于表面,而非深入探究各种极端情况,因此一些更隐蔽的 bug 常常被忽略。调整流程是读取 evaluator 的日志,找出它判断与我判断相悖的例子,并更新质量保证提示以解决这些问题。经过几轮这样的开发循环,evaluator 的评分方式才最终达到我认可的合理水平。即便如此,测试结果仍然显示出该模型在质量保证能力上的局限性:一些小的布局问题、某些地方交互体验不够直观,以及 evaluator 尚未彻底测试的更深层嵌套功能中存在的未发现 bug。显然,通过进一步的调整,还有更大的验证空间。但与单独运行(应用程序的核心功能根本无法正常工作)相比,提升是显而易见的。

Iterating on the harness

第一组测试结果令人鼓舞,但它也存在体积庞大、速度缓慢且成本高昂的问题。合乎逻辑的下一步是找到在不降低性能的前提下简化框架的方法。这部分是出于常识,部分则源于一个更普遍的原则:框架中的每个组件都编码了关于模型自身无法完成的任务的假设,而这些假设值得进行压力测试,因为它们可能不正确,而且随着模型的改进,这些假设很快就会过时。我们的博文《构建高效 Agent》将这一基本理念概括为“找到尽可能简单的解决方案,仅在必要时增加复杂性”,对于任何维护 Agent 框架的人来说,这都是一个反复出现的模式。

在我第一次尝试简化框架时,我大幅缩减了框架,并尝试了一些富有创意的新想法,但我未能重现原始框架的性能。此外,我也很难判断框架设计的哪些部分实际上是在承受负载,以及它们是如何承受负载的。基于之前的经验,我转而采用更系统的方法,每次移除一个组件,并评估其对最终结果的影响。

在进行这些迭代周期的同时,我们也发布了 Opus 4.6,这进一步激励我降低框架的复杂性。我们有充分的理由预期 4.6 版本需要的脚手架会比 4.5 版本更少。正如我们在发布博客中所述:“Opus 4.6 的规划更加周密,能够更长时间地维持Agent 任务,在更大的代码库中运行更加可靠,并且拥有更强大的代码审查和调试能力,可以发现自身的错误。” 它在长上下文检索方面也得到了显著改进。而这些正是该框架旨在补充的功能。

Removing the sprint construct

Results from the updated harness

What comes next

随着模型不断改进,我们可以大致预期它们能够运行更长时间,并处理更复杂的任务。在某些情况下,这意味着随着时间的推移,模​​型框架的重要性会降低,开发者可以等待下一代模型,并看到某些问题自行解决。另一方面,模型越好,就越有空间开发出能够完成模型基线能力之外的复杂任务的框架。

考虑到这一点,这项工作中有一些值得借鉴的经验。始终对正在构建的模型进行实验,分析其在实际问题上的表现,并调整其性能以达到预期结果,这始终是一个好习惯。在处理更复杂的任务时,有时可以通过分解任务并针对问题的每个方面应用专门的 Agent 来提升性能。此外,当新模型发布时,通常最好重新检查框架,移除不再对性能产生影响的部分,并添加新的部分以实现以前可能无法实现的更强大的功能。

这项研究让我确信,随着模型的改进,有趣的硬件组合空间并不会缩小。相反,它会不断扩展,而人工智能工程师的真正乐趣在于不断寻找下一个新颖的组合。

Last updated on