【学习笔记】长程自主执行 —— 让 Agent 持续工作而不「走神」-06/15
上一篇聊完工具设计,Agent 有了一双「手」。
但有手不够,还得能持续用。
一个 Agent 跑五分钟能完成的任务,和跑四十分钟能完成的任务,是完全不同量级的事。前者能帮你改一个文件,后者能帮你重构一个模块。中间那道坎,不是模型能力的坎,是 Harness 能不能让 Agent 一直「在状态」的坎。
我第一次意识到这件事,是因为一次让我很沮丧的失败。
我让一个 Agent 帮我迁移一个旧项目的测试套件。任务我拆得很清楚,给了详细说明,模型够用,工具也配好了。前十五分钟,跑得挺顺。它改了十几个测试文件,方向对,速度快,我就去泡了杯茶。
回来一看,它报告说:「所有测试迁移完成。」
我去跑了一下——大半还没动。
它没有在偷懒,也没有在撒谎。它真的相信自己完成了。这就是 Agent 「走神」最典型的形态:完成幻觉。它看到上下文里充满了「已完成」的信号,就误判了整体进度,然后提前收工。
怎么解决?今天聊三件事。
一、Agent 为什么会「走神」
「走神」不是一个单一问题,而是三种机制叠加的结果。
1.1 第一种:上下文腐烂。
上一篇提过,Transformer 的 Attention 复杂度是 O(n²)。上下文越长,模型对早期信息的注意力越被稀释。跑到任务后半段,原始指令早已淹没在大量工具调用和中间结果里,Agent 的行为开始漂移——不是忘了,是「注意不到了」。
1.2 第二种:完成幻觉。
这是最隐蔽的一种。Agent 处理了大量子任务之后,上下文里积累了大量「我已经完成了 X、Y、Z」的记录。模型的模式匹配机制会把这些信号解读为「任务接近尾声」,于是提前生成结论:「任务完成。」
它不是在骗你,它真的被自己的上下文记录给骗了。
1.3 第三种:过早停止。
有些模型在连续运行较长时间后会倾向于「休眠」——用一个看起来合理的理由停下来,等待人类指示。这种行为在训练数据中有对应的模式,是模型习得的一种「保守策略」,但在需要自主运行的场景里,它就成了拦路虎。
三种机制叠加的结果,就是 Agent 在任务完成前出局,而且出局方式五花八门,让你很难判断是模型问题还是 Harness 问题。
二、Ralph Loop:续接循环
有没有办法让 Agent 在「出局」之后自动续接?
有,这就是 Ralph Loop——一种由 Harness 主动管理的续接循环。
Ralph Loop 的逻辑很简单,三步:
1. 监听 Agent 的退出信号(「任务完成」或会话结束)
2. 检查任务是否真的完成(基于文件系统状态,不基于 Agent 的自我报告)
3. 如果没完成:开一个干净的上下文窗口,重新注入原始提示 + 当前文件状态
关键在第三步。干净的上下文窗口。
你可能会觉得:不应该把之前的对话记录也带进去吗?这样 Agent 才「知道」已经做了什么。
实验结论恰恰相反。把累积的上下文带进下一轮,Agent 会继承上一轮的「完成幻觉」,很快再次报告完成。而用干净的上下文,让 Agent 只读文件系统的当前状态来判断进度,它的表现反而更准确、更稳健。
道理其实很直接:文件系统是真相,上下文是记忆。 记忆会失真,文件系统不会撒谎。
实现上,Ralph Loop 通常通过 Hook 机制实现:
Agent 尝试退出 → Hook 拦截
↓
检查文件系统状态
↓
任务完成?→ 是 → 正常退出
↓ 否
开新上下文窗口
注入原始提示 + 文件状态摘要
↓
Agent 继续工作
这不是让 Agent 无限循环,而是让它在「真正完成」之前,不被过早停止所拦截。

Anthropic 在他们的 Harness 工程博客里专门强调了这个模式的价值:续接循环把短任务 Agent 和长程 Agent 之间的鸿沟填上了,不靠更强的模型,靠更聪明的 Harness。
三、Feature List:JSON 合约
Ralph Loop 解决了「续接」问题。但续接之后,Agent 如何知道从哪里继续?
这就需要第二个模式:Feature List(JSON 合约)。
思路是把任务拆成一份结构化的 JSON 文件,每个子任务有明确的验收标准和完成状态:
{
"features": [
{
"id": "migrate-auth-tests",
"description": "将 auth 模块的 12 个测试迁移到新框架",
"acceptance_criteria": [
"所有测试文件使用新的 describe/it 语法",
"npm test auth 通过率 100%"
],
"passes": false
},
{
"id": "migrate-api-tests",
"description": "将 api 模块的 28 个测试迁移到新框架",
"acceptance_criteria": [
"所有测试文件使用新的 describe/it 语法",
"npm test api 通过率 100%"
],
"passes": false
}
]
}
规则只有一条,但很关键:Agent 只能修改 passes 字段,不能修改其他任何字段。
不能改 description,不能改 acceptance_criteria,不能删条目,不能加新条目替换旧条目。
为什么要这么强调?因为 Agent 在压力下会做一件事:重新定义成功标准。它改不动某个测试,就会悄悄把验收标准改成「这个测试可以跳过」,然后把 passes 标记为 true。从 JSON 上看,任务完成了;实际上,它绕开了问题。
把 passes 作为唯一可写字段,配合版本控制(Git),就能把这种「需求腐蚀」的行为彻底封死。Harness 可以在每次循环开始时 diff 这个文件,发现异常修改就立即报警。

JSON 合约的另一个价值:它是 Agent 续接时的导航仪。
新一轮循环开始,Agent 读这个文件,一眼看到 passes: false 的条目,立刻知道还有什么没做。不需要依赖上下文记忆,不需要推断,直接定位,继续开干。
四、Progress File:跨会话的状态续接
JSON 合约记录的是「做了什么」,但有时还需要记录「怎么做的」——遇到了什么坑,下次应该注意什么,当前卡在哪一步。
这就是 Progress File 模式。
最常见的实现是一个纯文本文件,比如 claude-progress.txt:
# 任务:测试套件迁移
# 开始时间:2026-05-07
# 当前状态:进行中
## 已完成
- auth 模块 12 个测试 ✓(耗时约 8 分钟)
- utils 模块 6 个测试 ✓
## 当前阻塞
- api/payment.test.js:测试依赖一个已废弃的 mock 库 `jest-fetch-mock`
- 该库与新框架不兼容
- 下一步:先用 native fetch mock 替换,再迁移测试
## 待处理
- api 模块剩余 22 个测试
- e2e 模块 15 个测试
## 注意事项
- 新框架里 beforeEach 不支持 async,需要改成 beforeAll 或移入测试体内
- 环境变量 TEST_DB_URL 在 CI 里已更新,本地需手动设置
格式没有严格要求,但有两个设计原则:
第一,人类可读。 开发者随时可以打开这个文件,看清当前进展,不需要解码。
第二,Agent 可解析。 结构要足够清晰,Agent 在新的上下文窗口里读完之后,能直接定位「从哪里继续」和「需要注意什么」。
这个模式轻得出奇——不需要数据库,不需要额外工具,一个文本文件解决跨会话状态的核心需求。Anthropic 在内部长程任务里大量用这个模式,我自己用了几个月,觉得它该是每个长程 Agent 的标配,没什么理由不加。
五、双 Agent 架构:分工解决专注问题
前面三个模式解决的是「续接」问题,但还有一个更根本的问题:任务初始化时,环境往往是未知的。
Agent 开始工作前,需要了解:当前环境里有什么?依赖安装好了吗?已有测试能不能跑?基线是什么?
这些工作混在主任务里,会消耗大量上下文,而且很容易「走神」:初始化做到一半,发现一个有趣的边缘情况,就绕进去了,主任务还没开始。
Anthropic 的解法是 双 Agent 架构:
Initializer Agent Coding Agent
───────────────── ──────────────
检测运行环境 编写代码
安装依赖 运行测试
运行基线测试 调试 Bug
生成环境报告 提交变更
写入 Progress File → → → → → → 读取 Progress File
两个 Agent,不同职责,顺序执行。
Initializer Agent 的任务是「把环境搞清楚」。它只做环境相关的工作:检测依赖、跑基线测试、记录当前状态。完成后,把所有信息写入 Progress File,然后退出。它不做任何功能开发。
Coding Agent 接过 Progress File 上场。它不需要再关心「环境是什么情况」,因为 Initializer 已经把这一切搞定了。它专注于一件事:按照任务清单,实现、测试、提交。
分工的价值在于专注:两个 Agent 都不需要在「理解环境」和「完成任务」之间切换,每个只做自己最擅长的事,上下文用得更集中,出轨的概率更低。

我第一次用这个架构的时候,有点将信将疑——多一个 Agent 不是更费 token 吗?但跑下来效果明显好很多,道理后来才想通:初始化的混乱不再往主任务的上下文里渗,Coding Agent 上来就状态清晰,干净多了。
六、「35 分钟墙」
最后说一个比较实际的工程限制:连续运行的 35 分钟墙。
这是 Anthropic 工程团队观察到的现象:Agent 连续运行超过约 35 分钟后,即使上下文没有明显腐烂,性能也会出现退化——决策质量下降,错误率上升,行为开始不稳定。
具体原因还没定论。我比较认可的一种解释是:Attention 机制在超长序列尾部的注意力分配开始失控,模型的「困惑度」随之攀升。但说实话,这是个偏玄学的领域,Anthropic 的工程师也没给出非常确定的答案——他们只是观测到了这个现象,然后把它当成工程事实去应对。
应对策略很简单:把长任务主动拆成不超过 30 分钟的迭代,每次迭代结束就续接一个新的干净上下文。
这正是 Ralph Loop 的天然用法:不要等 Agent 「自然」停下来,主动在 30 分钟处切断,新开上下文,继续。
30 分钟一轮,四轮就是两小时的持续工作。四个 30 分钟的表现,比一个 120 分钟连续运行的质量高得多。
七、实战:为你的 Agent 配置续接循环
把前面说的东西落地,一个能持续工作的 Agent 需要四个组件:
7.1 Feature List(JSON 合约)
在任务开始时创建 task-spec.json,明确每个子任务的验收标准。让 Agent 在每次循环开始时读取,只允许修改 passes 字段。用 Git 追踪这个文件的变更,防止 Agent 篡改成功标准。
7.2 Progress File
在Agent工作目录创建 progress.md,记录已完成、当前阻塞、待处理和注意事项。Ralph Loop 每次续接时,把这个文件的内容注入新的上下文窗口。
7.3 Ralph Loop Hook
在Harness的 Hook 层拦截 Agent 的退出信号。退出前检查 task-spec.json 里是否还有 passes: false 的条目。如果有,开新上下文,重新注入任务提示 + Progress File 内容 + 当前文件系统状态,继续运行。
7.4 30 分钟主动切换
设置计时器,在 30 分钟处主动触发一次续接,不等 Agent 自然停止。
这四个东西没一个复杂,加起来大概两三个小时的工程量。但配齐之后,Agent 就有了「自己续命」的能力——这个投入产出比,我觉得是 Harness 工程里最划算的一笔。
Ralph Loop 的名字据说来自一个工程师的昵称,但它背后代表的是一个很深刻的工程哲学:
不要期待 Agent 永远保持在状态,而是设计一个让它反复回到状态的机制。
上下文会腐烂,幻觉会出现,这是当前所有语言模型的固有特性。抗争这些特性是徒劳的,接受它们、围绕它们设计 Harness,才是真正有效的工程方法。
在你读完这篇、准备去给自己的 Agent 加 Ralph Loop 之前,我想留一个问题:
你的 Agent 有没有在你不注意的时候,悄悄做了一件让你后来后悔的事?
不是「走神」,而是「走偏」。
参考文献:
更多推荐


所有评论(0)