打造高效框架,让 AI 智能体胜任“长跑”任务

随着 AI 智能体(AI Agents)的能力越来越强,开发者们开始把更艰巨的任务交给它们——那些需要耗时数小时甚至数天才能完成的复杂工作。然而,如何让智能体在跨越多个“记忆周期”的情况下还能保持连贯的工作进度,一直是个棘手的难题。

让智能体长时间运行的核心挑战在于,它们的工作是分段进行的(在不同的会话中),而每一个新开启的会话,本质上都对之前发生的事情“失忆”了。

想象一下,一个软件项目由一群轮班的工程师负责,但每一位新接班的工程师到来时,都完全不记得上一班发生了什么。由于上下文窗口(Context Window,指 AI 一次能处理的信息量上限)是有限的,而大多数复杂项目无法在单个窗口内完成,智能体需要一种方法来连接这些断裂的编程会话。

为了解决这个问题,我们开发了一套双重解决方案,使 Claude Agent SDK 能够跨越多个上下文窗口高效工作:

  1. 初始化智能体(Initializer Agent): 负责在第一次运行时搭建好环境。

  2. 编程智能体(Coding Agent): 负责在后续的每个会话中取得增量进展,并为下一个会话留下清晰的“交接文档”。

你可以在我们提供的 快速入门指南 中找到相关代码示例。

“长跑型”智能体的困境

Claude Agent SDK 是一个强大的通用智能体框架,擅长编程以及其他需要调用工具来收集信息、规划和执行的任务。它拥有上下文管理能力,比如“压缩”(Compaction,即精简历史信息),这理论上能让智能体在不耗尽内存的情况下持续工作。

按理说,有了这些设置,智能体应该能无限期地做有用的工作。

但现实很骨感,仅仅依靠“压缩”是不够的。

即使是像 Claude 3.5 Opus 这样顶尖的模型,如果只是给它一个笼统的指令,比如“克隆一个 claude.ai 网站”,并在循环中运行,它也无法构建出生产级的 Web 应用。

Claude 的失败通常表现为两种模式:

第一,贪多嚼不烂。 智能体倾向于试图“一次性”(One-shot)搞定整个 App。这往往导致模型在实现过程中耗尽了上下文窗口(内存爆了),留给下一个会话的是开发到一半的功能和缺失的文档。接手的智能体不得不猜测之前发生了什么,并花费大量时间试图让半成品的 App 重新跑起来。即便有信息压缩技术,传达给下一个智能体的指令也往往不够清晰。

第二,盲目自信。 这种失败通常发生在项目后期。在构建了一些功能后,后续的智能体实例环顾四周,看到已经有了一些成果,就会草率地宣布“工作完成”。

这让我们把问题拆解为两部分:

  1. 我们需要搭建一个初始环境,为提示词(Prompt)要求的所有功能打好基础,设定好让智能体“一步一个脚印”分功能开发的基调。

  2. 我们需要引导每个智能体朝着目标取得增量进展,并在会话结束时让环境处于干净状态(Clean State)。所谓的“干净状态”,是指代码达到了可以合并到主分支的标准:没有重大 Bug,代码整洁且有文档,就像人类开发者交接工作一样——下一个人可以直接开始新功能,而不用先去收拾烂摊子。

在内部实验中,我们采用了一个两步走的方案来解决这些问题:

  1. 初始化智能体: 最初的第一个会话使用专门的提示词,要求模型设置初始环境:一个 init.sh 脚本,一个记录智能体操作日志的 claude-progress.txt 文件,以及一个展示添加了哪些文件的初始 Git 提交。

  2. 编程智能体: 后续的每一个会话都要求模型取得一点增量进展,然后留下结构化的更新记录。<sup>1</sup>

这里的关键洞察是:要让智能体在面对一个全新的、空白的上下文窗口时,能迅速理解当前的工作状态。 我们通过 claude-progress.txt 文件配合 Git 历史记录实现了这一点。这些实践的灵感,正是源于高效的人类软件工程师每天的工作习惯。

环境管理 (Environment Management)

在更新版的 Claude 4 提示词指南 中,我们分享了一些多上下文窗口工作流的最佳实践,其中提到了一种架构:“为第一个上下文窗口使用不同的提示词”。这个“不同的提示词”要求初始化智能体为未来的编程智能体准备好所有必要的背景信息。

下面我们深入探讨这种环境的几个关键组件。

1. 功能列表 (Feature List)

为了解决智能体试图“一口气吃成胖子”或过早宣布完工的问题,我们让初始化智能体编写一份详尽的功能需求文件,对用户的初始指令进行扩充。

在克隆 claude.ai 的例子中,这意味着列出超过 200 个功能点,例如“用户可以打开新聊天,输入查询,按回车,并看到 AI 的回复”。

最初,这些功能都被标记为“失败”(Failing)状态。这样,后续的编程智能体就能清晰地看到,距离“功能完备”还有多远。

我们要求编程智能体只能修改这个文件中的“通过状态”(passes field),并使用了措辞严厉的指令,如**“严禁删除或编辑测试项,因为这会导致功能缺失或产生 Bug。”** 经过实验,我们决定使用 JSON 格式来存储这个列表,因为相比 Markdown,模型在处理 JSON 时更不容易随意篡改或覆盖文件。

2. 循序渐进 (Incremental Progress)

有了这个初始的功能脚手架,下一轮的编程智能体就被要求一次只处理一个功能。这种增量式的方法对于解决智能体“贪多”的毛病至关重要。

在进行增量开发时,模型在修改代码后必须让环境保持“干净”。在实验中,我们要让模型养成好习惯:每次提交代码到 Git 时都要写清楚描述性的提交信息,并在进度文件中写下工作摘要。 这使得模型能够利用 Git 撤销错误的代码更改,恢复到代码库的正常工作状态。

这种方法也提高了效率,因为它消除了智能体猜测“之前发生了什么”的需要,不再浪费时间去修复基础的 App 运行问题。

3. 测试 (Testing)

我们观察到的最后一个主要失败模式是:Claude 倾向于在没有进行适当测试的情况下就将功能标记为“完成”。

如果没有明确的提示,Claude 往往会修改代码,甚至用单元测试或 curl 命令对开发服务器进行测试,但它经常意识不到功能在端到端(End-to-End,指从用户界面到后端逻辑的完整流程)层面根本跑不通。

在构建 Web 应用时,一旦我们明确提示 Claude 使用浏览器自动化工具,像真人用户一样进行测试,它在验证端到端功能方面表现得相当出色。

图注:Claude 通过 Puppeteer MCP 服务器拍摄的截图,它正在测试 claude.ai 的克隆版。

为 Claude 提供这类测试工具显著提高了性能,因为智能体能够发现并修复那些仅看代码无法察觉的 Bug。

当然,仍有一些问题存在。例如,Claude 的视觉能力和浏览器自动化工具的局限性,使其难以识别所有类型的 Bug。Claude 无法通过 Puppeteer MCP(Model Context Protocol,一种连接 AI 模型和数据的协议)看到浏览器原生的弹窗警告,因此依赖这些弹窗的功能往往 Bug 较多。

快速上手 (Getting up to speed)

万事俱备后,每个编程智能体在开始工作前,都会被提示执行一系列步骤来“弄清方位”,这些步骤虽然基础但非常有用:

  1. 运行 pwd 查看当前工作目录。你只能编辑此目录下的文件。

  2. 阅读 Git 日志和进度文件,快速了解最近的工作内容。

  3. 阅读功能列表文件,选择优先级最高且尚未完成的功能开始工作。

这种方法为 Claude 在每个会话中节省了一些 Token(代币,AI 的计费和计算单位),因为它不需要重新弄清楚如何测试代码。此外,要求初始化智能体编写一个能启动开发服务器的 init.sh 脚本,并在实现新功能之前运行基本的端到端测试,也非常有帮助。

在 claude.ai 克隆项目的案例中,这意味着智能体总是先启动本地开发服务器,利用 Puppeteer MCP 开启新聊天、发送消息并接收回复。这确保了 Claude 能迅速发现 App 是否处于崩溃状态,并立即修复现有的 Bug。如果智能体不管不顾直接开发新功能,很可能会让问题雪上加霜。

基于以上流程,一个典型的会话通常以如下的助手消息开始:

[Assistant] 我将从熟悉环境和了解项目当前状态开始。
[Tool Use] <bash - pwd> (查看路径)
[Tool Use] <read - claude-progress.txt> (读取进度)
[Tool Use] <read - feature_list.json> (读取功能表)
[Assistant] 让我检查一下 git 日志,看看最近做了什么。
[Tool Use] <bash - git log --oneline -20>
[Assistant] 现在我要检查是否有 init.sh 脚本来重启服务器。
<启动开发服务器>
[Assistant] 太棒了!现在我要导航到应用程序,验证一些基础功能是否正常运行。
<测试基础功能>
[Assistant] 基于我的验证测试,我可以看到基础功能运行良好。核心聊天功能、主题切换、对话加载和错误处理都正常工作。现在让我更全面地查看 feature_list.json 文件,了解接下来需要实现什么。
<开始开发新功能>

智能体失败模式及解决方案对照表

问题

初始化智能体 (Initializer Agent) 的行为

编程智能体 (Coding Agent) 的行为

Claude 过早宣布整个项目完工。

建立功能列表文件: 基于输入的需求说明,建立一个结构化的 JSON 文件,包含所有端到端功能的描述。

会话开始时读取功能列表。选择单个功能开始工作。

Claude 留下的环境充满 Bug 或进度不明。

编写初始的 Git 仓库和进度说明文件。

会话开始时读取进度说明和 Git 提交日志,并在开发服务器上运行基础测试以捕捉未记录的 Bug。会话结束时提交 Git 代码并更新进度。

Claude 过早将功能标记为“完成”。

建立功能列表文件。

自我验证所有功能。只有在仔细测试后,才将功能标记为“通过”。

Claude 需要花时间弄清楚如何运行 App。

编写一个可以运行开发服务器的 init.sh 脚本。

会话开始时直接读取并运行 init.sh

总结:长跑型 AI 智能体常见的四种失败模式及解决方案。

未来展望

这项研究展示了一套针对“长跑型”智能体框架的解决方案,使模型能够跨越多个上下文窗口取得增量进展。然而,仍有一些悬而未决的问题。

最值得注意的是,目前尚不清楚是单个通用的编程智能体在跨上下文工作时表现最好,还是通过多智能体架构(Multi-agent Architecture)能实现更高的性能。按理说,引入专门的智能体——如测试智能体、质量保证(QA)智能体或代码清理智能体——在软件开发生命周期的各个子任务中可能会做得更出色。

此外,这个演示是针对全栈 Web 应用开发优化的。未来的方向是将这些发现推广到其他领域。这其中的部分或全部经验教训,很可能也适用于其他需要长时间运行的智能体任务,例如科学研究或金融建模。

致谢

本文由 Justin Young 撰写。特别感谢 David Hershey, Prithvi Rajasakeran, Jeremy Hadfield, Naia Bouscal, Michael Tingley, Jesse Mu, Jake Eaton, Marius Buleandara, Maggie Vo, Pedram Navid, Nadine Yasser 和 Alex Notov 的贡献。

这项工作反映了 Anthropic 多个团队的共同努力,是他们让 Claude 能够安全地进行长周期的自主软件工程,特别是 Code RL(代码强化学习)和 Claude Code 团队。有兴趣做出贡献的候选人欢迎在 anthropic.com/careers 申请。

脚注

1. 在本文语境中,我们将它们称为“不同的智能体”,仅仅是因为它们使用了不同的初始用户提示词(Prompt)。除此之外,系统提示词、工具集和整体的智能体框架是完全相同的。


来源: https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents