长任务很少和某个聊天窗口同时开始、同时结束。任务可能等待 CI、review 或授权,也可能因为时间和环境重启被拆成几次执行。要让后一次真正接住前一次,系统必须把“任务”从“这次对话”里拿出来。

读完这篇,你应该能回答:Outer Loop 是什么、为什么需要、最小版本有哪些部分;任务中断、并发处理和外部动作状态不明时分别怎样处理。

资料说明:本文从 OpenAI 的 Harness engineering 与 Symphony,以及 Anthropic 的 long-running harness 和 Managed agents 中抽象跨运行机制。篇末社区教程只补充教学视角,不作为产品行为定义。

一、先从失败故事看懂 Outer Loop

1.1 一个没有 Outer Loop 的任务怎样失败

第一次运行里,Agent 找到支付测试的失败原因,改了代码,但还没来得及运行完整验证就结束了。第二次运行启动时只收到最初任务,不知道前一次改过什么,于是重新调查;它看到创建 PR 的请求超时,以为失败,又创建了第二个 PR。

模型并不一定更笨。真正缺失的是一套跨运行控制:任务做到哪一步?哪次运行现在负责它?什么结果才能结束?中断后从哪里继续?一个外部动作究竟成功还是失败?

1.2 先认识四个最小概念

  • 任务卡(work item):真正要完成的长期工作,例如“支付测试修复并通过 review”。它可以存在几小时或几天。
  • 一次运行(run):Agent 针对任务卡进行的一次有限尝试。它可能完成,也可能中断或等待。
  • Agent Loop:一次运行内部“想一步、做一步、看结果”的循环。
  • Outer Loop:包在多次运行外面,负责让同一张任务卡被触发、接手、验证、恢复和重试的控制流程。

一句话记住:Agent Loop 管一次运行怎样前进;Outer Loop 管任务怎样跨多次运行活下去,直到完成或交给人。

1.3 一张任务卡怎样从进入系统走到完成

  1. 创建任务:测试失败事件或人工请求生成任务卡,写清目标和完成条件。
  2. 选择任务:系统从等待列表中挑出当前可以处理、优先级合适的一项。
  3. 启动运行:把目标、可用工具、权限和上次进度交给 Agent。
  4. 持续保存:运行过程中记录确认事实、代码改动、测试结果和未完成事项,而不是等最后才保存。
  5. 独立验证:CI、review 或业务检查确认结果;Agent 自己说“完成”只是一条候选结论。
  6. 决定下一步:验证通过就结束;失败就带着新证据再运行;缺权限就等待;多次尝试无效就交给人。

这六步才是主干。后面的 queue、lease、checkpoint 和 idempotency,都是在任务变多、运行会中断或存在外部动作后,为这条主干补上的保护机制。

二、把任务卡变成可运行的控制流程

2.1 任务为什么要离开聊天,成为独立任务卡

聊天记录适合保留对话,却不是任务本身。任务卡最先需要的只有稳定编号、目标、完成条件、当前状态、已完成和下一步。任务规模扩大后,再逐步加入优先级、依赖、当前处理者、尝试次数和外部对象引用。Run 只是任务卡上的一次处理记录,不应取代任务卡。

Outer loop 包含 trigger、select work、launch run、verify、persist、retry or next,内部有 agent loop
Inner loop 消耗一个 work item;outer loop 决定何时创建、交给谁、是否接受结果以及下一步。图中的 Persist 是最终交接点,checkpoint 与 effect ledger 在运行中持续写入。
任务:修复支付模块回归
状态:处理中
完成条件:支付测试通过,review 接受
已经确认:缺少过期订单校验
已经修改:payment.go
下一步:运行支付测试
最小任务卡:先让下一次运行能看懂目标、进度和下一步。并发、重试和副作用字段会在出现相应问题时再加入。

这张任务卡的权威副本属于 Outer Loop 的持久化存储,不属于某次 Prompt 或聊天历史。启动 Run 时,系统只把当前目标、权限和进度投影给 Agent;Run 通过 checkpoint 提交新事实;verifier 通过后,Outer Loop 才把任务状态从“待验证”改为“完成”。后续 Run 和人类 reviewer 都从更新后的任务卡与 artifact 引用继续。

2.2 把刚才的六步变成可运行的责任

这六项不是只执行一次的直线流水线。保存进度从 run 启动前就开始,并贯穿执行与最终交接;否则进程若在验证前崩溃,系统恰好没有可恢复事实。后文会逐个解释并发领取、checkpoint 和外部动作记录,先用普通语言记住表里的责任即可。

阶段控制问题必须留下的事实
Trigger什么事件或人工请求创建任务?任务来源、是否重复、时间
Select work现在哪项任务可以开始?选择理由、当前处理者
Launch run本次 Agent 得到什么目标、工具和进度?运行配置、输入版本、上次进度
Verify哪些独立检查可以接受结果?检查证据、失败原因
Persist哪些进度和外部变化必须保存?恢复起点、动作记录
Retry / next结束、等待、换策略还是找人?决定理由、剩余尝试

2.3 多个执行者同时工作时,谁拥有任务

想象两名执行者同时从等待列表里拿到了同一张支付任务卡,两边都开始改代码。等待列表只能决定谁先看到任务,还需要一种“限时领取”机制:某名执行者在一段时间内取得处理权,持续证明自己还在工作;超过时间后,任务才能被别人重新领取。这种限时处理权叫 lease。

真实系统里的 Webhook 可能重复,定时任务可能重叠,worker 也可能崩溃。没有这套所有权规则,同一任务会被并发修改,或在执行者死亡后永久卡住。

Outer loop 控制面图,展示去重、队列、租约、运行、验证、重试预算和人工升级
Queue 管等待顺序,lease 管当前处理权,fencing token 拒绝旧处理者的迟到写入;重复外部动作会在 3.3 节单独处理。
  • Deduplication:相同外部事件只创建一个 work item。
  • Lease:同一时刻只有一个 run 拥有排他写权;只读调查可另设并行策略。
  • Heartbeat:区分长运行与已死亡 worker。
  • Fencing token:旧 lease 即使迟到,也不能覆盖新 owner 的状态。
  • Backpressure:根据资源、风险和人工审查容量限制启动。
queued
  -- worker-A claim, fence=1 --> running(A)
  -- lease expires -----------> claimable
  -- worker-B claim, fence=2 --> running(B)

complete(worker-A, fence=1)  # rejected: stale owner
complete(worker-B, fence=2)  # accepted
所有权实验:Queue 只决定谁先被看见;lease 让所有权可过期;fencing token 让旧 worker 的迟到写被确定性拒绝。

三、进阶:跨运行恢复、重试与副作用

3.1 中断以后,下一次运行怎样接上

Checkpoint 就是下一次运行可以恢复的已保存起点。回到支付故事:Run 1 在结束前写下目标、已确认原因、payment.go 的修改、尚未执行的测试和日志引用;Run 2 启动后先核对文件与引用是否仍然有效,再从“运行支付测试”继续,而不是重新调查。

Anthropic 的 long-running harness 经验强调,让后续 agent 能从明确进展文件、git 状态和干净的任务切片继续。这样的 handoff 同时服务机器与人:结构化字段保证恢复,叙述记录决策与异常;原始 artifact 保留证据。

跨运行交接图,Run N 通过 checkpoint、effect ledger 和 artifacts 把状态交给 Run N+1
下一次 run 不应该重新猜上次发生了什么,也不应该盲信一段没有证据指针的摘要。
Handoff 字段回答的问题恢复检查
Goal / done要完成什么?是否仍有效
Confirmed facts已经知道什么?来源是否可访问、新鲜
Effects已经改了什么外部状态?与真实世界对账
Attempts哪些路径失败、为什么?避免同策略重复
Open / next阻塞点和最小下一步?能力与权限是否足够
Artifacts日志、diff、测试和截图在哪里?引用是否完整

3.2 失败后不是简单地“再试一次”

Inner loop 的一次工具重试与 outer loop 的整次 run 重试不同。后者成本更高,也更容易重复副作用。每次重新启动前要问:失败类别是什么?新 run 会得到什么变化?旧效果是否已对账?还剩多少预算?如果答案只有“再试一次”,就不该自动重试。

重试预算应随 work item 持久化,不能在 worker 重启后归零。可以按错误类别设置不同策略:基础设施短暂错误自动退避;权限阻塞直接等待审批;同一 invariant 连续失败转人工;高风险外部写在状态不明时先 reconcile,不能重放。

3.3 请求超时,不等于外部动作没有发生

“创建 PR”“退款”“发通知”都可能出现请求成功但响应丢失。Outer loop 若只看本地超时,会再次执行。每个可重试副作用应携带稳定 idempotency key,并把 proposed、started、committed、observed 状态写入 effect ledger。恢复时先查询外部世界,再决定继续、补偿或完成。

effect_key = "issue-1842:create-pr:v3"
ledger: proposed → started → unknown
remote: PR #41 already exists

reconcile(effect_key)
  → find PR #41
  → ledger: committed → observed
  → do not create PR #42
超时前后:天真的流程是 timeout → retry → 两个 PR;安全流程是 timeout → reconcile → 复用已有 PR。因此副作用发生前就必须持久化,不是 verify 之后才记录。
  • 把“我调用过”与“世界已经改变”分开记录。
  • 优先使用外部 API 的幂等键;没有时用业务唯一键和对账查询。
  • 补偿动作也要被建模和审计,不能假设所有效果可回滚。
  • 未知状态不是失败重试,而是 reconciliation 状态。

3.4 什么时候必须把任务交给人

OpenAI Harness engineering 的核心约束是人类注意力稀缺。Outer loop 应把人放在高杠杆边界:目标冲突、高风险授权、无法自动判定的质量、预算继续投入。系统要给出压缩但充分的升级包:发生了什么、已验证什么、选项与影响、推荐动作、截止时间。

四、复盘:从最小版本逐步加保护

问题最低机制没有它会怎样
重复事件会创建重复工作吗?dedupe key并发重复副作用
谁拥有当前 work item?lease + fencing双写或永久卡住
Run 结束后怎样判断完成?独立 verifier把 final 当结果真值
下一次 run 从什么继续?checkpoint + artifacts丢状态、重做调查
重试会改变什么?error class + persistent budget无限烧钱
外部效果状态不明怎么办?idempotency + effect ledger重复退款、PR 或通知
什么时候找人?escalation policy无人负责或人被频繁打断

Outer loop 把 run 变成了可运营的工作系统。但它仍需要一个外部真值来判断“verify”是否可信。最后一篇进入 Evals 与反馈:怎样测结果、定位失败层,并把证据变成下一次系统改动。

官方资料

延伸阅读