长时间运行 Agent 的高效 Harness
长时间运行 Agent 的高效 Harness
随着 AI Agent 的能力越来越强,开发者越来越多地要求它们承担需要持续数小时、甚至数天才能完成的复杂任务。然而,如何让 Agent 在多个上下文窗口之间持续、稳定地推进工作,仍然是一个尚未完全解决的问题。
长时间运行 Agent 的核心挑战在于:它们必须以离散的会话形式工作,而每个新会话开始时,都不会记得此前发生过什么。可以想象一个由工程师轮班完成的软件项目,每一班新来的工程师都完全不记得上一班发生过什么。由于上下文窗口有限,而大多数复杂项目又无法在单个上下文窗口内完成,因此 Agent 需要一种方法,在不同编码会话之间建立连续性。
为了让 Claude Agent SDK 能够跨多个上下文窗口有效工作,我们设计了一套双阶段方案:首先由一个 初始化 Agent(initializer agent) 在第一次运行时设置环境;之后由一个 编码 Agent(coding agent) 在每次会话中逐步推进工作,并为下一次会话留下清晰的工作痕迹。配套的 Quickstart 中提供了代码示例。
长时间运行 Agent 的问题
Claude Agent SDK 是一个功能强大、通用的 Agent Harness,不仅擅长编码,也适用于其他需要模型使用工具来收集上下文、制定计划和执行任务的场景。
它具备上下文管理能力,例如 压缩(compaction),可以让 Agent 在处理任务时避免耗尽上下文窗口。
从理论上说,在这种机制下,一个 Agent 应该可以无限期地持续执行有价值的工作。
然而,仅有压缩还不够。
即使是像 Opus 4.5 这样的前沿编码模型,在 Claude Agent SDK 中跨多个上下文窗口循环运行,如果只收到一个高层级提示词,例如:
“构建一个 的克隆版。”
它仍然很难真正构建出生产级质量的 Web 应用。
Claude 的失败主要表现为两种模式。
第一,Agent 往往试图一次完成太多事情,也就是试图“一次性把整个应用写完”。
这经常导致模型在实现到一半时耗尽上下文,使下一个会话接手时面对的是一个只完成了一半、又没有文档说明的功能。
新的 Agent 随后只能猜测此前发生了什么,并花大量时间重新让基础应用恢复正常。
即使启用了压缩,这种情况仍然会发生,因为压缩后的信息并不总能把足够清晰的指令传递给下一个 Agent。
第二种失败模式往往发生在项目后期。
当已经完成了一部分功能后,后续某个 Agent 实例可能环顾项目现状,看到已经取得了一些进展,然后直接宣布任务完成。
于是,这个问题可以拆分为两个部分。
第一,我们需要建立一个初始环境,为用户提示词所要求的全部功能打好基础,让 Agent 能够一步一步、一个功能一个功能地推进。
第二,每个 Agent 都应该被要求朝最终目标进行增量式推进,同时在会话结束时,把环境留在一个干净状态(clean state)。
这里所谓的“干净状态”,指的是代码已经达到可以合并到主分支的程度:
不存在重大 Bug;
代码结构整洁;
有良好的文档说明;
下一位开发者可以直接开始开发新功能,而不需要先收拾一堆与自己任务无关的烂摊子。
在内部实验中,我们通过以下两部分方案解决了这些问题:
初始化 Agent(Initializer Agent)
第一个 Agent 会话会使用一个专门的提示词,要求模型初始化整个环境,包括:
创建 init sh 脚本;
创建 claude-progress.txt 文件,用于记录各个 Agent 已经完成的工作;
创建一个初始 Git Commit,明确显示最初加入了哪些文件。
编码 Agent(Coding Agent)
此后的每一个会话都会要求模型进行增量式开发,并在结束时留下结构化的进度更新。
这里最关键的洞察,是找到一种方式,让每次在全新上下文窗口中启动的 Agent 都能快速理解当前工作的状态。
我们通过 claude-progress.txt 与 Git 历史记录共同实现了这一点。
这些实践的灵感,很大程度上来自优秀的软件工程师每天本来就在做的事情。
环境管理
在更新后的 Claude 4 提示词指南 中,我们介绍了一些适用于多上下文窗口工作流的最佳实践,其中包括一种 Harness 结构:
“在第一个上下文窗口中使用不同的提示词。”
这里的“不同提示词”,指的是要求初始化 Agent 设置好完整环境,把未来的编码 Agent 高效工作所需的所有上下文都准备好。
下面,我们进一步拆解这种环境中的几个关键组成部分。
功能列表
为了解决 Agent 试图一次性完成整个应用,或者过早认为项目已经完成的问题,我们要求初始化 Agent 创建一个完整的功能需求文件,对用户最初的提示词进行细化和扩展。
以 克隆项目为例,这份文件最终包含了 200 多项功能,例如:
用户可以打开一个新聊天,输入问题,按下 Enter,然后看到 AI 的回复。
所有功能最开始都会被标记为“未通过”,这样后续编码 Agent 就拥有了一份明确的完整功能清单,知道“真正完成”究竟是什么样子。
{
“category”: “functional”,
“description”: “New chat button creates a fresh conversation”,
“steps”: [
“Navigate to main interface”,
“Click the ‘New Chat’ button”,
“Verify a new conversation is created”,
“Check that chat area shows welcome state”,
“Verify conversation appears in sidebar”
],
“passes”: false
}
我们要求编码 Agent 编辑这个文件时,只允许修改 passes 字段的状态。
同时还会使用措辞非常强硬的指令,例如:
“删除或修改测试是不可接受的,因为这可能导致功能缺失或存在 Bug。”
经过一些实验之后,我们最终选择使用 JSON 来保存功能列表。
相比 Markdown 文件,模型不太容易错误地修改或覆盖 JSON 文件中的其他内容。
增量式推进
有了上面的初始环境脚手架之后,下一版编码 Agent 会被要求一次只处理一个功能。
事实证明,这种增量式方法对于解决 Agent 一次想做太多事情的倾向至关重要。
不过,即使采用增量开发,模型在每次修改代码之后仍然必须把环境留在干净状态。
在我们的实验中,让模型形成这种行为最有效的方法,是要求它:
使用具有描述性的 Commit Message 把进度提交到 Git;
在进度文件中记录本次工作总结。
这样一来,模型就可以利用 Git 回滚错误修改,并恢复到代码库此前的可用状态。
这些方法也提高了整体效率,因为 Agent 不再需要猜测之前发生了什么,也不必每次都花大量时间重新让基础应用运行起来。
测试
我们观察到的最后一个主要失败模式,是 Claude 经常在没有充分测试的情况下把功能标记为完成。
如果没有明确提示,Claude 往往会:
修改代码;
运行单元测试;
使用 curl 调用开发服务器进行测试;
但它仍然可能没有意识到,这个功能实际上并不能端到端正常工作。
在构建 Web 应用的场景中,一旦我们明确要求 Claude 使用浏览器自动化工具,并且像真正的人类用户一样进行完整测试,它在端到端验证方面的表现就好得多。
Claude 通过 Puppeteer MCP Server 测试 克隆版时截取的屏幕截图。
为 Claude 提供这类测试工具显著提高了整体表现,因为 Agent 可以发现并修复那些单纯阅读代码很难发现的 Bug。
当然,目前仍然存在一些问题。
例如,Claude 的视觉能力和浏览器自动化工具本身都存在限制,因此它仍然很难发现所有类型的 Bug。
举例来说,Claude 无法通过 Puppeteer MCP 看到浏览器原生的 Alert 弹窗,因此依赖这些弹窗的功能往往更容易出现问题。
快速了解当前状态
有了以上机制之后,每一个编码 Agent 在新会话开始时都会被要求依次执行一系列步骤,让自己快速了解当前项目。
其中一些步骤看起来非常基础,但依然很有帮助:
运行 pwd,确认自己当前所在的目录。Agent 只能编辑这个目录中的文件。
阅读 Git Log 和进度文件,快速了解最近完成了哪些工作。
阅读功能列表文件,从尚未完成的功能中选择优先级最高的一项进行开发。
这种做法可以节省 Claude 每个会话中的 Token,因为它不必重新摸索应该如何测试代码。
我们还发现,让初始化 Agent 提前编写一个能够启动开发服务器的 init sh 脚本也很有效。
随后,每次开发新功能之前,都先运行一次基本的端到端测试。
以 克隆版为例,Agent 每次都会:
启动本地开发服务器;
使用 Puppeteer MCP 新建一个聊天;
发送一条消息;
验证是否能够收到回复。
这样一来,Claude 就能很快判断应用是否被上一个会话留在了损坏状态,并立即修复已有 Bug。
如果 Agent 在应用已经损坏的情况下直接开始实现新功能,通常只会让问题变得更严重。
完成这些设计后,一个典型会话会以类似下面的 Assistant 消息开始:
[Assistant] I’ll start by getting my bearings and understanding the current state of the project.
[Tool Use] <bash - pwd>
[Tool Use] <read - claude-progress.txt>
[Tool Use] <read - feature_list.json>
[Assistant] Let me check the git log to see recent work.
[Tool Use] <bash - git log --oneline -20>
[Assistant] Now let me check if there’s an init sh script to restart the servers.
<Starts the development server>[Assistant] Excellent! Now let me navigate to the application and verify that some fundamental features are still working.
<Tests basic functionality>[Assistant] Based on my verification testing, I can see that the fundamental functionality is working well. The core chat features, theme switching, conversation loading, and error handling are all functioning correctly. Now let me review the tests.json file more comprehensively to understand what needs to be implemented next.
<Starts work on a new feature>- 点赞
- 收藏
- 关注作者
评论(0)