我还没从 Thoughtworks 离职的时候(现在在 Qoder 团队),我就想写一篇文章,把一张图里 Loop 那一栏补完整。那张图其实很直白:不是让 AI 生成更多代码,而是让 AI 在工程体系中稳定完成交付。左边是 Rules,把团队经验和开发约束变成 AI 可执行的工作规则;中间是 Spec,把模糊需求收敛成可拆解、可验证、可追踪的交付目标;右边是 Harness,把 AI 行为纳入工程治理边界,确保结果可验证、可度量、可放行。
唯独 Loop 这一块,我一直没找到特别好的写法。它当然可以写成一个原则:把一次性生成,变成带反馈、自修正、可持续推进的执行闭环。可这个说法太干净了,也太容易写成一段正确废话。后来我看过 Ralph Loop,它给了一个很朴素的参照:任务表、进度文件、测试、提交、下一轮 fresh context。只是呢,我还是觉得少了一点真实工程里的手感。
引子 0:三个 /goal 的试验
直到这个月试了 Codex 的 /goal,睡觉前给一个任务,起床看结果。我拿 Office、IDA、Contra 这几个目标做了一些尝试,才觉得 Loop 这块终于有东西可以写了。效果不能说神奇,但至少它把一个问题压到了工程现实里:当任务大到一次对话装不下时,Agent 应该怎么继续工作。
所以这篇文章某种程度上也是一个有点绕的产物:先用 /goal 做了几组试验,再让 AI 帮我起草,最后我反过来修它。写到这里,文章本身也成了一个小 Loop。
这个月我拿三个任务试了一下 /goal。它们的共同点不是“AI 写了多少代码”,而是最后都留下了一个可继续接班的状态。
| 试验 | 原始目标 | 收敛边界 | 验证物 | 留下的状态 |
|---|---|---|---|---|
| Office | 写 Office 渲染能力 | tools/office-wasm-reader 读协议,packages/office 包 Node API,packages/office-render 做 React 预览 |
/debug/office-wasm-poc、reader mode、wasmProtoSha256、渲染测试 |
reader / renderer 边界清楚,stage-1 layering 仍需继续拆 |
| IDA | 用 Next.js 一比一复刻 IDA | 先对齐 RtlVirtualUnwind 的文本、函数、右侧视图,再暴露 UI pixel gap |
compare scripts、截图 diff、mismatchRatio: 0.10940370734908136 |
数据层已过,像素层未过 |
| Contra | 从零写 NES 模拟器并跑 Contra | 浏览器 gameplay 先收敛到 ContraGame + WASM ABI + React canvas |
contra_step_frame、contra_set_button、非空 canvas frame |
可玩运行时先成立,NES-derived 行为后续迁移 |
IDA 的截图对齐报告最能说明这种状态。RtlVirtualUnwind 的参数、14 行 normalized instructions、32 个可见函数名、右侧五个视图都能对上,但截图层仍然有红色差异区。它不是简单的“失败”,也不是可以放心宣布“完成”;它告诉下一轮 Agent:数据层已经接住了,像素层还没有。
这三件事单独看都不轻。放在一起之后,我开始更确定一件事:复杂任务交给 Agent 时,不能期待它“想一次就想明白”。任务得被做成一个可以接班、验证、修正的工作系统。
/goal 在这里的价值,也不只是“让 Codex 跑更久”。它会把一个任务挂在当前 thread 上,让 Codex 跨多轮继续工作,直到走到可验证的停止条件。OpenAI 的文档把它描述成实验性能力:适合长时间编码、迁移、重构、实验、游戏和 side project;关键是目标要清楚、验证循环要清楚、停止条件要清楚。1
换到工程语言里,这件事很朴素:你不能只告诉 Agent “继续做”。你得告诉它:先读什么,改到哪里,怎么证明,失败了怎么修,什么时候该停。
Chat Window 已经不够用了
过去两年,大家谈 Agent 时,脑子里最容易浮现的还是聊天窗口。你给它一个目标,它读文件、改代码、跑命令、解释结果。这个模式已经很有用,但它有一个很硬的上限:一次对话很难承载一个真正长任务的全部上下文。
Addy Osmani 在《Long-running Agents》里对这个问题的定义很准确:长时间运行的 AI Agent 可以在数小时、数天,甚至数周里持续推进,跨多个上下文窗口和沙盒恢复工作,并留下结构化产物。3 这个定义里最重要的词落在“恢复”和“结构化产物”上。
因为模型会忘,session 会断,沙盒会重建,命令会失败。一个普通 coding assistant 可以在一次坐下来时修好一个函数;一个 long-running agent 要面对的是另一类问题:它需要把前一次做过的事留给下一次,把失败原因留给下一轮,把完成标准留在模型上下文之外。
我现在更愿意把它叫作“接班能力”。模型本身可以短暂,工作区不能短暂。只要任务、计划、验证结果、失败记录和 Git 历史还在,下一轮 Agent 就不必从一团聊天记录里重新猜项目现在到哪了。
Ralph Loop 是这个思想最小的版本:读 PRD,选下一个未完成任务,调用 coding agent,跑测试,更新进度文件,提交,再进入下一轮。它看起来像一个土办法,却抓住了核心:Agent 本体不必永远在线,工作状态必须能被下一轮接住。4
Claude Code 的 /loop、/insights、hooks、skills,和 Codex 的 /goal、/plan、skills、worktree,其实都在围绕同一件事长出来:把一次聊天里的聪明,变成一个可以反复启动的工程循环。Claude Code 文档里,/loop 可以重复运行 prompt,/insights 可以分析 session、项目区域和摩擦点。5 Anthropic 的长任务 harness 文章也提到 initializer agent、progress 文件、feature list 和 git commit,用这些东西帮助下一轮 Agent 快速知道当前状态。6
回到代码里,它对应的就是那些很普通的交接物。
flowchart LR
Goal["Goal / Spec"] --> Plan["Plan / Milestones"]
Plan --> Work["Edit / Build"]
Work --> Verify["Tests / Diff / Browser / Report"]
Verify -->|fail| Repair["Repair"]
Repair --> Verify
Verify -->|pass| Memory["Progress / Docs / Git"]
Memory --> Next["Next Turn / Fresh Context"]
Next --> Plan
这张图里最容易被低估的是 Memory。很多人讨论 Agent 时盯着模型能力,真正让长任务能跑下去的,反而是那些很普通的东西:README、PLAN、progress、artifact、test report、git log。
/goal 的任务:收紧目标
把这几个实现当成 /goal 任务来拆,差异首先出现在验收方式上。
第一类是模仿 Codex 编写 Office 能力。Routa 里的 packages/office 现在是一个 Node.js wrapper,底层通过 .NET 9 browser-wasm 读取 DOCX、PPTX、XLSX,外部 API 暴露 extractDocxProto、extractPptxProto、extractXlsxProto、getReaderVersion 等函数。它的输出先停在 protobuf bytes 或 canvas payload。另一边的 packages/office-render 是 React runtime layer,负责把 payload 渲染成 WordPreview、SpreadsheetPreview、PresentationPreview。
“让 Agent 写一个 Office 预览器”这句话太大了,几乎没有执行边界。能落地的目标得先拆成这些层:
| 层 | 代码里的边界 | Agent 能验证什么 |
|---|---|---|
| 读取层 | @autodev/office、WASM reader、protobuf 输出 |
文件能被解析,API 返回稳定 bytes / payload |
| 公共 API | src/index.ts、canvas payload builder |
外部调用面稳定,不把渲染细节泄漏出去 |
| 渲染层 | @autodev/office-render |
Word / Spreadsheet / Presentation 分格式渲染 |
| 运行时层 | spreadsheet canvas worker、presentation slideshow、DOM shell | 浏览器交互、worker、fullscreen 等行为可独立测试 |
如果把这个目标交给一次普通聊天,我很容易得到一个“看起来能渲染”的大组件。长任务需要反着来:先把边界固定下来,再让 Agent 在边界内一点点补能力。比如 office-render 的 README 里已经把目标层次写出来:public boundary、shared substrate、format shell、format engine、runtime adapters。这个分层会直接变成下一轮 Agent 的交接语。
第二类任务是用 Next.js 一比一复刻 IDA 项目。ida-demo 里最有价值的部分,是它把“像 IDA”拆成了一组可验证目标。API 层用 lib/pe.ts 分析真实的 samples/kernel32.dll,/api/analyze 支持 GET 样例和 POST 上传;UI 层用 IdaWorkbench.tsx 组织函数列表、Hex View、Imports、Exports、Strings、SegmentMap 和 Output;验证层有一组脚本专门比较结果:
npm run test:detail:compare
npm run test:text:compare
npm run test:functions:compare
npm run test:right-views:compare
npm run test:functions:coverage
npm run test:ui:compare
README 里还有一个很诚实的状态:RtlVirtualUnwind 的参数、14 行 normalized instructions、32 个可见函数名、右侧五个视图、1410 个 objdump 命名函数、2435 个 graph-ready function views 都有文本或数据层面的对齐证据;但是截图级 UI 对齐仍然失败,当前 mismatch ratio 约 0.108。这个失败很重要。它告诉 Agent:你不能因为数据对齐了,就宣布“复刻完成”;也不能因为像素没完全对齐,就否定已经完成的数据层工作。
第三类任务是 Contra。这个目标一开始听起来更像一句玩笑:从零编写 NES 模拟器,然后跑 Contra。代码最后给出的答案更有意思。项目里确实保留了 src/cpu.rs、src/ppu.rs、src/apu.rs、src/nes.rs 这些 NES emulator 模块,也有 contra.nes、nes-contra-us 里的 Contra 反汇编资料和文档。但浏览器 gameplay 最后收敛到 src/contra_game.rs 里的纯 Rust game runtime:输入、玩家、敌人、子弹、碰撞、滚动 camera、HUD、framebuffer 渲染,再通过 src/lib.rs 暴露 contra_init、contra_step_frame、contra_set_button、framebuffer pointer 这些 WASM ABI,最后由 React canvas shell 接住。
这里有一个很重要的工程收敛:目标可以叫“Contra”,当前停止条件必须更具体。先让游戏在浏览器里可玩、可构建、可冒烟验证;NES emulator 和 Contra disassembly 作为参考和验证支架存在。下一轮再逐步迁移 stage data、entity routines、sprite/tile data、audio mixer。
Contra 这类任务尤其如此:名字越大,停止条件越要具体。
好的 Goal 长什么样
Codex 文档里建议 /goal 要定义 objective、stopping condition、必须先读的文件、验证命令、checkpoint 和短进度日志。1 这和 OpenAI long-horizon Codex 文章里提到的 durable project memory 是同一套思路:把 spec、plan、constraints、status 写进 Markdown,让 Agent 能反复回看,减少 drift,并保持稳定的 done 定义。2
我现在会让一个 /goal 压住五件事。
第一,目标要比一次普通输入大,但比一个 backlog 小。实现 Office 渲染 太大,修一个 CSS 太小。更好的写法是:完成 WordPreview 对 header/footer、分页、表格、编号和图片的渲染闭环,保持 packages/office 的公共 API 不变,并用现有 word-preview 测试验证。
第二,先读文件要明确。Agent 最耗时间的地方,经常不是写代码,而是冷启动乱翻。Office 任务应该先读 packages/office/README.md、packages/office-render/README.md、src/index.ts 和对应格式的 preview shell;IDA 任务应该先读 README、lib/pe.ts、app/api/analyze/route.ts、IdaWorkbench.tsx 和 compare scripts;Contra 任务应该先读 README、src/lib.rs、src/contra_game.rs,再决定要不要碰 NES emulator 模块。
第三,验证命令要比“跑测试”更具体。IDA demo 的好处是它把验证拆成了 detail、text、functions、right views、coverage、UI compare。这样 Agent 失败时能知道是哪一层不对。Office 也应该按 reader contract、payload contract、渲染测试、build gate 拆开。Contra 则至少要有 cargo test、WASM build、web build 和浏览器 smoke。
第四,要允许阶段性失败被记录下来。像 IDA 的 UI mismatch,就不应该被抹掉。它是一条有价值的进度线:文本和数据层已经对齐,像素层仍然有差距。这个差距如果不进 README 或 progress,下一轮 Agent 很可能重新证明一遍已经证明过的事情。
第五,停止条件要能被别人检查。看起来不错 不算停止条件。test:right-views:compare diffLines 为 0、cargo build --release --target wasm32-unknown-unknown --lib 通过、Web canvas 渲染非空帧、公开 API 没有破坏,这些才算。
我会把一个 /goal 写成这样:
/goal 完成 ida-demo 的 RtlVirtualUnwind 对齐,不要改 public API。
先读 README.md、lib/pe.ts、app/api/analyze/route.ts、app/components/IdaWorkbench.tsx、scripts/compare-*.mjs。
每个 checkpoint 只解决一个对齐面:detail/text/functions/right views/UI。
每次修改后运行对应 compare script,失败先修失败,不要扩大范围。
把已通过项、仍失败项和 mismatch 数字更新到 README。
停止条件:除明确记录的 UI pixel gap 外,文本/函数/右侧视图/coverage 全部通过。
这样的输入读起来更接近任务合同。Agent 能长时间运行,靠的就是这些有点笨的操作细节。
原始输入要先被校验
拆这几个目标时,我越来越觉得,“prompt 正在变成操作手册”这个说法还不够准确。真正发生变化的不是 prompt 这个文本本身,而是原始用户输入需要先被校验。
用户一开始给出的往往是一个方向:复刻 IDA,写 Office 渲染,从零做 Contra。这个输入可以很短,也可以很粗糙。问题在于,Agent 不能直接把它当成可执行任务。它要先过一层校验:这个目标能不能被拆开,能不能验证,能不能在失败之后继续交接。
我现在会用五个问题校验一个 /goal 输入。
1. 目标能不能落到一个可观察产物?
2. 非目标和公共边界有没有写清?
3. Agent 第一轮应该先读哪些文件?
4. 每个 checkpoint 用什么命令、截图或报告验收?
5. 失败和阶段性完成要写回哪里?
这五个问题比“写一个更好的 prompt”更重要。IDA 的原始输入如果只是“复刻 IDA”,Agent 很容易陷进界面细节;校验之后,它会变成:先让 RtlVirtualUnwind 的 detail/text/functions/right views 对齐,再记录 UI screenshot diff。Office 的输入如果只是“做 Office 渲染”,也会太大;校验之后,它会变成:packages/office 保持读取和 payload 边界,packages/office-render 承担 Word / Spreadsheet / Presentation 预览。Contra 也是一样,目标可以叫 Contra,但当前可执行边界是 ContraGame、WASM ABI 和非空 canvas frame。
所以 long-running agent 对人的要求其实更高。人仍然要懂代码,只是还要更懂什么样的输入可以被执行。一个模糊目标交给人,人可以在沟通里补;交给长时间运行 Agent,它会把模糊变成随机游走。很多失败不是模型不会写代码,而是原始输入没有通过校验:没有边界、没有证据、没有停止条件,也没有下一轮能接住的状态。
Office、IDA、Contra 三个任务里,最值得留下来的不是某个组件,而是三种不同的验收方式:
| 实验 | 表面目标 | 真正的 Agent 边界 |
|---|---|---|
| Office | 模仿 Codex 写 Office | 读取层、payload、渲染层、运行时层分开,公共 API 稳定 |
| IDA | Next.js 一比一复刻 IDA | 先对齐文本/数据,再暴露 UI pixel gap |
| Contra | 从零写 NES/Contra | 用 Rust game runtime 收敛可玩版本,NES/disassembly 作为迁移支架 |
它们共同说明了一件事:复杂任务靠一组越来越清楚的检查点往前推。
Git、报告和失败记录,是 Agent 的黑匣子
长时间运行 Agent 一定会失败。它会走错路,会误判完成,会为了修一个错误引入另一个错误。关键是失败之后有没有东西留下来。
Git 在这里承担了版本控制之外的黑匣子角色:什么时候改了什么,哪一轮引入了风险,哪一次验证通过,哪一次回滚,哪一个 commit 可以作为恢复点。Anthropic 的 harness 文章也强调,让 Agent 做增量工作、写 progress、提交 git,可以减少下一轮重新猜状态的成本。6
报告也是同一类东西。IDA demo 的 README 把通过项和失败项放在一起,读起来一点也不“漂亮”,但它很可用。它告诉下一轮 Agent:不要再证明 RtlVirtualUnwind 的 14 行指令已经对齐,那里过了;也不要假装 pixel parity 已经完成,那里还没过。
这就是 /insights 这类能力真正该去的地方。它不一定非得是某个工具里的固定命令。它更应该成为 long-running agent 的复盘层:每轮结束之后,提炼失败路径、验证证据、隐含规则和下一步,让项目记忆变厚一点。
如果一轮 Agent 做完之后,只留下“完成了”的一句话,那下一轮其实没有得到任何东西。它只得到了一次信任请求。
长时间运行需要人能插手
我不太相信完全放飞式的 Agent,至少在真实工程里不相信。
长时间运行不能变成 Agent 在仓库里随便跑几个小时。它要在一个有约束的 loop 里持续推进。这个 loop 里必须有计划,有验证,有权限边界,有日志,有 checkpoint,有人类可以中途插手的位置。
OpenAI 的 long-horizon Codex 文章把 Codex loop 写成:plan、edit code、run tools/tests/build/lint、observe、repair、update docs/status、repeat。2 这套循环有效,是因为每一步都能产生外部证据。模型的自信不重要,命令输出、diff、测试报告、浏览器截图、README 状态更重要。
所以我现在设计复杂任务时,会先问几个很土的问题:
- 这个目标能不能拆成 3 到 7 个 checkpoint?
- 每个 checkpoint 有没有一条验证命令或一个可检查 artifact?
- 下一轮 Agent 如果失忆了,能不能从 README、PLAN、progress、git log 里恢复?
- 哪些失败是允许存在但必须记录的?
- 哪些文件是公共边界,不能为了局部通过而破坏?
少一条,下一轮 Agent 就会多花一段时间重新找入口,或者更糟,重新证明一件已经证明过的事。
管理一支异步工程队
如果把 Ralph Loop、Claude Code、Codex 放在一起看,会发现它们的方向并不神秘。
Ralph Loop 先证明最小循环成立:任务表、进度文件、Git、fresh context。Claude Code 把 loop、hooks、skills、insights 放进开发者日常工具。Codex 的 /goal 则把一个 durable objective 挂到 thread 上,让 Agent 可以跨 turns 持续推进,并用 /goal pause、resume、clear 控制运行状态。
这三条路最后会碰到同一个管理问题:如何让多个异步执行者在同一套工程证据上协作。
围绕这几个目标,最后最重要的事反而不是盯着模型怎么写代码,而是写 spec,设边界,设计验证,拆 checkpoint,看报告,决定哪些状态要写回 README,哪些失败要留给下一轮。
这不轻松,甚至比写一个普通提问麻烦得多。只是呢,它更接近真实软件工程。
我现在对 Agent 复杂任务的判断可以压成一句话:不要让 Agent 记住一切,要让工作区接得住下一轮。
-
OpenAI Developers, Follow a goal;Slash commands in Codex CLI。 ↩↩
-
OpenAI Developers, Run long horizon tasks with Codex。 ↩↩
-
Addy Osmani, Long-running Agents。 ↩
-
GitHub, snarktank/ralph。 ↩
-
Anthropic Engineering, Effective harnesses for long-running agents。 ↩↩
或许您还需要下面的文章: