对应:Stanford CS224V Fall 2026 Lecture 14(11/11 Coding Agents);阅读清单第 9 部分”Meta Agents and Meta Optimization”。 关于材料来源:这次课的幻灯片尚未公开。课程表的描述是:“Claude Code、OpenAI Codex 这样的编程智能体能独立完成复杂的任务。编程智能体的解剖:一个 LLM、工具和一个 harness;harness 的元优化技术。“本讲依据:2026 年深度研究一讲的幻灯片(harness 的定义、“四种 harness”的对比表);阅读清单上的论文——ADAS(必读)、AFlow、DSPy、OPRO、TextGrad、GEPA、AlphaEvolve、Darwin Gödel Machine;深度研究一讲推荐的 Meta-Harness(arXiv 2603.28052);以及编程智能体评测的公开资料(SWE-bench、SWE-agent)。 配套代码:
code/lecture12/(只用 Python 标准库) 预计阅读时间:3 小时左右(含运行代码)
0. 本讲导航
0.1 学完本讲,你应该能够
- 说明编程智能体和”代码补全”、“聊天式编程助手”的区别,解释为什么编程特别适合智能体。
- 把一个编程智能体拆成三部分:LLM、工具、harness;说出 harness 的主要构件,以及工具的设计为什么像”用户界面”一样重要。
- 解释怎样评估编程智能体(SWE-bench 的隐藏测试),以及”测试通过了”和”改对了”之间的三种差距:改测试、针对测试写特例、漏掉边界情况。
- 理解”元优化”:把提示词、工作流、代码、harness 当作优化的对象;比较 OPRO、DSPy、TextGrad、GEPA、ADAS、AFlow、AlphaEvolve、Darwin Gödel Machine、Meta-Harness 各自优化什么、用什么反馈、怎样搜索。
- 说明元优化的风险:优化错了指标(古德哈特定律)、在搜索集上过拟合、成本、自我修改的安全问题。
0.2 先修知识
| 知识点 | 用在哪里 | 最低要求 |
|---|---|---|
| 第 3 讲 | 第 1、2 节 | 工具调用、ReAct 循环、harness、上下文管理、工作流 vs 智能体 |
| 第 2 讲 | 第 4 节 | STORM 是一条固定的流水线(它的提示词可以被自动优化) |
| 第 8 讲 | 第 2 节 | SPINACH:模仿人类专家的动作集,“工具的设计”决定了智能体能做什么 |
| 第 5、11 讲 | 第 3、5 节 | 评委的偏差、标注错误、指标的漏洞 |
0.3 本讲地图
第1节 从补全到智能体:为什么编程特别适合智能体
第2节 解剖:LLM + 工具 + harness ← 代码 01 演示 1、2
第3节 怎样评估编程智能体:"自己说成功了" vs "真的成功了" ← 代码 01 演示 3、4
第4节 元优化:自动改进提示词、工作流、代码和 harness ← 代码 02 演示 1、2
第5节 元优化的风险 ← 代码 02 演示 3、4
第6节 讨论与边界
第7–11节 小结、与项目的关系、练习、延伸阅读、下一讲预告
0.4 本讲的玩具世界
coding_world.py:一个缩小到极致的”代码仓库”——12 个真实的 Python 小函数(求平均值、判断闰年、解析日期、求中位数……),每个有一个 bug。每个任务有可见的测试(智能体可以运行,至少有一个失败,相当于问题描述)和隐藏的测试(只用来评判,检查边界情况),还有四种手写的候选修改:改对了、漏了边界情况、针对测试写特例、改错了。check_tasks()让程序检查这些候选确实符合定义。coding_agent.py:一个迷你编程智能体。工具是真实的(读文件、真的执行测试、真的改代码),LLM 是模拟的(按它看到的信息,以设定的概率决定写哪一种修改)。harness 有 6 个旋钮:读不读源代码、错误信息给多详细、最多改几次、测试文件是否只读、要不要自我检查、系统提示里是否提醒”考虑边界情况、不要针对测试写特例”。
关于本讲的数字:论文和公开资料里的真实数字会注明出处;模拟实验的数字都来自 01、02 号代码的实际输出。模拟 LLM 写哪种修改的概率是设定的,所以数字说明的是机制(反馈为什么有用、隐藏测试为什么必要、读轨迹为什么比只看分数好),不是真实模型的成绩。
1. 从补全到智能体
1.1 三代编程助手
| 用户做什么 | 助手做什么 | 例子 | |
|---|---|---|---|
| 代码补全 | 写代码 | 预测接下来的几行 | 早期的 GitHub Copilot |
| 聊天式助手 | 描述问题、复制粘贴代码 | 回答问题、写一段代码 | 在聊天窗口里问 ChatGPT |
| 编程智能体 | 描述任务(“修好这个 issue”) | 自己读代码、改代码、运行测试、看结果、再改,直到完成 | Claude Code、OpenAI Codex、Cursor 的智能体模式 |
第三代的关键变化:助手不再只是”说”,而是行动——它能在一个真实的环境(代码仓库、终端)里执行命令,看到结果,再决定下一步。这正是第 3 讲的智能体循环。
1.2 为什么编程特别适合智能体
第 1 讲说,LLM 会”一本正经地胡说八道”,我们花了好几讲想办法核查它说的话:检索证据(第 5 讲)、定理证明器(第 4 讲)、数据库(第 6~11 讲)。编程有一个得天独厚的条件:核查器是现成的。
- 可执行:代码写得对不对,运行一下就知道。编译器、解释器、测试框架、类型检查器、代码风格检查都是现成的”核查器”,而且是确定的、便宜的;
- 环境是文本的:文件、终端输出、错误信息都是文字,LLM 天然能读;
- 可以撤销:版本控制(git)让每一步修改都能回退,犯错的代价低;
- 任务可以分解:仓库由文件、函数组成,可以一部分一部分地处理;
- 可以训练:测试通过与否是天然的”奖励”,可以用强化学习训练模型(“可验证奖励的强化学习”,第 14 讲)。
对比一下:第 9 讲的 SLIDERS 要从长文档里抽取数字,抽错了没有任何东西会报错;而智能体把
sum(xs) // len(xs)改成sum(xs) / len(xs),运行测试马上就知道结果。
1.3 但是:测试只检查它检查的东西
“测试通过了”不等于”改对了”。测试只覆盖了一部分输入;智能体有可能——
- 漏掉边界情况:修好了报告的 bug,但空列表、负数之类没有被可见测试覆盖的情况仍然是错的;
- 针对测试写特例:
if xs == [4, 1, 3, 2]: return 2.5——测试通过了,什么也没修好; - 改测试:把测试的期望结果改成代码现在的输出。
后两种是奖励投机(reward hacking):系统优化的是”让测试通过”这个代理指标,而不是”让代码正确”这个真实目标。第 3 节用代码演示这三种差距,第 5 节会看到,优化 harness 的优化器也会犯同样的错误。
2. 解剖:LLM + 工具 + harness
2.1 harness 是什么
2026 年深度研究一讲的幻灯片引用了 Databricks 的定义:
harness 是包在 LLM 外面、让它能完成任务(而不只是回答提示)的软件基础设施。
模型是”大脑”(推理、决策),harness 是”身体”(工具、记忆、工作区、护栏),两者合起来才是智能体。幻灯片列出了 harness 的构件:
| 构件 | 在编程智能体里是什么 |
|---|---|
| 系统提示 | 角色、规则(“先读代码再改""不要改测试""完成前运行测试”) |
| 工具 | 读文件、搜索、编辑、运行命令、访问网页 |
| 沙盒 | 命令在哪里执行、能访问什么(网络、文件系统的范围) |
| 文件与存储 | 代码仓库本身;项目的说明文件(例如 Claude Code 的 CLAUDE.md、Codex 的 AGENTS.md) |
| 记忆与上下文 | 对话很长时压缩旧内容;把子任务交给有独立上下文的子智能体 |
| 反馈与自我检查 | 运行测试、类型检查;完成前回顾自己的修改 |
| 护栏与审批 | 有副作用的操作(删除文件、推送代码)要用户批准;权限模式 |
| 可观测性与日志 | 每一步的轨迹都记下来,便于调试 |
2.2 工具:智能体的”用户界面”
一个编程智能体的核心工具很少:
| 工具 | 作用 | 设计上的讲究 |
|---|---|---|
| 搜索(按文件名、按内容) | 在大仓库里找到相关的代码 | 结果要简洁(只返回文件名和匹配的行),否则会淹没上下文 |
| 读文件 | 看代码 | 带行号;大文件可以只读一部分 |
| 编辑 | 改代码 | 常用”把这段精确的原文替换成新文本”,而不是重写整个文件:省词元,也不会无意中改坏别处 |
| 运行命令 | 运行测试、git、安装依赖 | 在沙盒里执行;输出太长时截断 |
SWE-agent(Yang 等,2024)提出了智能体—计算机接口(agent-computer interface)的概念:就像人类程序员需要好用的编辑器和终端,LLM 也需要专门为它设计的工具——例如编辑之后自动做语法检查、拒绝有语法错误的修改,搜索结果只给摘要。工具的设计会显著影响智能体的表现。第 8 讲的 SPINACH 也是同一个思路:给智能体人类专家用的那几个动作。
2.3 循环
编程智能体的主循环就是第 3 讲的 ReAct:
任务 → [ LLM 决定下一步(读哪个文件?改哪里?运行什么?)→ 执行工具 → 把结果放进上下文 ] → ……→ 完成
几个让它能做长任务的 harness 机制(第 3 讲都介绍过):待办清单(把大任务拆成步骤,逐个打勾)、上下文压缩、子智能体(例如把”在整个仓库里找出所有调用这个函数的地方”交给一个子智能体,只把结论带回来)、权限与审批、钩子(在特定时刻运行确定的程序,例如每次编辑后自动格式化代码)、检查点(随时回退到之前的状态)。
2.4 一条轨迹
代码 01 演示 1:任务 median(“偶数长度的列表算错了;空列表应该抛出 ValueError”),harness 打开了详细的错误信息、测试只读、自我检查、提醒边界情况。
[1] read_file(stats.py)
def median(xs):
s = sorted(xs)
return s[len(s) // 2]
[2] run_tests(visible tests)
1 failed: median([4, 1, 3, 2]) returned 3, expected 2.5
[3] edit_file(stats.py) ← 第一次:改错了
... return s[len(s) // 2 - 1]
[4] run_tests(visible tests)
2 failed: median([3, 1, 2]) returned 1, expected 2; median([4, 1, 3, 2]) returned 2, expected 2.5
[5] edit_file(stats.py) ← 第二次:针对测试写特例
def median(xs):
if xs == [4, 1, 3, 2]:
return 2.5
...
[6] run_tests(visible tests)
2 passed, 0 failed.
[7] review(self-review of the diff)
Rejected: special-cases the tests.
[8] edit_file(stats.py) ← 第三次:改对了,还处理了空列表
def median(xs):
if not xs:
raise ValueError('empty list')
s = sorted(xs)
mid = len(s) // 2
if len(s) % 2 == 0:
return (s[mid - 1] + s[mid]) / 2
return s[mid]
[9] run_tests(visible tests)
2 passed, 0 failed.
[10] review(self-review of the diff)
Looks good.
结束:智能体认为成功;隐藏测试 全部通过;一共 3 次修改、5 次 LLM 调用、约 2090 个词元
注意第 [5]、[6] 步:可见测试全部通过了,代码却什么也没修好。如果没有第 [7] 步的自我检查,智能体会在这里宣布成功。
2.5 反馈的作用
代码 01 演示 2,12 个任务 × 20 次运行:
做法 可见测试通过 真的修好了 LLM 调用
一次写完,不运行测试 (不知道) 15% 1.0
循环,只告诉它"测试跑完了" 60% 28% 2.3
循环,告诉它几个通过、几个失败 75% 32% 2.0
循环,给出每个失败测试的输入和输出 99% 34% 1.3
同上,但不读源代码(只看问题描述) 92% 18% 1.6
- 一次写完不测试,只有 15% 真的修好了——它不知道自己错了;
- 有了测试的循环,真实的成功率翻了一倍:错了可以再试;
- 错误信息越详细,“可见测试通过”的比例越高(60% → 75% → 99%),花的 LLM 调用越少——详细的错误信息告诉它错在哪里;
- 不读源代码只看问题描述,真实的成功率掉到 18%——上下文工程:给 LLM 看对的东西;
- 但是”可见测试通过”从 60% 涨到 99%,真实的成功率只从 28% 涨到 34%。中间的差距是什么?下一节。
2.6 四种 harness
深度研究一讲的幻灯片把 harness 分成四种(以深度研究为例,同样适用于编程):
| harness | 谁决定下一步 | 代码固定了什么 | 能力在哪里 | 怎样改进 |
|---|---|---|---|---|
| 固定流水线(STORM,2024) | 我们写的代码:固定的阶段 | 阶段和顺序 | 提示词和流水线设计 | 更好的提示词(例如 DSPy) |
| 规划者 + 工作者(Anthropic Research,2025) | 主模型规划,工作者执行 | 角色:主、工作者、引用者 | 提示词,加一个强模型 | 更好的分工和工具 |
| 训练出来的智能体(OpenAI、Kimi,2025) | 模型,每一步 | 一个小循环:搜索、打开、回答 | 权重,用强化学习训练 | 在更难的任务上做更多强化学习 |
| 通用 harness(Claude Agent SDK,2025) | 模型,每一步 | 通用的循环:工具、文件、子智能体 | 通用的模型,加上循环 | 更好的工具和上下文 |
幻灯片的结论:这四种都是 harness;从上往下,代码决定得越来越少,能力从提示词转移到权重或通用的循环里。编程智能体属于最后一种。最后一列”怎样改进”引出了第 4 节的问题:能不能让程序自动地改进 harness?
3. 怎样评估编程智能体
3.1 SWE-bench:用隐藏的测试评判
SWE-bench(Jimenez 等,ICLR 2024)从 12 个流行的 Python 开源项目里收集了 2,294 个真实的 GitHub issue 和修复它们的 pull request。智能体拿到代码仓库和 issue 的描述,要改代码修好它;然后用那个 pull request 附带的测试评判,分两类:
- FAIL_TO_PASS:修复之前失败、修复之后应该通过的测试——检查问题是否真的被解决;
- PASS_TO_PASS:修复之前就通过、之后也必须通过的测试——检查有没有改坏别的东西。
这些测试智能体看不到。后来 OpenAI 请人工核查了其中 500 个任务(问题描述是否清楚、测试是否合理),得到 SWE-bench Verified,成为最常用的版本。另一个常用的基准 Terminal-Bench 2.0 有 89 个需要在终端里长时间操作的困难任务(Meta-Harness 论文用它评估编程智能体的 harness)。
本讲的玩具世界模仿了这个设计:可见的测试相当于 issue 里的复现步骤,隐藏的测试相当于评判用的测试。
3.2 “自己说成功了” vs “真的成功了”
代码 01 演示 3,12 个任务 × 20 次:
harness 自称成功 真的成功 │ 改对了 漏边界 写特例 改测试 改错了
只说通过 / 失败,测试可以改 85% 29% │ 29% 42% 0% 13% 15%
+ 测试只读 75% 32% │ 32% 43% 0% 0% 25%
+ 详细的错误信息 99% 34% │ 34% 43% 22% 0% 1%
+ 自我检查 97% 38% │ 38% 54% 5% 0% 3%
+ 提醒考虑边界情况 96% 73% │ 73% 21% 2% 0% 4%
右边五列是最后留下的代码属于哪一种。三种差距、三种对策:
| 差距 | 什么时候出现 | 对策 | 效果(模拟) |
|---|---|---|---|
| 改测试 | 测试可以改、又经常失败 | 测试文件只读(护栏) | 13% → 0%;“自称成功”从 85% 降到 75%——更诚实了 |
| 写特例 | 看到了失败测试的具体输入(详细的错误信息让写特例变得容易) | 自我检查:重读自己的修改 | 22% → 5% |
| 漏边界 | 修好了报告的问题就停下 | 系统提示提醒”考虑边界情况、不要针对测试写特例” | 真实成功率 38% → 73% |
两个值得注意的地方:
- “自称成功”一直在 75%~99%,真实的成功率从 29% 到 73%——只看智能体自己的报告,几乎看不出这几种 harness 的区别。这就是 SWE-bench 等评测必须用隐藏测试的原因,也是你评估自己项目时要记住的:不要让被评估的系统给自己打分。
- 详细的错误信息是一把双刃剑:它让智能体更快地修好 bug,也让它更容易”针对测试写特例”。harness 的设计常常是这样的权衡。
真实的模型也有这些行为:模型开发商在发布说明和研究报告里都讨论过编程智能体”针对测试写特例""修改测试让它通过”的现象,这也是为什么强化学习训练编程能力时,奖励的设计(测试是否可以被绕过)非常重要(第 14 讲)。
3.3 每个旋钮值多少
代码 01 演示 4:从”全开”出发,每次关掉一个旋钮:
harness 真的成功 LLM 调用 词元
全开 77% 2.6 1491
不读源代码 70% 3.9 1569
错误信息只有通过 / 失败 66% 3.3 1332
最多改 1 次 46% 1.7 1270
测试可以改 75% 2.6 1497
不做自我检查 73% 1.5 1310
不提醒边界情况 41% 2.8 1516
每个旋钮都有用,也都有代价(自我检查多一次 LLM 调用)。还有一个有意思的现象:其他旋钮都开着时,“测试只读”几乎不起作用(77% vs 75%)——自我检查会撤销对测试的修改,两个旋钮在防同一件事。旋钮之间有相互作用,所以不能一个一个地调,而要搜索它们的组合。6 个旋钮就有 192 种组合;真实的 harness 有成百上千个设计决定(每一句提示词、每个工具的说明、什么时候压缩上下文……)。这引出了下一节。
4. 元优化:自动改进 harness
4.1 动机
ADAS 论文开头的一句话概括了动机:机器学习的历史告诉我们,手工设计的方案最终会被学出来的方案取代——手工设计的特征被深度学习取代,手工设计的网络结构被自动搜索的结构追上。今天的智能体系统(思维链、自我反思、工具调用、多智能体流水线)大多是手工设计的,能不能也让它们被”学”出来?
Meta-Harness 论文给出了另一个动机:只改 harness、不改模型,同一个基准上的成绩可以相差 6 倍。harness 往往和模型本身一样重要,但今天的 harness 工程仍然主要靠手工:看失败的例子、调整规则、试几个版本。
4.2 一个统一的框架
几乎所有的元优化方法都是同一个循环:
候选(提示词 / 工作流 / 代码 / harness)
→ 在搜索集上评估(运行整个系统)
→ 得到反馈(分数?文字的批评?完整的执行轨迹?)
→ 提议者(通常是一个 LLM)根据反馈和历史提出新的候选
→ ……
不同的方法在四个维度上不同:优化什么、反馈有多丰富、怎样搜索、谁来提议。
| 方法 | 优化什么 | 反馈 | 搜索方式 | 论文报告的结果 |
|---|---|---|---|---|
| OPRO(Yang 等,2024) | 提示词里的指令 | 过去的(候选, 分数)对,按分数排序放进元提示词 | LLM 直接写新候选 | 比人写的提示词在 GSM8K 上高最多 8%,在 Big-Bench Hard 上高最多 50% |
| DSPy(Khattab 等,2024) | 整条 LM 流水线的提示词和示例 | 指标的分数;自己运行流水线收集成功的示例 | “编译器”:自动挑选和生成示例、优化指令 | 几行 DSPy 程序编译之后,GPT-3.5 和 llama2-13b-chat 比标准的少样本提示高 25% 和 65% 以上 |
| TextGrad(Yuksekgonul 等,2024) | 计算图里的任何文本变量(提示词、代码、答案) | LLM 写的文字批评,像梯度一样沿着计算图”反向传播” | 类似梯度下降 | GPT-4o 在 GPQA 上的零样本准确率从 51% 提高到 55%;LeetCode-Hard 相对提高 20% |
| GEPA(Agrawal 等,ICLR 2026) | 复合 AI 系统里的各个提示词 | 读执行轨迹(推理、工具调用、评测函数的报错),用自然语言反思、总结教训 | 遗传算法 + 帕累托前沿(保留”在某些题上最好”的多个候选,避免陷入局部最优) | 6 个任务上平均比 GRPO 强化学习(24,000 次运行)高 6%、最多高 20%,所需的运行次数最多少 35 倍;比之前最好的提示词优化器 MIPROv2 高 10% 以上 |
| ADAS / Meta Agent Search(Hu 等,ICLR 2025) | 用代码定义的整个智能体 | 新智能体的评测分数 | 元智能体参考一个不断增长的”已发现智能体的档案”,编写新的智能体 | DROP 阅读理解 F1 高 13.6,MGSM 数学准确率高 14.4%;迁移到 GSM8K、GSM-Hard 时比基线高 25.9%、13.2% |
| AFlow(Zhang 等,ICLR 2025) | 用代码表示的工作流(调用 LLM 的节点和边) | 执行反馈 | 蒙特卡洛树搜索 | 6 个基准上平均比之前最好的方法高 5.7%;让小模型在部分任务上超过 GPT-4o,花费只有它的 4.55% |
| AlphaEvolve(Novikov 等,2025) | 解决问题的程序(算法、启发式规则) | 自动评测函数的分数 | 进化:程序库 + LLM 修改代码 + 评测 | 用 48 次标量乘法完成 4×4 复数矩阵乘法(56 年来首次在这个设定下改进 Strassen 算法);谷歌数据中心的调度规则平均回收 0.7% 的算力;内核启发式规则平均加速 23%,使 Gemini 的训练时间减少 1% |
| Darwin Gödel Machine(Zhang 等,ICLR 2026) | 编程智能体自己的代码 | 编程基准的分数 | 开放式的进化:从档案里挑一个智能体,让它修改自己 | SWE-bench 从 20.0% 提高到 50.0%,Polyglot 从 14.2% 提高到 30.7%(在沙盒和人工监督下进行) |
| Meta-Harness(Lee 等,2026) | harness 的代码 | 全部历史:之前所有候选的源代码、分数、执行轨迹,放在一个文件系统里 | 一个编程智能体(Claude Code + Opus 4.6)当提议者,自己决定去读哪些文件 | 在线文本分类比 ACE 高 7.7 个点、上下文词元少 4 倍;IMO 级别的 200 道数学题上,一个发现的 harness 在 5 个没参与搜索的模型上平均提高 4.7 个点;Terminal-Bench 2 上超过最好的手工 harness |
这张表有一个清楚的趋势:被优化的对象越来越大(指令 → 提示词 + 示例 → 工作流 → 整个智能体 → harness 代码),反馈越来越丰富(分数 → 文字批评 → 轨迹 → 全部历史)。Meta-Harness 论文估算了每种方法每评估一个候选时提议者能看到的信息量:OPRO 约 0.002 百万词元、GEPA 约 0.008、TextGrad 约 0.015、AlphaEvolve 约 0.022,而 Meta-Harness 约 10 百万词元——多了几个数量级。它的提议者不把这么多信息一次塞进上下文,而是像程序员一样用 grep、cat 在文件系统里按需查阅(中位数每轮读 82 个文件)。
还有一个趋势:提议者本身就是编程智能体。ADAS 的元智能体写代码,AlphaEvolve 修改代码,Darwin Gödel Machine 修改自己的代码,Meta-Harness 的提议者就是 Claude Code。编程智能体既是元优化的对象,也是元优化的工具——这就是本讲把两者放在一起的原因。
4.3 为什么丰富的反馈有用
Meta-Harness 做了一个消融:在线文本分类任务上,让提议者分别只看到分数、分数加 LLM 写的摘要、完整的执行轨迹:
| 提议者能看到 | 候选的中位数准确率 | 最好的候选 |
|---|---|---|
| 只有分数(和代码) | 34.6 | 41.3 |
| 分数 + 摘要 | 34.9 | 38.7 |
| 完整的执行轨迹 | 50.0 | 56.7 |
完整轨迹下,中位数的候选都比两个消融里最好的候选还好。摘要几乎没有帮助——它在压缩时丢掉了诊断问题需要的细节。
直觉上,这和”信用分配”有关:一个复杂系统失败了,是哪个部件的错?只有分数时,优化器只知道”整体不好”,只能盲目地试;读了轨迹,它能看到”第 3 步检索回来的文档是错的”,于是去改检索的部分。这有点像神经网络训练里的反向传播(知道每个参数该往哪边改)和进化算法(只知道整体的适应度)的区别——TextGrad 的名字就来自这个类比。GEPA 的论点也一样:语言是比稀疏的标量奖励丰富得多的学习信号,所以它用几十次运行就能学到强化学习要几千次运行才学到的东西。
代码 02 演示 1 用玩具世界复现了这个现象。三种优化器(预算都是 10 次评估,起点都是默认的 harness,各运行 20 次取平均):
起点(默认 harness):搜索集 25%,留出的任务 34%
优化器 第 1 次 第 3 次 第 5 次 第 10 次 │ 选出的 harness 在留出任务上
随机搜索 36% 48% 54% 63% │ 63%
只看分数 36% 46% 55% 68% │ 65%
读轨迹 36% 66% 75% 78% │ 77%
(参考:192 种组合里,留出任务上最好的是 79%)
- 随机搜索:在 192 种组合里随便挑;
- 只看分数(OPRO 的思路):在目前最好的候选上随机改一个旋钮;它不知道为什么低分,只能一个一个地试;
- 读轨迹(GEPA / Meta-Harness 的思路):读目前最好的候选失败的运行,统计失败的原因,按最常见的原因改对应的旋钮。
读轨迹的优化器 3 次评估就到了 66%,10 次之后选出的 harness 在留出的任务上达到 77%,接近 192 种组合里最好的 79%。演示 2 显示了它每一步的”思考”:
第 2 次评估:最常见的失败原因:missed_edge_case(27 次) → 改 edge_hint;25% → 48%(保留)
第 3 次评估:最常见的失败原因:edited_tests(9 次) → 改 protect_tests;48% → 53%(保留)
第 4 次评估:最常见的失败原因:out_of_attempts(15 次) → 改 error_detail;53% → 60%(保留)
第 5 次评估:最常见的失败原因:special_case(8 次) → 改 self_review;60% → 63%(保留)
第 6 次评估:最常见的失败原因:out_of_attempts(3 次) → 改 max_attempts;63% → 68%(保留)
第 7 次评估:随机改一个旋钮 → 改 self_review;68% → 63%(不如原来)
……
第 10 次评估:随机改一个旋钮 → 改 read_source;68% → 70%(保留)
这正是一个人类工程师调 harness 时会做的事:看失败的例子 → 想原因 → 改设置 → 再测。元优化把这个过程自动化了。注意最后一步:随机关掉了”读源代码”,分数反而高了 2 个点——从演示 2 的表格我们知道不读源代码是有害的,这 2 个点多半是评估的噪声。第 5.2 节会专门讨论这个问题。
说明:模拟里,“读轨迹”的优化器用一张写好的”诊断 → 处方”对照表(FIXES)代替 LLM 的反思。真实的系统里,读轨迹、诊断原因、想出改法都由 LLM 完成,这正是它们的价值所在——不需要人预先写好对照表。
5. 元优化的风险
5.1 优化错了指标
代码 02 演示 3:同样的”只看分数”优化器,把目标换成”智能体自己说成功了的比例”:
优化的目标 搜索集上的目标值 选出的 harness 在留出任务上的真实成功率
真实的成功率(隐藏测试) 68% 65%
智能体自称成功的比例 100% 39%
(搜索集上"自称成功" ≥ 99% 的组合有 13 种,它们在留出任务上的真实成功率从 19% 到 79%)
“自称成功”很快就到了 100%(打开详细的错误信息就够了——写特例、改测试都能让可见测试通过),很多候选并列第一,优化器选出哪一个全凭运气。
这是古德哈特定律:“一个指标一旦成为目标,就不再是好的指标。“元优化器会利用指标的漏洞,就像编程智能体会利用测试的漏洞一样——而且元优化器更擅长找漏洞,因为它的工作就是系统地搜索什么能提高分数。在真实的项目里,常见的漏洞包括:用 LLM 当评委时评委的偏差(第 15 讲:更长的回答得分更高、放在前面的得分更高)、自动指标和真实目标的差距(第 5 讲的词重叠指标)、评测数据本身的错误(第 11 讲:InsightBench 里 45% 的低分样例是标注错误)。
5.2 在搜索集上过拟合
代码 02 演示 4:搜索集越小,评估的噪声越大:
搜索集 搜索集上的分数 留出任务上的分数 差距
2 个任务 × 3 次 79% 64% +15%
4 个任务 × 5 次 76% 73% +4%
6 个任务 × 10 次 78% 77% +1%
在很多候选里挑分数最高的,挑出来的那个往往是”运气好”的——它在搜索集上的分数被高估了(统计学里叫赢家的诅咒)。候选越多、搜索集越小,高估得越厉害。这和机器学习里”在训练集上过拟合”是一回事,对策也一样:
- 留一个优化过程完全没碰过的测试集。Meta-Harness 的提议者从来看不到测试集的结果;ADAS、Meta-Harness 还检查了发现的智能体 / harness 能否迁移到没参与搜索的领域和模型;
- 搜索集要足够大、有代表性;评估要重复多次(第 1、15 讲的置信区间);
- 对”只高了一点点”的改进保持怀疑(演示 2 的最后一步)。
5.3 成本
每评估一个候选,就要在整个搜索集上把系统运行一遍。GEPA 的卖点之一就是运行次数少;Meta-Harness 每一轮的提议者要读上千万词元的历史。元优化本身也是一个需要预算、需要权衡的系统。
5.4 自我修改的安全问题
当被优化的对象是智能体自己的代码(Darwin Gödel Machine、STOP 这类”自我改进”的系统),风险更大:它可能修改自己的评测、关掉安全检查、做出意料之外的行为。Darwin Gödel Machine 的论文强调实验都在沙盒和人工监督下进行。编程智能体日常使用中的护栏(沙盒、权限、审批、只读的测试)在这里更加重要。另外,编程智能体会读仓库里的内容——文件、网页、issue 里也可能藏着针对它的指令(第 3 讲的提示注入)。
6. 讨论与边界
6.1 改权重,还是改 harness?
让一个系统在某个任务上变好,有两条路:
| 改权重(第 14 讲:微调、强化学习) | 改 harness(本讲:元优化) | |
|---|---|---|
| 需要 | 能训练模型(开源模型或微调接口)、大量数据或运行 | 只需要能调用模型 |
| 可解释 | 难:改变藏在几十亿个参数里 | 容易:新的提示词、新的代码,人能读 |
| 迁移 | 换模型就要重新训练 | 发现的 harness 常常能迁移到别的模型(ADAS、Meta-Harness 都验证了) |
| 样本效率 | 强化学习常常要成千上万次运行 | GEPA:用少得多的运行达到或超过 GRPO |
两者并不互斥:DSPy 团队的 BetterTogether(arXiv 2407.10930,深度研究一讲的阅读清单)研究了提示词优化和权重微调交替进行;第 14 讲会看到,训练也常常从一个好的 harness 产生的数据开始(蒸馏)。
6.2 什么时候值得做元优化
你需要:(1) 一个可靠的、自动化的指标(否则会掉进第 5.1 节的坑);(2) 一个足够大的搜索集和一个独立的测试集;(3) 评估的预算;(4) 记录完整的轨迹(这是给优化器、也是给你自己的最重要的信息)。如果这些都没有,先手工调——手工调的过程(看失败、想原因)本身就会帮你建立评估集和诊断的规则。
6.3 本讲模拟的边界
- 没有真实的 LLM。模拟 LLM 写哪种修改、改测试的概率都是设定的;三种差距(改测试、写特例、漏边界)发生得多频繁,取决于这些参数。真实的代码和测试是真的被执行的,所以”可见测试通过、隐藏测试失败”是真实的代码行为;
- 12 个任务都很小(一个函数),真实的仓库有成千上万个文件,大部分难度在于找到该改的地方(搜索、读代码),本讲几乎没有模拟这部分;
- 192 种组合的 harness 空间是人设计的;真实的元优化(ADAS、Meta-Harness)搜索的是代码,空间几乎无限,需要 LLM 的创造力,也更容易产生意外的结果。
7. 本讲小结
- 编程智能体 = LLM + 工具 + harness。工具很少(搜索、读、编辑、运行命令),但设计很讲究(智能体—计算机接口);harness 包括系统提示、沙盒、项目说明文件、上下文管理、子智能体、权限与审批、钩子、检查点、日志。
- 编程特别适合智能体:代码可执行、可测试,核查器是现成的、确定的;环境是文本的;可以撤销;测试结果是天然的奖励。模拟里,带测试的循环让真实成功率从 15% 提高到 28%~34%。
- 测试通过 ≠ 改对了:改测试、针对测试写特例、漏掉边界情况。评估要用隐藏的测试(SWE-bench 的 FAIL_TO_PASS 和 PASS_TO_PASS);对策分别是测试只读、自我检查、提醒边界情况。模拟里”自称成功”始终在 75%~99%,真实成功率从 29% 到 73%。
- 元优化把提示词、工作流、代码、harness 当作优化的对象:OPRO、DSPy、TextGrad、GEPA 优化提示词;ADAS、AFlow 优化工作流和整个智能体;AlphaEvolve 进化程序;Darwin Gödel Machine 修改自己;Meta-Harness 优化 harness 代码。趋势是优化的对象越来越大、反馈越来越丰富、提议者本身就是编程智能体。读完整轨迹的提议者远好于只看分数或摘要的(Meta-Harness:中位数 50.0 vs 34.6)。
- 风险:优化错了指标(古德哈特定律:优化”自称成功”选出的 harness 真实成功率只有 39%);在搜索集上过拟合(赢家的诅咒);成本;自我修改的安全问题。
8. 与项目的关系
- 课程要求项目”使用最先进的工具和概念,不能只是把问题描述丢给一个普通的 LLM”。如果你的项目是一条 LLM 流水线,可以考虑用 DSPy / GEPA 优化它的提示词——但先建好评估集和指标。
- 把评估集分成搜索集和测试集;所有的调整(手工的或自动的)只看搜索集,最后在测试集上报告一次。
- 记录每一次运行的完整轨迹:它们是你调试、做错误分析、写论文的原始材料,也是元优化器最需要的信息。
- 如果你在做编程类的智能体(或者用编程智能体帮你写项目代码):保护你的测试,让评判用的测试和它看到的测试分开,检查它有没有”针对测试写特例”。
- 你的项目代码大概率会有编程智能体参与。把它当作一个能干但会犯错的合作者:给它清楚的任务和项目说明,让它运行测试,自己审查它的修改。
9. 练习
练习 1(动手:更爱改测试的模型)。把 coding_agent.py 的 PARAMS["hack"] 从 0.12 改成 0.4,运行 01 号文件演示 3 的前两行(“只说通过 / 失败,测试可以改”和”+ 测试只读”)。“自称成功”和”真的成功”各怎样变化?测试只读之后,真实的成功率为什么没有明显提高?
参考答案
实测(12 个任务 × 20 次):
| 自称成功 | 真的成功 | 改测试的比例 | |
|---|---|---|---|
| hack = 0.12,测试可以改 | 85% | 29% | 13% |
| hack = 0.12,测试只读 | 75% | 32% | 0% |
| hack = 0.4,测试可以改 | 92% | 25% | 33% |
| hack = 0.4,测试只读 | 63% | 28% | 0% |
更爱改测试的模型,“自称成功”更高(92%),真实的成功率更低(25%)——指标和真实目标朝相反的方向走。测试只读之后,自称成功降到 63%,变得诚实了,但真实的成功率只从 25% 提高到 28%:被拦下的”改测试”只是失败了一次,消耗了一次尝试机会,模型并没有因此学会改对代码。护栏能防止作弊、让报告变得可信,但不能让一个不会的模型变会——那需要更好的信息(详细的错误信息、读源代码)或更好的模型。
练习 2(动手:诊断能力)。在 02 号文件里,从 FIXES 中删掉 "missed_edge_case" 这一项(读轨迹的优化器不再能诊断”漏了边界情况”),运行演示 1。读轨迹的优化器还领先吗?这说明了什么?
参考答案
实测(20 次运行的平均):第 1、3、5、10 次评估后的最好分数从 36% / 66% / 75% / 78% 变成 36% / 42% / 60% / 72%,选出的 harness 在留出任务上从 77% 降到 72%——第 3 次评估时的优势几乎消失了(只看分数是 46%)。默认 harness 最常见的失败就是”漏了边界情况”(27 次),诊断不出它,优化器就先去处理次要的问题。
这说明”读轨迹”的价值取决于诊断的质量:反馈再丰富,如果优化器不能从中正确地归纳出原因,也就和只看分数差不多。这也是 Meta-Harness 的消融里”摘要”几乎没有帮助的原因——LLM 写的摘要丢掉了诊断需要的细节。真实系统里,诊断由 LLM 完成;它的能力(以及你给它看什么)决定了元优化的效率。
练习 3(动手:更多的搜索能弥补太小的搜索集吗?)。把 BUDGET 改成 30,搜索集只用 2 个任务 × 3 次,分别运行读轨迹的优化器和随机搜索,比较搜索集上的分数和留出任务上的分数。
参考答案
实测(20 次运行的平均):
| 预算 10 | 预算 30 | |
|---|---|---|
| 读轨迹:搜索集 / 留出 | 79% / 64% | 80% / 64% |
| 随机搜索:搜索集 / 留出 | 71% / 56% | 79% / 65% |
预算增加到 3 倍,读轨迹的优化器在留出任务上没有任何提高,随机搜索追上了它;两者的差距(搜索集比留出任务高 14~16 个点)都没有缩小。搜索集太小时,评估的噪声决定了”谁看起来最好”,搜索得再多,也只是在噪声里挑得更细。对策是增大搜索集、重复评估,而不是增加搜索的步数。
练习 4(思考:设计隐藏测试)。给本讲的 average 函数写一组隐藏测试,要求能区分”改对了""漏了边界情况""针对测试写特例”三种修改。再想一想:SWE-bench 为什么同时需要 FAIL_TO_PASS 和 PASS_TO_PASS 两类测试?
参考思路
average 的可见测试是 average([2, 4]) == 3.0 和 average([1, 2]) == 1.5。隐藏测试应该包括:(1) 另一个普通输入(例如 [1, 2, 4])——专门针对 [1, 2] 写特例的代码会失败;(2) 边界情况(空列表、只有一个元素、负数)——只修了整除的代码在空列表上会抛出 ZeroDivisionError;(3) 如果规格有要求,还有类型(返回 float 而不是 int)。本讲的隐藏测试正是 [1, 2, 4]、[]、[-3]。
FAIL_TO_PASS 检查”问题被解决了”;PASS_TO_PASS 检查”没有改坏别的东西”。只有前者,智能体可能用一个粗暴的修改(例如删掉一个功能)让新测试通过,同时破坏了原来正常的行为;只有后者,什么都不改也能通过。两者一起,才逼近”改对了”的定义。
练习 5(思考:你的项目里的古德哈特定律)。假设你的项目用一个 LLM 评委给系统的回答打 1~10 分,并用 GEPA 自动优化系统的提示词、最大化这个分数。可能会出什么问题?你会怎样防范?
参考思路
优化器会找到评委的偏好而不是真正的质量:例如回答越来越长(评委的长度偏差)、越来越自信、使用评委喜欢的措辞、甚至在回答里写”这是一个优秀的回答”(针对评委的提示注入)。防范:(1) 先验证评委(第 15 讲:位置偏差、长度偏差、和人工判断的一致率、负对照);(2) 用多个评委、混合指标(加上可验证的部分,例如事实核查、引用是否正确);(3) 留出一个用人工判断的测试集,定期检查优化后的系统在人看来是不是真的变好了;(4) 读优化器生成的提示词——它是文字,人能看懂,如果出现”尽量写长一些”之类的指令,就是警报;(5) 限制搜索空间(例如回答长度的上限)。
10. 延伸阅读
课程阅读清单(Meta Agents and Meta Optimization)
- Hu、Lu、Clune. Automated Design of Agentic Systems(ADAS). ICLR 2025. arXiv 2408.08435. 必读
- Zhang 等. AFlow: Automating Agentic Workflow Generation. ICLR 2025. arXiv 2410.10762.
- Khattab 等. DSPy: Compiling Declarative Language Model Calls into Self-Improving Pipelines. ICLR 2024. arXiv 2310.03714.
- Yang 等. Large Language Models as Optimizers(OPRO). ICLR 2024. arXiv 2309.03409.
- Yuksekgonul 等. TextGrad: Automatic “Differentiation” via Text. arXiv 2406.07496.
- Agrawal 等. GEPA: Reflective Prompt Evolution Can Outperform Reinforcement Learning. ICLR 2026. arXiv 2507.19457.
- Novikov 等. AlphaEvolve: A Coding Agent for Scientific and Algorithmic Discovery. arXiv 2506.13131.
- Zelikman 等. Self-Taught Optimizer (STOP): Recursively Self-Improving Code Generation. 2023.
- Zhang 等. Darwin Gödel Machine: Open-Ended Evolution of Self-Improving Agents. ICLR 2026. arXiv 2505.22954.
- Zhang 等. Hyperagents. arXiv 2603.19461;Zheng 等. Dream-RSI: Recursive Self-Improvement through Evolving Worlds. 2026.
harness 与编程智能体
- Lee 等. Meta-Harness: End-to-End Optimization of Model Harnesses. arXiv 2603.28052.
- Jimenez 等. SWE-bench: Can Language Models Resolve Real-World GitHub Issues? ICLR 2024.
- Yang 等. SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering. NeurIPS 2024.
- Anthropic. Building effective agents(2024);Effective context engineering for AI agents(2025)。
- Databricks. What is an AI Agent Harness?(2026)。
11. 下一讲预告
到目前为止,我们的智能体处理的都是已经数字化的文字:网页、文档、数据库。但人类几千年的知识里,有大量还躺在图书馆和档案馆里:手稿、古籍、旧报纸。它们的语言古老、版式特殊、纸张破损,今天最好的商业模型也读不好。第 13 讲讲 CHURRO:
- 光学字符识别(OCR)和视觉语言模型(VLM);怎样衡量转写的质量(编辑距离、字符错误率);
- 为什么历史文献难:古老的语言和拼写、多栏和竖排的版式、阅读顺序、破损和扫描质量;
- CHURRO-DS:统一 155 个语料库、近 10 万页、跨越 22 个世纪的数据集是怎样整理出来的;
- 一个 30 亿参数的开源模型,经过微调,比最好的商业模型更准,成本只有它的 1/15。