文章

【Anthropic】长期运行 Agent 的有效容器架构

跨越多个上下文窗口维持 Agent 连贯性的架构模式:初始化 Agent 建立基础,编码 Agent 增量推进,通过结构化进度追踪和端到端测试实现稳定的自主工作流。

【Anthropic】长期运行 Agent 的有效容器架构
  1. 问题:跨越上下文窗口的连贯性困境
  2. 架构方案:两阶段容器
  3. 关键组件与实践
    1. 功能清单
    2. 增量推进
    3. 测试验证
  4. 会话初始化流程
  5. Agent 失败模式与对策
  6. 未来方向
  7. 核心
  8. 评价

原文:Effective harnesses for long-running agents

问题:跨越上下文窗口的连贯性困境

AI Agent 变得越来越强,开发者期望它们承担耗时数小时甚至数天的复杂任务。但让 Agent 在多个上下文窗口(context window)之间保持一致进展,仍然是未解的问题。

核心挑战在于 Agent 只能以离散会话(session)形式工作,每个新会话都始于无记忆状态。想象一个软件项目由轮班工程师维护,每班新人都不知道前一班发生了什么。上下文窗口有限、大多数复杂项目无法在单窗口内完成,因此 Agent 需要一种方式来衔接编码会话之间的断层。

Claude Agent SDK 是强大的通用型 Agent 容器,擅长编码及其他需要模型用工具收集上下文、规划和执行的任务。它具备诸如压紧(compaction)之类的上下文管理能力,理论上应该能让 Agent 在任意长时间内持续做有用工作。

但压紧不够。即使用 Opus 4.5 这样前沿的编码模型在 Claude Agent SDK 中跨多个上下文窗口循环工作,如果只给高层级提示(如 "build a clone of claude.ai"),也无法构建出生产级 Web 应用。

Claude 的失败表现为两种模式。第一,Agent 倾向于一次做太多——本质上想一次性完成应用。这往往导致模型中途上下文耗尽,给下个会话留下一堆半实现且未记录的代码。Agent 只能猜测发生了什么,花大量时间让基本应用重新跑起来。即使有压紧也是如此,因为它并不能总是给下个 Agent 传递完全清晰的指令。

第二类失败模式往往在项目后期出现。某些功能已完成后,后续 Agent 实例环顾四周,看到有进展,就宣布任务完成。

这把问题分解为两部分。第一,需要建立初始环境,为给定提示要求的所有功能打下基础,让 Agent 能逐步逐特性工作。第二,要提示每个会话朝目标做增量进展,同时让环境在会话结束时保持干净状态。"干净状态"指的是适合合并到主分支的那种代码:没有主要 bug、代码整洁有序、有良好文档,开发者能轻松开始新特性工作而不必先清理不相关的混乱。

架构方案:两阶段容器

我们用一个两层方案让 Claude Agent SDK 有效地跨越多个上下文窗口:

  1. 初始化 Agent(initializer agent):首次运行时用专门提示让模型建立初始环境:环境配置脚本 init.sh、进度日志文件 claude-progress.txt、以及初始化 git commit 记录已添加的文件。
  2. 编码 Agent(coding agent):后续每个会话都提示模型做增量进展,并留下结构化更新。

这里的关键洞察是找到一种方式让 Agent 在新上下文窗口开始时快速理解工作状态,这通过 claude-progress.txt 文件和 git 历史实现。灵感来自观察有效软件工程师的日常实践。

关键组件与实践

功能清单

为解决 Agent "一次性做应用" 或过早认为项目完成的问题,我们提示初始化 Agent 写一个详尽的功能需求文件,扩展用户的初始提示。在 claude.ai 克隆例子里,这包含 200 多项特性,如「用户能打开新聊天、输入查询、按回车、看到 AI 回应」。所有特性初始都标记为「失败」,让后续编码 Agent 清晰看到完整功能的样子。

1
2
3
4
5
6
7
8
9
10
11
12
{
  "category": "functional",
  "description": "New chat button creates a fresh conversation",
  "steps": [
    "Navigate to main interface",
    "Click '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 字段状态,使用强烈措辞如「删除或编辑测试是不可接受的,因为这可能导致功能缺失或 buggy」。经过一些实验,我们选定用 JSON,因为相比 Markdown 文件,模型不太可能不当修改或覆盖。

增量推进

有了这个初始环境脚手架之后,后续编码 Agent 迭代就只被要求一次做一个特性。这种增量方法对解决 Agent 一次做太多的倾向至关重要。

一旦以增量方式工作,在代码变更后让模型保持环境干净仍然关键。在我们实验中,发现引发这一行为的最佳方式是要求模型用描述性提交信息把进展提交到 git,并且在进度文件里写进展摘要。这让模型能用 git 回滚坏的代码变更,并恢复代码库的工作状态。

这些方法也提高了效率,因为它们消除了 Agent 猜测发生了什么、花时间让基本应用重新跑起的需求。

测试验证

我们观察到的最后一个主要失败模式是 Claude 倾向在没有恰当测试的情况下就宣布功能完成。如果没有明确提示,Claude 趋向于做代码变更、甚至用单元测试或对开发服务器运行 curl 做测试,但无法认识到功能端到端(end-to-end)不工作。

在构建 Web 应用场景里,Claude 一旦被明确提示用浏览器自动化工具并像人类用户一样做所有测试,就能很好地端到端验证特性。

提供 Claude 这类测试工具极大提生了性能,因为 Agent 能识别和修复仅从代码看不出的 bug。

仍有一些问题待解,比如 Claude 的视觉和浏览器自动化工具局限让它难以识别每种 bug。例如,Claude 通过 Puppeteer MCP 看不到浏览器原生 alert 模态框(modal),依赖这些模态框的特性就 tend 更 buggy。

会话初始化流程

有了上述所有组件,每个编码 Agent 都被提示运行一系列步骤来定位方位,有些很基础但仍有帮助:

  1. 运行 pwd 看你在哪个目录。你只能编辑这个目录里的文件。
  2. 读 git 日志和进度文件以了解最近做了什么工作。
  3. 读功能清单文件,选择优先级最高且还没完成的特性去工作。

这节省了 Claude 每个会话的一些 token,因为它不需要搞清楚如何运行代码。它也有助于请初始化 Agent 写一个 init.sh 脚本,能运行开发服务器,然后在实现新特性之前运行基础端到端测试。

在 claude.ai 克隆例子里,这意味着 Agent 总是启动本地开发服务器、用 Puppeteer MCP 开始新聊天、发送消息、接收回应。这确保 Claude 能快速识别应用是否被留在破损状态、立即修复已有 bug。如果 Agent 反而开始实现新特性,很可能让问题更糟。

考虑所有这些后,典型会话以如下助手消息开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
[Assistant] 我会先定位方位并理解项目当前状态。
[Tool Use] <bash - pwd>
[Tool Use] <read - claude-progress.txt>
[Tool Use] <read - feature_list.json>
[Assistant] 让我查下 git log 看最近工作。
[Tool Use] <bash - git log --oneline -20>
[Assistant] 现在我看看是否有 init.sh 脚本来重启服务器。
[Tool Use] <read - init.sh>
[启动开发服务器>
[Assistant] 很好!现在让我导航到应用并验证一些基础特性仍正常工作。
[测试基础功能>
[Assistant] 基于我的验证测试,我看到基础功能工作得很好。核心聊天特性、主题切换、对话加载和错误处理都正常。让我更全面审查 tests.json 文件来理解接下来要实现什么。
[开始新特性工作>

Agent 失败模式与对策

问题初始化 Agent 行为编码 Agent 行为
Claude 太早宣布整个项目胜利设置功能清单文件:基于输入规范,建立带端到端功能描述列表的 JSON 文件。会话开始读功能清单文件。选择单个特性开始工作。
Claude 让环境处于有 bug 或进度未记录的状态写下初始 git 仓库和进度备注文件。会话开始读进度备注文件和 git commit 日志,对开发服务器运行基础测试捕获未记录 bug。会话结束写 git commit 和进度更新。
Claude 过早宣布功能完成设置功能清单文件。自验证所有功能。只仔细测试后才标记功能为"通过"。
Claude 得花时间搞懂怎么运行应用写下能运行开发服务器的 init.sh 脚本。会话开始读 init.sh

未来方向

这项研究展示了长期运行 Agent 容器中一种可能的解决方案,让模型能跨越多个上下文窗口做增量进展。但仍然有开放问题。

最明显的是,不清楚是单个通用型编码 Agent 在跨上下文情况下表现最好,还是多 Agent 架构能实现更好性能。合理的是,专用 Agent 如测试 Agent、质量保证(QA)Agent 或代码清理 Agent,能在软件开发生命周期的子任务上做得更好。

此外,这个演示已针对全栈 Web 应用开发优化。未来方向是将这些发现推广到其他领域。很可能部分或全部这些经验能应用于其他领域所需的长期代理任务,如科学研究或金融建模。


核心

这篇文章解决的本质问题是:长期自主工作需要的不是更聪明的 Agent,而是更清晰的「交接单」(handoff)

传统容器(harness)概念通常指人为设计一套测试、约束和验证机制;这里的创新在于把它用在多轮 Agent 之间——容器不是约束 Agent,而是保证 Agent 之间的协调和连贯。

三个关键洞察:

  1. 初始化和编码分离:避免初始化逻辑混入每个会话,减少重复工作和决策空间。
  2. 结构化进度追踪胜于上下文压缩:即使 context 很短,只要有清晰的进度点和特性列表,Agent 也能高效续接。
  3. E2E 测试是硬约束,不是软建议:Agent 容易过度乐观,浏览器自动化测试是对抗这种偏差的有效工具。

这个模式提示了更广泛原则:无论多麽强大的 LLM,面对长期任务时都需要良好的「工作簿」(workbook)设计。好的容器不是限制 Agent 能力,而是帮助它们在长时间跨度内保持理性和连贯。

评价

文中提出的两阶段架构(初始化 + 增量编码)在工程实践上很有价值。从传统软件工程中借鉴「交接」「进度追踪」「测试先行」等经验,应用到 LLM Agent 上是正确方向——自主执行系统本质上也是一种工程实践。

说得好的地方:

  • 对「干净状态」的定义清晰。git commit 作为事件记录、进度文件作为交接文档的组合方式实用。
  • 功能清单初始全失败是精妙设计——它既明确定义「完成」的标准,又防止 Agent 过早乐观。这点在思维链(Chain of Thought)和自我评估普遍过激的当前 LLM 上尤其重要。
  • 失败模式表格对齐明确,给读者诊断自家 Agent 应用提供了检查清单。

有问题或未说到的:

  • 测试部分承认了浏览器自动化的视觉局限(看不到原生 modal),但没有深入讨论解决方案。这是一个已知盲区:如果业务逻辑依赖视觉外观或动态 UI 元素,当前基于 DOM 遍历的 MCP 工具不够用。
  • 「功能清单 JSON 比 Markdown 不易被改写」这一观察有实验性,但它背后隐含的假设是 LLM 会更「保守地」改 JSON。这个假设有多大普适性?如果项目用 YAML 或 TOML 会更安全吗?文章没讨论。
  • 文章最终承认「多 Agent 专用角色」可能是未来方向,但没给出具体候选架构或原型。QA Agent 和编码 Agent 如何协同?测试失败后的回滚谁触发?这些是现实部署中必须设计的协作协议。

隐含假设或盲点:

  • 假设「端到端测试」对验证已经充分。浏览器自动化固然能抓 UI bug,但不保证业务逻辑正确(如数据一致性、并发问题、边界条件)。一个全功能 Web 应用可能还需要 API 层集成测试、数据迁移验证等非可观察层的测试。
  • 假设开发者「知道正确的需求」才能写详尽功能清单。但许多项目恰恰是需求迭代、边做边明确。初始化 Agent 写 200 项功能是基于一个「固定」的高层提示——这对需求可能漂移的探索性项目不现实。
  • 没讨论恢复成本。「增量推进」+「git commit」听起来干净,但单个特性必然与全局架构产生耦合。后期特性实现可能推翻早期设计(常见于复杂项目),这时 revert 单个 commit 不足以恢复可工作状态——需要系统性重构而不是功能级 revert。

总体而言,这篇文章为长期 Agent 跨上下文工作提供了扎实的基础框架,贡献了一个可复用的「容器 + 交接」模式。但要让它在真实生产环境中可靠,还需要补充多 Agent 协作协议、测试分层策略、以及需求迭代机制。后者恰恰是这篇文章没说的下一步研究点。

本文由作者按照 CC BY 4.0 进行授权