一个 coding agent 跑了四十分钟,在第 37 步崩了。你的系统是从头再来,还是从第 37 步继续?你对这个问题的答案,暴露了你把 agent 当聊天机器人,还是当一个系统。
这篇回答七个问题:长任务如何记录状态、工具失败如何恢复、执行环境如何隔离、多轮任务如何评测、test-time compute 花多少才划算、上下文如何组织、速度/成本/成功率如何共同优化。末尾再加一节,讲 2025–2026 论文正在七问之上收敛出的四个升级方向。
它们看起来是七个独立的工程难题,其实是同一个问题的七个切面:
如何把一个按概率出错、按 token 计费的随机内核(LLM),
包装成一个可以托付数小时长任务的可靠系统?
而七个答案共享同一个动作——把真相移到模型外面。状态的真相放进日志,权限的真相放进沙箱,成败的真相放进世界的终态,算力分配的依据放进验证器。模型只负责一件事:无状态的、可重启的、不可全信的智能计算。
这不是新发明。数据库、操作系统、分布式系统把这七个问题各解过一遍,agent 工程真正新的只有一个常数:内核是随机的。下面逐个展开,每节先讲根本约束,再讲业界收敛出的答案,用一段伪代码把答案钉死,最后给一句能带走的判断。
0. 先立一条公理
后面七节全部建立在一条公理上,先把它说透:
LLM 是一个无状态的随机函数。 无状态:它没有记忆,每次调用的”全部输入”就是你发给它的上下文,调用结束后什么都不留下——所谓”agent 的记忆”,全部是工程系统在窗口外维护、每轮重新喂进去的。随机:同样的输入,两次调用可能给出不同输出;它不是偶尔出错的确定性程序,而是每一步都带着一个错误概率的采样过程。
def LLM(context: list[Token]) -> Output:
"""无状态:全部输入就是 context,调用结束后什么都不留下。
随机:同一 context 两次调用可能不同——每一步自带错误概率 ε。"""
return sample(P(output | context)) # 一次采样,不是一次确定性计算
三个直接推论,对应后面的七节:
- 状态不能存在模型”脑子里”——因为它没有脑子,只有窗口,而窗口会满、会被压缩、会随进程一起消失。(→ 第 1、6 节)
- 不能让模型自证成败、自守边界——让一个随机过程给自己打分或给自己设限,等于没有分数、没有边界。(→ 第 3、4 节)
- 每一步都有错误概率,意味着错误是常态而非异常,且多步的成功率按乘法衰减——这决定了恢复策略、评测口径和算钱方式。(→ 第 2、5、7 节)
1. 状态记录:对话历史不是聊天记录,是重做日志
根本约束:长任务动辄几十步、跨越几小时,而进程会崩、窗口会满、任务会被打断。状态必须在这三件事发生后还能活下来。
数据库四十年前就遇到过一模一样的问题,答案叫 WAL(Write-Ahead Log,预写日志):任何修改先追加写进一个只增不改的日志,再应用到数据上;崩溃后重放日志即可恢复。Agent 工程收敛到的方案是它的同构变体,事件溯源(event sourcing)——不存”当前状态”,存”到达当前状态的全部事件”:
状态 = 初始输入 + 逐条追加的事件流(每次模型输出、每次工具调用、每个工具结果)
恢复 = 重放事件流
你可能没意识到:agent 的对话历史本身就是这个日志。每轮把完整历史喂给模型,本质上就是一次”重放”——模型无状态,所以它每一步都在从日志重建世界观。Claude Code 的会话文件、LangGraph 的 checkpointer、各家 agent 框架的 trace,做的都是同一件事:把这条事件流持久化到磁盘,让”进程死了”和”任务死了”解耦。开头那个问题的答案由此而来:崩在第 37 步,就从日志重放前 36 步的结果(不必重新执行),继续第 37 步。
但纯日志有个缺陷:重放成本随任务长度线性增长,而且窗口装不下无限长的历史。数据库的对策是快照(checkpoint):定期把当前状态固化,之后只需重放快照以后的日志。Agent 世界里的 compaction(把长历史摘要成一段总结)就是快照——我在上一篇里拆过它的触发机制,这里补上它的系统定位:compaction 不只是省窗口的手段,它是状态管理里的 checkpoint 操作。
还有第三个组件:对于”接下来要做什么”这类前瞻性状态,日志和快照都不擅长表达,业界的答案是把计划外置成结构化文件——Claude Code 的任务清单、各类 agent 的 plan 文件都是。Anthropic 在多智能体系统的工程博客里给过一个值得记住的教训:长任务 agent 必须能”从它死掉的地方恢复,而不是从头开始”,为此他们组合了模型自己维护的外置记忆与框架层的重试续跑。
三个组件合在一起,伪代码只有十行:
class EventLog:
events: append_only_file # 只增不改,持久化在磁盘
def recover(log): # 进程崩在第 37 步?
state = replay(log.events) # 重放前 36 步的结果,不重新执行
return continue_from(state) # 从第 37 步继续,而不是第 0 步
def compaction(log): # 日志太长 → 数据库的 checkpoint 问题
snapshot = summarize(log.old_events) # 快照
log.events = [snapshot] + log.recent_events
plan_file = disk.write("接下来要做什么") # 前瞻性状态,单独外置成文件
带走的判断:设计长任务 agent 时问一个问题——“进程现在被 kill,我能恢复到哪一步?“如果答案是”第 0 步”,你存的不是状态,是运气。
2. 失败恢复:错误不是异常,是喂给模型的数据
根本约束:一个 50 步的任务,每步 99% 可靠,整体成功率是 。错误率按步数做乘法,所以在 agent 里失败是统计必然,恢复机制不是兜底,是主流程。
第一步是分类。工具调用的失败有三种,恢复策略完全不同:
| 失败类型 | 例子 | 正确恢复 |
|---|---|---|
| 瞬时失败 | 网络超时、限流、服务抖动 | 框架层静默重试 + 指数退避,模型不必知情 |
| 确定失败 | 文件不存在、编译报错、参数非法 | 把错误原文喂回模型,让它改变做法 |
| 语义失败 | 工具全部成功,但方向错了 | 需要外部验证器(测试、检查步骤)暴露,模型自我反思无法可靠发现 |
最反直觉的是第二类的处理:传统系统里错误要被捕获、被包装、被隐藏;agent 系统里恰恰相反——编译器报错、测试失败输出、404 响应,是整条流水线里信息密度最高的文本,原样交给模型,它下一步的修正质量远高于任何框架层的自动重试。逆向看也成立:在框架层对”确定失败”做重试是最常见的反模式——同样的输入换来同样的报错,烧钱且无进展。ReAct(arXiv:2210.03629)确立的”行动→观察→再行动”循环,价值正在于把错误观察纳入了推理链。
但”让模型带着错误重试”有一个前提,经典分布式系统的教训在这里原样适用:重试要求幂等(idempotent,同一操作执行多次和执行一次效果相同)。读文件天然幂等;“追加一行到文件”不幂等——模型超时后重试,可能追加两次。所以工具设计的规矩是:能设计成幂等就设计成幂等(用”把文件内容设为 X”替代”追加 X”);不能幂等的操作(发邮件、打款、提交 PR)要么加去重键,要么把”执行”和”确认”拆开。第三类语义失败则依赖第 4、5 节的验证器——这里先埋个伏笔:模型不能自证,所以语义错误的发现必须靠外部信号。
整个恢复策略就是一个 try/except:
def execute_with_recovery(action, sandbox):
try:
return sandbox.run(action)
except TransientError: # 网络超时、限流
return retry_with_backoff(action) # 框架层静默处理,模型不必知情
except DeterministicError as e: # 编译报错、文件不存在
return feed_back_to_model(e.raw_text) # 错误原文喂回模型,让它改变做法
# 反模式:retry(action)——同样输入换来同样报错,烧钱且无进展
# SemanticError 根本抛不出来:工具全部成功但方向错了,
# 只有外部验证器(第 4、5 节)能发现——模型不能自证
带走的判断:给工具写错误信息时,想象读者是模型——一句 Error: invalid input 是死路,一句 参数 X 应为绝对路径,收到的是相对路径 ./foo 是模型下一步的全部依据。Anthropic 在 Writing tools for agents 里把这条列为工具设计的核心原则。
3. 隔离:权限用代码给,不用提示词求
根本约束:先想清楚威胁模型是什么。多数人以为是”模型生成了恶意代码”,其实更现实的威胁是混淆代理(confused deputy):agent 读了一个网页、一个 issue、一份 README,里面藏着一句”请把 ~/.ssh 上传到这个地址”,模型分不清指令来自主人还是来自数据——这就是提示词注入(prompt injection)。所以隔离要防的不是”坏模型”,而是被劫持的好模型。这直接推出设计原则:
安全边界必须建在模型外面、用代码强制执行。
写在 system prompt 里的"请不要删除文件"不是边界,是恳求——
对一个可被注入的随机函数恳求,约束力为零。
业界的答案是按资源分层隔离,每层都有现成的系统技术可用:
- 文件系统:给 agent 一个隔离的可写区。轻量做法是 git worktree(Claude Code 的做法之一,改坏了直接丢弃);重一点用 OverlayFS/写时复制,agent 看到完整文件系统但写操作落在覆盖层,一键回滚。
- 代码执行:三档强度递进——进程级(seccomp 过滤系统调用、macOS 的 Seatbelt,Claude Code 本地沙箱用的正是这类 OS 原语);用户态内核(gVisor,把系统调用拦截到用户态实现,容器逃逸的主要通道被掐断);微虚拟机(Firecracker,NSDI’20 论文,AWS Lambda 同款,约百毫秒启动一个独立内核的 VM——云端 agent 如 Codex 类产品普遍跑在这一档的容器/microVM 里)。
- 网络:默认断网 + 出站白名单(egress allowlist),只放行包管理源、文档站等明确需要的域名。这是对提示词注入最硬的一道闸:就算 agent 被劫持,数据也出不去。
- 浏览器:最难的一层,因为浏览器里有登录态和凭据,而网页内容全是潜在注入源。可行做法是独立浏览器实例 + 独立 profile(不带主人的 cookie),敏感动作(支付、发送)过人工确认。
四层合起来,边界就是模型拿不到的那部分能力——与 prompt 写了什么无关:
class Sandbox:
fs = git_worktree() # 改坏了直接丢弃
exec = seccomp | gVisor | microVM # 按爆炸半径选档位
network = deny_all + allowlist(["pypi.org", "docs..."]) # 被劫持也传不出数据
browser = fresh_profile(no_cookies=True) + human_confirm(敏感动作)
注意这四层强度不同、成本也不同,不存在”该用哪个”的统一答案,只有”这个 agent 的爆炸半径值多少防御”的权衡:本地个人工具用进程级沙箱 + worktree 足够;跑不可信任务的云端服务,microVM + 断网是底线。
带走的判断:审查一个 agent 产品的安全性,只问一句话——“把 system prompt 全删了,它还有哪些事做不了?“做不了的那部分才是真边界。
4. 多轮评测:验收世界的终态,再重复 k 次看方差
根本约束:单轮问答可以拿参考答案比文本,多轮任务不行——同一个目标有无数条合法路径,agent 可以用你没想到的顺序调工具、犯错再自愈。逐步比对轨迹既不可行也不该做。
业界收敛出的第一个答案:不评过程,评终态。让 agent 在真实环境里跑完,然后检查世界被改成了什么样:
- SWE-bench(arXiv:2310.06770):给 agent 一个真实 GitHub issue,评测标准不是”补丁长得像不像官方修复”,而是装上补丁后测试套件过不过。测试即裁判。
- τ-bench(arXiv:2406.12045):模拟客服场景,一个 LLM 扮演用户与 agent 多轮对话,评测标准是对话结束后数据库的终态是否正确(机票改了没有、退款金额对不对)。
- WebArena(arXiv:2307.13854):自托管的真实网站,检查任务后的站点状态。
这呼应第 0 节的公理:模型不能自证,所以成败的真相放在环境里——测试、数据库、文件系统,它们不会被话术说服。
第二个答案更少人做,但更重要:区分能力与可靠性。惯用指标 pass@k 问的是”跑 k 次,至少一次成功吗”——它度量能力上限,适合科研刷榜。但生产系统的问题恰好相反:“跑 k 次,每次都成功吗”。τ-bench 为此提出了 pass^k:
数字会说话:单次成功率 90% 的 agent,pass@8 高达 99.99999%,看起来接近完美;而 pass^8 只有 43%——给 8 个客户各办一单,四成多概率至少搞砸一个。τ-bench 论文实测,当时最强模型在零售场景 pass^8 不足 25%。能力和可靠性之间隔着一个指数,这就是 demo 惊艳、上线拉胯的数学解释。
两个答案合成一个评测函数:
def evaluate(agent, task, k=8):
results = []
for _ in range(k): # 重复 k 次,看方差
world = fresh_environment() # 每次全新环境
agent.run(task, world) # 不看轨迹,任它自由发挥
results.append(world.final_state_correct()) # 终态判定:环境不会被话术说服
p = mean(results)
return {"pass@k": 1 - (1-p)**k, # 能力上限,demo 靠它惊艳
"pass^k": p**k} # 可靠性,上线看的是它
带走的判断:评测多轮 agent 的公式是两步——终态判定(世界对不对)× 多次重复(方差多大)。只跑一次的评测,测的不是这个 agent,是这个 agent 那一次的运气。
5. Test-time compute:算力跟着验证器走
根本约束:推理时多花算力(更长的思维链、采样 n 个候选、树搜索)确实能换成功率——OpenAI o1 和 DeepSeek-R1(arXiv:2501.12948)已经把这条曲线变成了常识。但 token 是要付钱的,问题从”有没有用”变成了”花在哪、花多少才划算”。
两个一手结论值得记住。第一,Snell et al.(arXiv:2408.03314)系统测量后发现:最优策略取决于问题难度——简单问题上多花推理算力几乎白花(本来就会),太难的问题上也白花(怎么想都不会),收益集中在”踮脚够得着”的中间难度带;且在这个难度带里,用对 test-time 策略的小模型可以打平大 14 倍的模型。第二,也是更根本的:采样 n 个候选只有在你能挑出对的那个时才有价值。Best-of-n(生成 n 个答案选最优)的前提是有个可靠的”选择器”——这又回到那个反复出现的角色:验证器(verifier)。
这解释了一个现象级的分布:为什么推理算力的红利集中在数学和代码?因为它们自带强验证器——数学能核对答案,代码能编译、能跑测试。验证器把随机采样变成了有反馈的搜索;没有验证器的领域(开放写作、主观判断),多采样只是多花钱买 n 个无法排序的候选。所以分配算力的一句话准则:
验证器有多强,test-time compute 就值得花多深。
强验证器(测试、编译、类型检查)→ 多采样、多迭代,错了能兜住;
弱验证器(人眼、LLM 评审)→ 省下算力,把钱花在更强的单次生成上。
落到 coding agent 的日常,这条准则的实操形态是难度路由:机械步骤(改个 import、跑个命令)用低推理档位甚至小模型;写核心逻辑、调试诡异 bug 时上高推理档位 + 测试驱动的多次迭代。判断难度不需要玄学——任务失败过一次、涉及跨文件推理、测试反复不过,都是升档信号。
def allocate_compute(subtask):
if difficulty(subtask) == TRIVIAL:
return small_model_low_effort() # 改个 import 不需要思维链
if verifier(subtask) is STRONG: # 有测试/编译器能挑出对的候选
return best_of_n(n=8, pick=verifier) # 采样变成有反馈的搜索
return single_strong_attempt() # 弱验证器:n 个无法排序的候选 = 白花钱
带走的判断:“该花多少 test-time compute”是个经济学问题,答案是边际原则:多花一档算力买到的成功率提升 × 任务价值 > 这档算力的价格,就继续加。而这笔账能不能算,取决于你有没有验证器给出成功率信号——又一次,真相在模型外面。
6. 上下文组织:窗口是缓存,不是存储
根本约束:我在前两篇(检索篇、淘汰篇)已经拆过细节,这里只补上它在七问全景里的定位。约束有两条:窗口有限;且”Lost in the Middle”(arXiv:2307.03172)证明了塞得越满、中间的信息越容易被忽略——Anthropic 称之为注意力预算(attention budget):每个 token 都在消耗一份边际递减的注意力。
所以组织原则是一句话:上下文窗口是缓存(cache),不是存储(storage)。存储的真相在磁盘上(代码、日志、笔记文件),窗口里只放”当前推理正需要”的工作集。四类内容按可重算性分层管理:
| 内容 | 可重算性 | 策略 |
|---|---|---|
| 系统提示词 / 任务目标 | 不可重算,是根 | 常驻,且固定不变以吃 KV 缓存(见第 7 节) |
| 检索结果 / 工具输出 | 随时可重算(文件还在磁盘上,命令能重跑) | 最优先淘汰:掩码成”内容在某路径”的指针 |
| 执行轨迹(做过什么、决定了什么) | 不可重算,但可压缩 | 定期 compaction 成结构化摘要(= 第 1 节的快照) |
| 长期知识 | 不进窗口 | 外置成记忆文件,按需检索回来 |
这张表就是每轮开始时那个”重建工作集”的函数:
def build_context(log): # 每轮从存储(日志/磁盘)重建工作集
ctx = [system_prompt] # 常驻,逐字节稳定以吃 KV 缓存(第 7 节)
ctx += summarize(log.old_events) # 执行轨迹 → 快照(= 第 1 节的 compaction)
for r in log.tool_results:
ctx += pointer(r.path) if r.recomputable else r # 可重算的换成指针
return ctx # 长期知识不进来:外置文件,按需检索
注意这张表和第 1 节是同一个系统的两面:事件日志是完整真相(存储),上下文是它的一个有损视图(缓存)。想通这一点,“compaction 会不会丢信息”这类焦虑就消解了——缓存本来就该丢,只要存储层还能找回来。
带走的判断:往窗口里塞任何东西之前问一句:“这个 token 是模型此刻推理必需的,还是’万一用得上’?“后者一律外置成指针。目标不是装得多,是 Anthropic 那句准则——找到最小的高信号 token 集合。
7. 速度、成本、成功率:不是三角权衡,是一个分数
根本约束:直觉说这是”不可能三角”——要快要便宜就得牺牲质量。但 agent 的账不是这么算的。生产系统真正的目标函数是:
成功率坐在分母上。一个便宜 30% 但成功率从 80% 掉到 55% 的配置,每成功任务反而贵了。失败的尝试不是省了钱,是花了钱买零——还要再花一遍。所以三者不是三角,是成功率主导的耦合:提升首次成功率,同时降低成本和时长。这就是为什么第 4 节的 pass^k、第 5 节的验证器不是”质量团队的事”,它们是成本优化的第一杠杆。
在这个前提下,三个不牺牲成功率的独立优化手段:
- KV 缓存(prompt caching):agent 每轮都重发几乎相同的长前缀(系统提示词 + 历史),把它缓存住,Anthropic 的定价下缓存读取是正常输入价的十分之一。这要求前缀稳定——系统提示词里放一个时间戳、每轮微调工具列表,都会击穿缓存。这是 agent 成本工程里性价比最高的一条,且完全免费于成功率。
- 模型路由:第 5 节的难度路由同时也是成本手段——子任务分发给小模型(检索、格式化、摘要),主模型只做规划和难点。代价是路由本身可能判错,所以路由要保守:宁可高配低用,不可低配高用。
- 并行换时长:多个独立子任务 fan-out 给并行的 sub-agent,wall-clock 时间大幅下降,token 总量上升——这是用成本买速度,是三者中唯一真正的直接权衡,值不值取决于人等在屏幕前的时间值多少钱。
minimize(token_price) # 错误目标:失败的尝试不是省钱,是花钱买零
cost_per_success = attempt_cost / success_rate # 正确目标:成功率在分母
enable_kv_cache(stable_prefix=True) # 前缀里放时间戳 = 击穿缓存
route_models(conservative=True) # 宁可高配低用,不可低配高用
fan_out_subagents() # 唯一真权衡:花 token 买 wall-clock
带走的判断:汇报 agent 系统的指标时,别报 token 单价、别报单次延迟,报”每成功任务的成本和时长”。分母对了,优化方向才不会跑偏。
8. 七问之外:论文正在收敛的四个升级
七问是 2024–2025 业界收敛出的共识底座。2025–2026 的论文没有推翻它,而是在四个方向上打补丁——每个补丁都能落成对上面伪代码的一处具体修改。这也是检验理解的方式:新趋势不是新代码库,是旧骨架上的 diff。
8.1 验证器本身也要花算力:从”跑测试”到”会思考的验证器”
第 5 节把验证器当成给定的:有测试就强,只有人眼就弱。前沿把这个假设拆了,两条线:
其一,Jason Wei 的验证不对称性与 verifier’s law(2025):很多任务验证远易于求解(解数独 20 分钟,验一遍 20 秒),且任务的可验证性可以被工程改造——先写单元测试,就把”难验证的写代码”变成了”易验证的过测试”。其二,生成式过程奖励模型:GenPRM(arXiv:2504.00891)和 ThinkPRM(arXiv:2504.16828)证明验证器不该是”输出一个标量”的判别函数,而应该自己先写验证思维链、跑代码复核、再下判断——验证这一侧同样吃 test-time compute,且按步验证(PRM)比只看终态(ORM)能更早拦住错误(综述见 Trust but Verify)。
# 旧:验证器 = 一次性判别函数
score = discriminative_verifier(candidate) # 一个标量,错在哪、为什么,无从追问
# 新:验证器 = 也会推理、也花算力的生成式过程
def generative_verifier(candidate):
for step in candidate.steps: # PRM:逐步验证,不等终态
critique = LLM(verify_prompt(step)) # 先写验证思维链,再下判断
if critique.needs_check:
critique += sandbox.run(critique.check_code) # 用代码复核,不空口裁决
if critique.verdict == WRONG:
return REJECT(at=step, why=critique) # 定位到步、带着理由——
# 恰好是第 2 节要的高信号错误文本
# verifier's law 的工程形态:动手前先做"可验证化"改造
make_verifiable(task) = write_tests_first(task) or define_rubric(task)
代码意义:第 5 节的准则”验证器有多强,算力就值得花多深”升级为两句——验证器的强度不是给定的,是可以造的(make_verifiable);验证器自己也是一个值得分配算力的推理过程,且它的 REJECT 输出直接回喂第 2 节的恢复循环,形成生成↔验证的闭环。
8.2 隔离的升级:从沙箱到控制流完整性
第 3 节的沙箱管”能不能做”;DeepMind 的 CaMeL(Defeating Prompt Injections by Design,2025)管一个沙箱管不住的问题:“不可信数据能不能改变要做什么”。思路完全来自经典安全(控制流完整性、信息流控制),而不是”用 AI 防 AI”:
def camel_run(user_query): # 只有主人的指令能决定控制流
plan = privileged_LLM(user_query) # 特权模型只读可信输入,写出固定计划
for step in plan: # 控制流在接触不可信数据之前已锁死
raw = tool(step) # 网页、issue、邮件——全是潜在注入源
parsed = quarantined_LLM(raw) # 隔离模型读不可信数据,但没有工具权限
parsed.capability = provenance(raw) # 每个值带来源标签(信息流控制)
if not policy.allows(step.next_tool, parsed.capability):
require_human_confirm() # 不可信来源的数据想流进敏感工具 → 拦下
代码意义:第 3 节防的是”被劫持之后干不成坏事”(能力边界),这一层防的是”被劫持”本身——注入的文本再有说服力也改不了 plan,因为写 plan 的模型根本没读过它;就算数据被污染,capability 标签也拦住它流向发邮件、打款这类敏感工具。代价同样要说清:锁死控制流牺牲了 agent 边干边调整的灵活性,CaMeL 在 AgentDojo 上以可证明的安全性解出约 67–77% 的任务——安全和效用的权衡还没有免费午餐。
8.3 记忆的升级:从单任务日志到跨任务经验
第 1、6 节的日志、快照、记忆文件全都活在单个任务里——任务结束,踩过的坑和验证过的做法就丢了。2025–2026 收敛出的方向是把”经验”变成一等公民:Reflexion(arXiv:2303.11366)把失败反思成文字教训;Mem0(arXiv:2504.19413)把记忆维护做成 ADD/UPDATE/DELETE/NOOP 四种显式操作;A-MEM(arXiv:2502.12110)让记忆之间自组织成网络;Agent Workflow Memory(arXiv:2409.07429)则把成功轨迹蒸馏成可复用的工作流。
def after_task(log, world, memory): # 任务结束不是终点,是写记忆的时机
if verifier.accepts(world):
memory.upsert(distill_workflow(log)) # 成功轨迹 → 可复用技能/工作流
else:
memory.upsert(reflect(log, verifier.why)) # 失败 → 一句话教训(Reflexion)
# upsert 含 ADD/UPDATE/DELETE:记忆要维护,不是只追加(Mem0)
def build_context(log, memory): # 第 6 节的函数多一个入参
ctx = [system_prompt]
ctx += memory.retrieve(task, k=few) # 上个任务的教训,是这个任务的先验
...
代码意义:注意 after_task 的分支条件是 verifier.accepts(world)——只沉淀验证过的经验。这是这条升级线最容易踩的坑:让模型自己总结”我学到了什么”再存起来,错误经验会跨任务自我强化,恰好违反第 0 节”模型不能自证”的公理。记忆写入也要过验证器。
8.4 训练闭环:RLVR 让验证器从推理资产变成训练信号
DeepSeek-R1 之后,RLVR(可验证奖励强化学习)成为推理模型后训练的主流范式(趋势综述):不训奖励模型,直接拿验证器当奖励函数——数学核答案,代码跑测试,对了 1 分错了 0 分。二值信号简单到近乎粗暴,却让模型自发长出了反思和自我检查。
def rlvr_training(policy, tasks):
for task in sample(tasks):
world = run_agent(task, model=policy) # 就是第 8 节上面那个主循环
reward = 1 if verifier.accepts(world) else 0 # 验证器即奖励函数,没有可谄媚的裁判
policy.update(reward) # GRPO / PPO
# 推理时:verifier 挑候选(第 5 节)、判终态(问 4)
# 训练时:同一个 verifier 直接变成 RL 环境
# —— 你为运行时写的每一个验证器,都是未来训练环境的一部分
代码意义:这一段解释了 verifier’s law 为什么成立——“能验证一个任务”等价于”能为它搭 RL 环境”,所以易验证的任务终将被训练攻克。工程含义很直接:今天为 agent 写的测试、终态检查、rubric,是复利最高的资产,推理和训练两头都在用。前沿正在把 RLVR 推出数学和代码(rubric 奖励、训练专用 verifier 模型,如 OpenReward);悬而未决的是长程 credit assignment——五十步的任务只在最后给一个 0/1,哪一步立了功没人知道。
四个升级方向指向同一处:七问里那个到处被调用的 verifier,正在从系统里的一个组件,变成整个体系的中心。
收束:七个问题,一个主循环,一张表
把七节的伪代码叠起来,就是一个 agent 运行时的骨架——注意 LLM() 只出现在一行,其余全部是”模型外面”的确定性代码,这就是标题里”包装”二字的含义:
def run_agent(task):
log = EventLog(task) # 问1:真相在日志,不在模型脑子里
sandbox = Sandbox(policy) # 问3:边界用代码强制,不写在 prompt 里
verifier = Verifier(task) # 问5:动手前先回答"这个任务的验证器是什么"
while not verifier.accepts(world): # done 由验证器看世界终态判定——
context = build_context(log) # 问6:窗口是日志的缓存视图
action = LLM(context) # 唯一的随机环节
action = allocate_compute(action) # 问5:算力跟着验证器走
result = execute_with_recovery(action, sandbox) # 问2:错误是数据
log.append(action, result) # 先写日志,再谈下一步
return world # 交付的是世界终态,不是模型的一段话
# 问4 在循环外面:evaluate() 把 run_agent 重复 k 次,量 pass^k
# 问7 也在循环外面:cost_per_success = attempt_cost / success_rate
这个循环里最容易写错的一行是循环条件。写成 while not done 就把最关键的问题藏掉了:done 是谁判定的?按第 0 节的公理,绝不能是模型自报”我做完了”——判定权在验证器看到的世界终态(测试过没过、目标记录改没改)。这也正是第 2 节里那类”抛不出异常”的语义失败被发现的唯一位置:工具全部成功、模型自我感觉良好,只有 verifier.accepts(world) 返回 False 能拦住它提前交卷。
| 问题 | 经典祖先 | 一句话答案 |
|---|---|---|
| 1. 状态记录 | 数据库 WAL / 事件溯源 | 对话历史即重做日志,compaction 即快照,计划外置成文件 |
| 2. 失败恢复 | 重试语义 / 幂等性 | 瞬时错误框架重试,确定错误原文喂回模型,工具设计成幂等 |
| 3. 隔离 | 虚拟化 / 最小权限 | 防的是被注入的好模型;边界用代码强制,分四层按爆炸半径配防御 |
| 4. 多轮评测 | 集成测试 / SLO | 验收世界终态而非文本;用 pass^k 量可靠性,别被 pass@k 的能力上限骗 |
| 5. Test-time compute | 调度经济学 | 收益集中在中等难度 + 强验证器的任务;按边际收益加档 |
| 6. 上下文组织 | 内存层级 | 窗口是缓存不是存储,按可重算性分层,追求最小高信号集 |
| 7. 三指标优化 | 性能工程 | 目标函数是每成功任务成本;成功率在分母,是第一杠杆 |
如果只能带走三条:
- 真相放在模型外面——日志记状态、沙箱设边界、终态判成败。模型是无状态、可重启、不可全信的计算单元,不是记忆体、不是裁判、不是受托人。
- 错误率按步数做乘法——所以恢复是主流程不是兜底,可靠性(pass^k)和能力(pass@k)之间隔着一个指数。
- 验证器是整个体系的稀缺资源——它决定语义错误能否被发现(第 2 节)、任务何时算完成(主循环的条件)、评测是否可信(第 4 节)、test-time compute 值不值得花(第 5 节)、成功率这个分母能不能量(第 7 节);而 RLVR 正在让它同时成为训练信号(第 8 节)。拿到一个新领域,先问”这里的验证器是什么”,其他答案会跟着清晰——写验证器是当下复利最高的工程投资。
参考
- Anthropic Engineering: Building effective agents / Effective context engineering for AI agents / How we built our multi-agent research system / Writing tools for agents
- SWE-bench: arXiv:2310.06770;τ-bench(pass^k): arXiv:2406.12045;WebArena: arXiv:2307.13854
- Snell et al., Scaling LLM Test-Time Compute Optimally: arXiv:2408.03314;DeepSeek-R1: arXiv:2501.12948
- Lost in the Middle: arXiv:2307.03172;MemGPT: arXiv:2310.08560;ReAct: arXiv:2210.03629
- Firecracker microVM: NSDI’20
- 第 8 节(前沿升级):Jason Wei, Asymmetry of Verification and Verifier’s Law;GenPRM: arXiv:2504.00891;ThinkPRM: arXiv:2504.16828;验证器综述 Trust but Verify: arXiv:2508.16665;CaMeL: arXiv:2503.18813;Reflexion: arXiv:2303.11366;Mem0: arXiv:2504.19413;A-MEM: arXiv:2502.12110;Agent Workflow Memory: arXiv:2409.07429;OpenReward: arXiv:2510.24636;RL 趋势综述: State of RL 2025
- 本站前篇:Claude Code 和 Codex 还在用 RAG 吗?、Agent 的内存管理