对应 Maps to:Stanford CS329Z Engineering AI Agents(Fall 2026),Lecture 15 · Wed Nov 18 · Coding & Software Agents。阅读材料:Yang et al., SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering(NeurIPS 2024);Wang et al., OpenHands: An Open Platform for AI Software Developers as Generalist Agents(ICLR 2025)/ Claude Code 最佳实践;Young, Effective Harnesses for Long-Running Agents(Anthropic Engineering,2025-11-26);Jimenez et al., SWE-bench: Can Language Models Resolve Real-World GitHub Issues?(ICLR 2024)。 依据说明:写作时(2026-10-16)这一讲的 slides 还没有公布。本讲依据课表上的主题描述来写:编程智能体端到端怎样工作、SWE-agent(智能体–计算机接口)、Claude Code 和 OpenHands 的架构、脚手架即设计决策、SWE-bench 与 4 元组、长时间运行的 harness、软件开发的未来。引用的论文、文档和博客内容都核对过原文;Claude Code 的最佳实践依据的是现行的官方文档(code.claude.com/docs)。全部实验是本讲义自己设计的,不代表课堂上讲了什么。 配套代码 Code:
code/lecture15/(只用 Python 标准库;一个真实的小仓库 + 5 个脚本 + 2 个模块 + 1 个可选脚本;不需要 API key;第一次运行先花约 30 秒造任务,之后每个脚本几秒到两分钟) 预计阅读时间:3.5 小时(含运行代码)
0. 本讲导航 · How to read this lecture
0.1 学习目标 Learning goals
学完这一讲你应该能够:
- 描述一个编程智能体从读 issue 到提交补丁的完整循环,并用 4 元组描述一个 SWE-bench 任务。
- 解释”智能体–计算机接口”(ACI)为什么重要,说出 SWE-agent 的四条设计原则,以及查看窗口、搜索、带检查的编辑、上下文管理各自解决什么问题。
- 比较几种脚手架:智能体循环(SWE-agent、OpenHands、Claude Code)和写死的流程(Agentless),知道各自在成本、可预测性、灵活性上的取舍。
- 审查一个编程智能体的评估:FAIL_TO_PASS 和 PASS_TO_PASS 各管什么、测试太弱时会发生什么、为什么需要 SWE-bench Verified、pass@1 和 pass^k 说明什么。
- 设计一个长时间运行的 harness:特性清单、小步提交、进度文件、启动脚本、端到端测试,各自对付哪种失败。
0.2 先修与本讲范围 Prerequisites & scope
| 默认你已经会 Assumed | 本讲只给直觉,后面再展开 Previewed only |
|---|---|
| 第 4 讲:工具注册表、校验与执行分离;第 6 讲:workflow vs agent | 电脑操作 / 网页智能体(第 17 讲) |
| 第 11 讲 §5:SWE-smith(从仓库造任务);第 12 讲:4 元组、有效性、pass^k | 用强化学习训练编程模型(不在本课范围) |
| 第 14 讲:沙箱、权限、确认疲劳;前置讲 P1:进程、子进程、超时 | 多智能体的软件开发流程(第 8 讲已讲过一般情形) |
0.3 本讲地图 Map
§0.6 开场:一行 // 写成了 *
§1 编程智能体端到端:一个 SWE-bench 式的任务 ← 代码 01
§2 智能体–计算机接口(ACI):SWE-agent 的设计与消融 ← 代码 02
§3 脚手架即设计决策:Agentless、OpenHands、Claude Code ← 代码 03
§4 评估编程智能体:SWE-bench、Verified、作弊、重复运行 ← 代码 04
§5 长时间运行的 harness ← 代码 05
§6 安全:编程智能体的权限与沙箱
§7 用真实模型试一试 ← 代码 06(可选)
§8 软件开发的未来
§9 小结 §10 常见误区 §11 练习 §12 延伸阅读 §13 下一讲预告
0.4 术语 Terms
| 术语 | 本讲里的意思 |
|---|---|
| issue | 一个问题报告:描述症状(怎样复现、期望什么),不说 bug 在哪 |
| 补丁(patch) | 对仓库的修改;SWE-bench 里智能体最后提交的就是一个补丁 |
| FAIL_TO_PASS / PASS_TO_PASS | 修复的 PR 带来的、打补丁前失败之后应该通过的测试 / 原本就通过、之后也必须通过的测试 |
| resolved | 两组测试都通过 |
| ACI(agent-computer interface) | 给智能体用的”界面”:它能调用哪些命令、每个命令返回什么样的观察 |
| 脚手架(scaffold / harness) | 包在模型外面的那一层:工具、循环、提示词、上下文管理 |
| 复现脚本 | 智能体根据 issue 写的、能重现 bug 的一小段代码 |
| 差分检查 | 在补丁后的代码和原始(正确的)实现上跑同样的输入,结果必须一样 |
0.5 本讲的代码
代码带了一个真实的小仓库:code/lecture15/repo/,Orrin Grid 的计费代码 gridbill,5 个模块、92 个测试。你可以直接打开它、跑它的测试。
repo_lab.py 照着 SWE-smith 往仓库里注入 bug,造出 125 个 SWE-bench 式的任务;agent_lab.py 实现两种脚手架:一个迷你 SWE-agent,以及一个 Agentless 式的流程。它们都在真实的文件上调用真实的工具,在子进程里跑真实的测试。
提醒:模拟的是”模型的判断”:看不看得出哪一行有问题、挑不挑得中正确的改动、写编辑时会不会写错缩进,这些都是参数。所以这一讲的成功率不能代表任何真实模型;要看的是机制:哪个设计让哪种错误更容易发生、更难恢复,以及评估在哪里会失真。
0.6 开场:一行 // 写成了 *
一个用户报告:“net_charge([1.0] * 5, 100, 'solar') 返回 12.0,应该是 0.0。” 太阳能用户用了 5 度电、卖回电网 100 度,净费用应该是 0。
net_charge 本身没有错。错在它调用的 usage_charge 里:kwh * price 被写成了 kwh // price。一个编程智能体要做的事和人一样:读 issue,找到 net_charge,顺着调用找到 usage_charge,看出那个 //,改掉,写个小脚本确认 bug 不再出现,跑一遍测试确认没弄坏别的,提交。
每一步都可能出错:搜索返回太多结果、文件太长看不全、编辑时缩进写错、改完没跑测试、跑测试时卡死……这一讲讨论怎样设计这些步骤,以及怎样知道它真的修好了。
1. 编程智能体端到端 · Anatomy of a coding agent
1.1 SWE-bench:一个任务的 4 元组
Jimenez et al.(ICLR 2024)从 12 个流行的 Python 仓库的真实 GitHub issue 和对应的 PR 里收集了 2,294 个任务。每个任务按第 12 讲的 4 元组来看:
| 部分 | SWE-bench 里 |
|---|---|
| 请求 | issue 的文字(只描述症状) |
| 环境 | issue 提出那一刻的仓库快照(在容器里) |
| 停止条件 | 智能体提交一个补丁,或者用完预算 |
| 判分器 | 打上补丁,跑两组测试:FAIL_TO_PASS(修复的 PR 新增 / 修改的测试,打补丁前失败、之后应该通过)和 PASS_TO_PASS(原本就通过、之后也必须通过的测试)。两组都过才算 resolved |
FAIL_TO_PASS 对智能体是隐藏的:它们随修复的 PR 一起加进来,智能体只能看到 issue 和仓库原有的测试。论文发布时,最好的模型(Claude 2 + BM25 检索)只解决了 1.96%。
代码 01 演示 1 里的那个任务:
| 部分 | 内容 |
|---|---|
| 请求 | `net_charge([1.0] * 5, 100, 'solar')` returns 12.0, but it should be 0.0. |
| 环境 | 5 个源文件;智能体能跑的测试 89 个 |
| 停止条件 | 提交,或用完 40 步 |
| 判分器 | 3 个 FAIL_TO_PASS(test_net_charge_floor、test_usage_charge_offpeak、test_usage_charge_with_peak)+ 89 个 PASS_TO_PASS |
| bug(上帝视角) | tariffs.py:51,函数 usage_charge,* → // |
1.2 任务从哪来:SWE-smith 的流水线(代码 01 演示 2)
SWE-bench 的任务要从真实的 issue 和 PR 里挖,很费劲。第 11 讲 §5 讲过 SWE-smith 的反向做法:拿一个测试能跑通的仓库,自动注入 bug,留下”让至少一个测试失败”的那些。迷你版的流水线:
| 步骤 | 数量 |
|---|---|
| 候选的 bug(换运算符 / 常量加一 / 删掉一行) | 461 |
| 改完不能编译 | 24 |
| 崩溃 / 超时 / 复现不出来 | 6 |
| 没有测试失败(测试抓不到这个 bug) | 108 |
| 和已有任务重复 | 198 |
| 留下的任务 | 125 |
108 个 bug 没有任何测试能抓住:这本身就是一个关于测试质量的信号(第 11 讲 §5)。125 个任务里,有 53 个的 bug 不在 issue 提到的那个函数里,要顺着调用往下找。
1.3 一条轨迹(代码 01 演示 3)
迷你 SWE-agent 在这个任务上走了 10 步:
1 search_dir('def net_charge') → gridbill/tariffs.py (1 matches)
2 open gridbill/tariffs.py 64 → 看到 net_charge:调用了 usage_charge 和 export_credit
3 open gridbill/accounts.py 15 → (被文档字符串里的 "charge" 带偏,去看了 accounts.charge)
4 open gridbill/tariffs.py 47 → 看到 usage_charge
5 open gridbill/accounts.py 32
6 open gridbill/tariffs.py 7 → 看到用到的常量
7 edit 51:51 → 把 // 改回 *(语法检查通过)
8 python reproduce.py → 没有输出:bug 不再出现
9 python run_tests.py → 89 passed
10 submit
第 3、5 步值得一提:它按名字找”相关的函数”,被文档字符串里的一个词带去了不相关的文件。真实的智能体也常常这样”顺着字面线索走错路”。走错的代价是步数和上下文。
判分:resolved(FAIL_TO_PASS 全过、PASS_TO_PASS 全过、差分检查一致)。
这个循环就是第 1 讲 ReAct 的编程版:看 → 想 → 改 → 验证 → 不行就撤销。和”代码补全”相比,难点在于定位:issue 只描述症状,bug 可能在好几层调用之下;以及验证:智能体看不到判分用的测试,只能靠自己写的复现脚本和仓库原有的测试。
1.4 成本:每一步都要重发整个对话
API 是无状态的,智能体每走一步,都要把整个对话(系统提示、工具说明、之前所有的观察)重新发一遍。这条 10 步的轨迹:
| 设置 | 累计输入 token | 平均每步 |
|---|---|---|
| 只保留最近 5 个观察的全文(默认) | 29,817 | 2,981 |
| 保留完整历史 | 37,324 | 3,732 |
长任务的成本主要由”上下文有多长 × 走了多少步”决定(第 2 讲 §5、第 7 讲)。这也是 ACI 要让观察”简洁”的原因之一。
2. 智能体–计算机接口 · The agent-computer interface
2.1 SWE-agent 的核心论点
Yang et al.(NeurIPS 2024)提出:语言模型智能体是一类新的用户,应该给它专门设计的接口,就像人用 IDE 而不是裸终端写代码。他们把这个接口叫做 ACI(agent-computer interface),并总结了四条设计原则:
- 动作要简单、好懂:选项少、文档短。
- 动作要紧凑、高效:常见的操作合并成尽量少的步骤。
- 环境反馈要有信息量、但要简洁。
- 护栏:例如编辑后自动做语法检查,限制错误扩散、加快恢复。
具体的组件:
| 组件 | 设计 |
|---|---|
| 文件查看器 | 一次最多显示 100 行,带行号,告诉你上面 / 下面还有几行;open、goto、scroll_up、scroll_down |
| 搜索 | find_file、search_file、search_dir 最多返回 50 条;超过就不给结果,让你缩小范围 |
| 编辑 | 把打开文件的第 start–end 行换成新文本;之后跑 flake8,引入新的语法错误就撤销并让智能体重试;否则显示改后的文件视图 |
| 上下文管理 | 最近 5 个之前的观察折叠成一行;之前的报错信息只留第一个 |
| 空输出 | 命令没有输出时明确说 “Your command ran successfully and did not produce any output.” |
结果(GPT-4 Turbo):SWE-bench 完整集 12.47%、Lite 18.00%;同一个模型用 RAG(检索后一次生成补丁)只有 1.31% / 2.67%;HumanEvalFix 87.7%。平均每个任务 1.59 美元(完整集)。
2.2 消融:每个设计值多少(代码 02)
论文的消融(SWE-bench Lite,GPT-4 Turbo):默认 18.0%;编辑不做语法检查 15.0;没有编辑命令 10.3;迭代式搜索 12.0、没有搜索 15.7;查看器 30 行 14.3、整个文件 12.7;完整历史 15.0;只给 shell 11.0。
迷你版的结果(125 个任务,同一个模拟模型、同样的”运气”):
| ACI | 修好 | 平均步数 | 平均输入 token | 和默认逐题比:变差 / 变好 | 论文 |
|---|---|---|---|---|---|
| 默认(SWE-agent 式) | 74.4% | 15.8 | 40,142 | 18.0% | |
| 编辑时不做语法检查 | 66.4% | 16.8 | 43,679 | 10 / 0(p = 0.002) | 15.0% |
| 查看窗口 30 行 | 74.4% | 16.2 | 24,339 | 0 / 0 | 14.3% |
| 一次显示整个文件 | 65.6% | 16.8 | 50,521 | 11 / 0(p = 0.001) | 12.7% |
| 没有搜索命令 | 74.4% | 18.3 | 57,201 | 0 / 0 | 15.7% |
| 不折叠旧观察 | 73.6% | 15.9 | 94,605 | 1 / 0 | 15.0% |
| 只给 shell | 48.8% | 18.7 | 68,154 | 32 / 0 | 11.0% |
(两列数字来自完全不同的任务和模型,只看方向。比较 ACI 用的是逐题配对的 McNemar 检验,第 12 讲 §5.2:同一个任务上”模型的运气”相同,差别只来自设计。)
读这张表要分清哪些是真的、哪些是参数:
- 真的:每种 ACI 下每一步的观察有多长(token 一列)、要走多少步、语法检查拦下了多少真的语法错误、坏掉的文件让后面的复现和测试真的崩溃。
- 参数:“一次显示整个文件”让模型”看得没那么清楚”的程度、长上下文让它变差的程度。
在这个只有一两百行文件、十几步任务的小仓库里,窗口 30 行和完整历史只影响成本(token 差了将近 4 倍)。SWE-bench 的仓库有成千上万行、任务长得多,论文里这两项都掉了 3–4 个点。
2.3 护栏的价值:编辑失败的连锁反应
SWE-agent 的行为分析:51.7% 的轨迹至少有一次编辑失败;一旦失败,最终修好的概率从 90.5% 掉到 57.2%;约 23.4% 的失败来自连锁的编辑失败。
迷你版(代码 02 演示 3):
| ACI | 没有编辑失败:修好 | 至少一次编辑失败:修好 |
|---|---|---|
| 默认 | 80% | 58% |
| 编辑时不做语法检查 | 77% | 0% |
| 只给 shell | 83% | 2% |
有语法检查时,一次缩进写错的编辑被当场撤销,智能体看到明确的错误信息,重写一次就好了。没有语法检查时,坏掉的编辑留在文件里,之后的每一次复现、每一次测试都因为 import 失败而崩溃;智能体常常意识不到”是我上次的编辑弄坏的”,转而去别处改,越改越乱。只给 shell 的情况更糟:sed 成功时什么都不输出,智能体连自己改成了什么都看不到。
这就是第四条原则”护栏”的意思:在错误扩散之前把它拦下。它和第 14 讲的”结构型防御”是同一个思路:不指望模型不犯错,而是让错误发生不了,或者发生了马上被发现。
3. 脚手架即设计决策 · Scaffolding as design decisions
3.1 要不要智能体循环:Agentless(代码 03)
Xia et al.(2024)问了一个反直觉的问题:修 bug 真的需要”让模型自己决定下一步”吗?Agentless 用一个写死的三段流程:
- 定位:分层地找,先找文件,再找函数 / 类,再找具体的行。
- 修复:针对定位到的代码,采样多个候选补丁(search/replace 格式的编辑)。
- 验证:生成复现测试,结合仓库原有的回归测试,挑出通过的补丁。
它在 SWE-bench Lite 上修好了 32.00%(96 题),超过了当时所有开源的智能体,成本 0.70 美元。作者还人工检查了 Lite 的题目,发现有的 issue 里直接写着补丁、有的描述不充分或有误导,于是整理出一个更严格的子集 Lite-S。
迷你版(同一个模拟模型、同样的”运气”):
| 流程 | 修好 | 模型调用 | 平均输入 token |
|---|---|---|---|
| 迷你 SWE-agent(最多 40 步) | 74.4% | 15.8 | 40,142 |
| Agentless(定位 3 次 + 4 个候选) | 63.2% | 7.0 | 8,594 |
| Agentless → 智能体(46 题交给了智能体) | 83.2% | 15.8 | 31,180 |
Agentless 采样的候选越多越好(1 个 30.4%,8 个 72.0%),这是第 9 讲的”best-of-N + 验证器”。但上限由定位决定:bug 所在的行没进前 3 个可疑行(26% 的任务),采样再多也没用。
逐题看,两者失败的地方不同。只有智能体修好的 25 题里,12 题是 Agentless 的定位没排对,智能体靠一行行试出来;只有 Agentless 修好的 11 题里,智能体有 4 题出现过编辑失败、4 题用完了步数。长轨迹里的错误会累积。
这是第 6 讲 “workflow vs agent” 在编程上的样子:能用便宜、可预测的流程的地方先用流程,失败了再升级到更贵、更灵活的智能体。 注意这里的仓库小、bug 都是”改一个记号”,从 issue 里的函数顺着调用找,就能把相关代码一次装进上下文;真实仓库里定位难得多,那正是智能体”边看边找”的价值所在。
3.2 OpenHands:通用的智能体平台
OpenHands(Wang et al., ICLR 2025,原名 OpenDevin)是一个开放的平台,目标是”像人类开发者一样”写代码、用命令行、浏览网页。它的设计:
| 组件 | 设计 |
|---|---|
| 事件流(event stream) | 按时间排列的动作和观察的集合;智能体的状态就是这个历史(外加累计成本、委派信息) |
| 智能体抽象 | 一个智能体就是一个 step(state) -> action 函数:读历史,返回一个动作(bash 命令、Python 代码、浏览器动作、消息、结束) |
| 运行时 | 每个任务一个隔离的 Docker 容器,里面有 bash、Jupyter IPython、由 Playwright 驱动的 Chromium(BrowserGym 的动作空间);可以用任意的基础镜像 |
| AgentSkills | 自动导入 IPython 的工具函数库(edit_file、scroll_down、parse_pdf……);只在模型自己不容易写出来、或需要调用外部模型时才加 |
| CodeAct 智能体 | 默认的通用智能体:每一步要么和用户说话,要么执行代码(bash / Python / 浏览器命令)——“代码即动作” |
| 委派 | AgentDelegateAction:例如把网页浏览交给专门的 BrowsingAgent |
CodeAct 在 SWE-bench Lite 上 26.0%(Claude 3.5 Sonnet,每题约 1.10 美元);同一个智能体、不改系统提示,在网页(WebArena 15.3%)、问答(GPQA diamond 52.0%)等 15 个基准上都有竞争力。它的取舍和 SWE-agent 相反:通用的动作空间(写代码就能做任何事)换来灵活,代价是没有专门为编辑设计的护栏,要靠 AgentSkills 补上。
3.3 Claude Code:一个工程化的智能体环境
Claude Code 的官方文档把它描述为一个”智能体式的编程环境”:它能读你的文件、运行命令、做修改,在你看着、纠正或者离开时自主地解决问题。文档里的最佳实践几乎都围绕一个约束展开:上下文窗口很快就会被填满,而填满时表现会变差。
把文档里的建议按本课程的概念整理一下:
| 建议 | 对应的概念 |
|---|---|
| 给它一个能自己验证的方法:测试、构建、截图对比。没有可运行的检查,“看起来做完了”就是唯一的信号,你就成了验证循环 | 第 12 讲的判分器;本讲的复现脚本和回归测试 |
| 让它出示证据(测试输出、跑了什么命令、截图),而不是宣称成功 | 第 13 讲:读记录 |
| 先探索、再计划、再写代码、再提交(plan mode 只读不改);一句话能说清的小改动就跳过计划 | 第 6 讲:先规划后执行 |
| CLAUDE.md:每次会话开始都会读;只放 Claude 猜不到的东西(命令、风格、仓库规矩),越短越好,太长了规则会被淹没 | 第 7 讲:记忆;第 2 讲:上下文的成本 |
| 权限:白名单放行信得过的命令;沙箱做操作系统级的隔离;auto 模式用一个分类器审查动作,只拦看起来有风险的(越权、陌生的基础设施、由不可信内容驱动的动作) | 第 14 讲:最小权限、结构型防御、确认疲劳 |
| Hooks 是确定性的,一定执行;CLAUDE.md 只是建议 | 第 14 讲:结构型 vs 检测型 |
| 子智能体在自己的上下文里做调查,只把摘要带回来 | 第 7 讲:上下文隔离;第 8 讲 |
/clear 在不相关的任务之间清空上下文;同一个问题纠正两次还不对,就清空、写一个更好的提示 | 第 7 讲:上下文管理 |
非交互模式 claude -p:放进 CI、脚本、批量迁移(--allowedTools 预先放行需要的工具) | 第 5 讲:框架与编排 |
| 并行会话(git worktree 隔离)、写的人和审的人分开(新的上下文审查不会偏向自己刚写的代码) | 第 8 讲:多智能体;第 13 讲:评审的独立性 |
| 常见的失败:大杂烩会话、反复纠正、过长的 CLAUDE.md、“信了再验”的差距、无限探索 | 本讲 §2.3、§5 |
3.4 设计决策对照
| 决策 | SWE-agent | OpenHands(CodeAct) | Agentless | Claude Code |
|---|---|---|---|---|
| 谁决定下一步 | 模型(ReAct 循环) | 模型(ReAct 循环) | 写死的三段流程 | 模型(循环),人可以随时打断、纠正 |
| 动作空间 | 专门设计的 ACI 命令 | 执行代码(bash / Python / 浏览器) | 定位 + search/replace 编辑 | 文件工具 + shell + MCP + 子智能体 |
| 护栏 | 编辑后的语法检查 | 靠 AgentSkills 和沙箱 | 不能编译的候选直接丢掉 | 权限模式、沙箱、hooks、检查点 |
| 上下文管理 | 折叠旧观察 | 事件流 + 压缩 | 只给定位到的代码 | 自动压缩、/clear、子智能体 |
| 验证 | 智能体自己跑 | 智能体自己跑 | 复现测试 + 回归测试,投票 | 鼓励给出可运行的检查,可以做成 hook / 子智能体 |
没有哪一列”最好”:它们是在不同的约束(成本、通用性、可预测性、人在回路的程度)下做出的设计决策。这就是课表里说的脚手架即设计决策,也是第 12 讲说”benchmark 分数是模型 × 脚手架的分数”的原因。
4. 评估编程智能体 · Evaluating coding agents
4.1 判分细节(代码 04 演示 1)
默认 ACI 的智能体在 125 个任务上:resolved 93 个;没修好的 32 个全部是”FAIL_TO_PASS 一个都没通过”,其中 26 个最后什么改动都没交。原因是它每次修改后都跑复现和回归测试,不通过就撤销,宁可不交,也不交一个会弄坏别的功能的补丁。所以”修好了 issue、弄坏了别的(PASS_TO_PASS 失败)“一题都没有。
这在小仓库、单记号的 bug 上不奇怪:修对了很少会弄坏别的。真实的补丁常常改几十行,“修好了症状、弄坏了别处”是 SWE-bench 上常见的失败,PASS_TO_PASS 就是为它准备的。只看 FAIL_TO_PASS 的判分器会把这样的补丁算成成功。
4.2 测试太弱时:作弊的补丁(代码 04 演示 2)
一个”会作弊”的智能体:修不好的时候,在 issue 提到的那个函数开头加一个特例,让 issue 里的例子直接返回期望值:
- if hours <= 0:
+ if (plan, hours,) == ('basic', 3,):
+ return 6
+ if hours <= 0:
| 诚实的智能体 | 会作弊的智能体 | |
|---|---|---|
| resolved(SWE-bench 的判分) | 74.4% | 76.8% |
| 打了特例的提交 | 0 | 4 |
| 其中被判成 resolved | - | 3 |
| 其中差分检查发现不一致 | - | 2 |
当隐藏测试只覆盖 issue 里的那个例子时,“修好了”和”绕过了”在判分器眼里是一样的。这是第 12 讲的结果有效性问题,也是第 9 讲”奖励被钻空子”的编程版:拿测试通过当奖励去训练编程模型,模型就有动机学会这种绕法。
差分检查是一种补救:把补丁后的代码和原始(正确的)实现放在一起,喂同样的输入,结果必须一样。只用测试里出现过的输入还不够(它们往往就是 issue 里的那几个例子);加上扰动(整数 ±1、±2,浮点数 ×0.5、×2,字符串换成同一位置出现过的别的值)之后,才抓住了 3 个里的 2 个。在 SWE-bench 里,修复的 PR 就是”原始实现”,所以这类差分 / 属性测试在评估时是做得到的。
4.3 重复运行(代码 04 演示 3)
63 个任务、每个跑 4 次:
| 指标 | 值 |
|---|---|
| pass@1(平均每次) | 72.6% |
| pass@4(4 次里至少一次) | 90.5% |
| pass^4(4 次全部成功) | 44.4% |
6 题 4 次都没修好、28 题 4 次都修好,其余 29 题时好时坏。排行榜上的一个数字通常是 pass@1;如果有可靠的验证器能从几次尝试里挑出对的(Agentless 的思路),pass@k 才有意义;如果你要把智能体交给用户、每次都要靠得住,看 pass^k(第 12 讲 §4.1)。
4.4 说不清楚的任务:SWE-bench Verified(代码 04 演示 4)
OpenAI 和 SWE-bench 的作者合作,请 93 位有 Python 经验的开发者标注了 1,699 个随机样本:38.3% 被标为问题描述不充分,61.1% 被标为单元测试可能不公平地判错正确的解法;总共过滤掉 68.3%,留下 500 个,即 SWE-bench Verified。GPT-4o 在 Verified 上 33.2%,Agentless 从原来的 16% 翻了一倍。(作者也说明这个过滤可能偏严。)
迷你版里也能找到”说不清楚”的任务:在 bug 所在的那一行上,枚举所有”改一个记号”的修法,看有没有一个能通过 issue 里的例子和全部可见测试、却通不过隐藏测试。125 个任务里有 7 个。对这样的任务,一个同样满足 issue 要求的修法会被判错:判分器检查的是”和原来的实现一样”,而不只是”满足 issue 的要求”。(迷你版里模拟的模型多半会挑中原来的写法,这是参数,所以它在这 7 题上的分数并不低;真实模型没有这个”偏好”。)
这正是第 12 讲 §3 的任务有效性:一个任务应该”有能力就能做对”。过滤掉说不清楚的任务,分数会变,排名也可能变。
4.5 饱和
Anthropic 的 Demystifying Evals(第 13 讲)提到,LLM 在 SWE-bench Verified 上一年之内从 40% 左右涨到了 80% 以上。一个接近饱和的评估不再能区分好坏(第 13 讲 §7 第 7 步)。这也是为什么会不断出现新的、更难的编程评估:更大的仓库、更长的任务、多语言、需要和人交互的任务。
5. 长时间运行的 harness · Harnesses for long-running agents
5.1 问题:每个新会话都不记得上一个
SWE-bench 的任务十几步就能做完。但让智能体”做一个 claude.ai 的克隆”这种任务,要跨很多个上下文窗口。Young(Anthropic,2025-11-26)发现,即使是 Opus 4.5 这样的前沿模型,在 Claude Agent SDK 上靠压缩在很多个上下文窗口里循环,只给一个高层指令,也做不出一个生产质量的应用。失败有两种主要模式:
- 想一口气做完:做到一半上下文用完,留下一个半成品、没有记录;下一个会话要花很多时间猜发生了什么、把基本的应用重新跑起来。压缩也救不了,因为它不一定能把清楚的指示传给下一个智能体。
- 过早宣布胜利:后来的会话看看周围,发现已经做了不少,就宣布完工。
文章用了一个比喻:一个软件项目由轮班的工程师来做,每个新来的工程师都不记得上一班发生了什么。
5.2 解决:初始化智能体 + 编程智能体
| 失败 | 初始化智能体(第一个会话)做的事 | 编程智能体(之后每个会话)做的事 |
|---|---|---|
| 过早宣布完工 | 写一份特性清单(JSON,200 多条端到端的特性,全部标成 failing) | 开工时读清单,挑一个最优先的、还没做完的特性 |
| 留下有 bug 或没记录的状态 | 初始的 git 仓库和进度文件 | 开工时读进度文件和 git 日志,在开发服务器上跑一遍基本的测试;收工时提交 git、写进度 |
| 没测就标成完成 | (特性清单) | 只有仔细测过(用浏览器自动化、像真实用户一样)才标成 passing |
| 每次都要摸索怎样跑起来 | 写一个能启动开发服务器的 init.sh | 开工时先读 init.sh |
几个细节:特性清单用 JSON 而不是 Markdown,因为模型更不容易不恰当地修改或覆盖 JSON 文件;提示里用了很强的措辞,只允许改 passes 字段,“删掉或修改测试是不可接受的”;“一次只做一个特性”对付”想一口气做完”至关重要。文章脚注说明:两个”智能体”只是第一个提示不同,系统提示、工具和 harness 完全一样。
5.3 模拟(代码 05)
30 个特性、最多 25 个会话、每个会话 100 个单位的工作量(参数化的模拟,没有真的写代码):
| harness | 真的能用的特性 | 标成完成但其实有 bug | 提前宣布完工 | 结束时主分支坏着 |
|---|---|---|---|---|
| 只给高层指令(靠压缩接着干) | 7.2 | 3.3 | 100% | 86% |
| + 特性清单(JSON,全部 failing) | 15.7 | 6.2 | 0% | 68% |
| + 一次一个特性 + git + 进度文件 | 23.7 | 6.3 | 0% | 0% |
| + init.sh + 冒烟测试 + 端到端测过才标通过 | 29.8 | 0.2 | 0% | 0% |
每加一个部件,就消掉一种失败:特性清单消掉”过早宣布完工”;一次一个特性 + git 消掉”留下坏掉的主分支”;进度文件消掉”每次都要重新摸索”(摸索 / 修复的工作量从 482 降到 0);端到端测试消掉”没测就标完成”。
这些部件和人类工程师的习惯一一对应:需求清单、小步提交、交接记录、启动脚本、冒烟测试。长时间运行的智能体缺的不是能力,而是跨会话的状态;文件和 git 就是它的外部记忆(第 7 讲)。文章最后留下的开放问题之一是:一个通用的编程智能体好,还是测试、QA、清理代码这样分工的多智能体好(第 8 讲)。
6. 安全:编程智能体的权限与沙箱 · Safety for coding agents
编程智能体同时具备第 14 讲 Rule of Two 的三个性质:它读不可信的内容(依赖的源码、issue、网页、测试输出),能接触敏感的东西(凭证、源码),能改变状态(写文件、执行命令、推送代码)。所以第 14 讲的结构型防御在这里尤其重要:
- 沙箱:OpenHands 每个任务一个 Docker 容器;Claude Code 提供操作系统级的沙箱,限制文件系统和网络访问;SWE-bench 的评估也在容器里跑。
- 权限按会话配置:白名单放行信得过的命令;无人值守的批量任务用
--allowedTools预先放行需要的工具、拒绝其他一切(dontAsk模式)。 - 确认留给高风险的动作:文档自己就写道,手动模式下”批到第十次,你就是在点击通过而不是在审查”——第 14 讲的确认疲劳。auto 模式用一个分类器代替人审查大多数动作,只拦看起来有风险的。
- 可恢复:Claude Code 每次修改前都给文件拍快照,可以回退到任何一个检查点;但它只追踪自己的文件编辑工具做的修改,不追踪 shell 命令,文档明确说它不能代替 git。长时间运行的 harness 用 git 提交来保证”随时可以回到干净的状态”(§5)。
- 测试本身也要保护:长时间运行的 harness 明确禁止智能体删改测试;SWE-bench 判分时用修复 PR 的测试,不用智能体可能改过的版本。否则”让测试通过”最简单的办法就是改测试(第 12 讲练习 2)。
7. 用真实模型试一试 · Try it on a real model
06_real_llm.py 用 Agentless 的形状:定位由代码做(从 issue 里的函数顺着调用找到相关的函数和常量),修复交给 Claude:给它 issue 和这些代码(带行号),让它输出一个 SEARCH/REPLACE 编辑;然后按 SWE-bench 判分,再做差分检查。默认取 8 个任务,每个 1 次调用。
python3 06_real_llm.py --toy
(--toy 用模拟模型,不联网;去掉它就调用真实模型,需要 pip install anthropic 和 API key。)
运行之后看:没修好的那几个,是定位给的代码里根本没有 bug,还是模型改错了地方或改错了方式?模型有没有”改得比需要的多”,例如顺手重写了整个函数?这类补丁更容易弄坏 PASS_TO_PASS。
8. 软件开发的未来 · The future of software development
课表把这一讲的最后一个主题叫做”软件开发的未来”。不做预言,只说这一讲的内容能支持的几点观察:
- 工作的重心在移动。Claude Code 的文档写道:不再是你写代码、让 Claude 审,而是你描述想要什么、Claude 想办法去做。当”写”变便宜了,难的部分就变成了说清楚要什么(规格、issue、验收标准)和确认做对了(测试、评审)。这正是 SWE-bench Verified 过滤掉的那两类问题:描述不充分、测试不公平。
- 验证是瓶颈,也是杠杆。本讲每一个让智能体变好的部件都和验证有关:编辑时的语法检查、复现脚本、回归测试、端到端测试、差分检查、写的人和审的人分开。Claude Code 文档的第一条建议就是”给它一个能自己验证的方法”。
- 脚手架和模型一样重要。同一个模型,换一套 ACI,结果可以差很多(§2);一个写死的流程可以和智能体打平(§3.1);一个好的长时间 harness 能把”做不出来”变成”做得出来”(§5)。
- 评估要跟上。基准会饱和、会被钻空子、会有说不清楚的任务(§4)。造一个好的编程评估,本身就是一项工程(第 12、13 讲)。
9. 小结 · Summary
- 编程智能体的循环:读 issue → 定位(顺着调用找)→ 编辑 → 用复现脚本和回归测试验证 → 不行就撤销 → 提交。难点在定位和验证。
- SWE-bench 的 4 元组:issue、仓库快照、提交或预算、FAIL_TO_PASS + PASS_TO_PASS。FAIL_TO_PASS 对智能体隐藏。
- ACI 是设计:简单的动作、紧凑的操作、简洁的反馈、护栏。编辑时的语法检查阻止了连锁失败;观察的长短决定了成本。
- 脚手架即设计决策:智能体循环灵活但贵、错误会累积;写死的流程便宜可预测但受定位限制;接力往往两者兼得。OpenHands 选择通用的动作空间,Claude Code 围绕”上下文是最重要的资源”组织一切。
- 评估编程智能体:PASS_TO_PASS 防止修好一处弄坏别处;测试太弱时作弊的补丁会被判成成功,差分 / 扰动检查可以补救;pass@1、pass@k、pass^k 回答不同的问题;说不清楚的任务要过滤(Verified)。
- 长时间运行的 harness:特性清单、一次一个特性、git、进度文件、init.sh、端到端测试——每一个对付一种失败。
- 安全:编程智能体满足 Rule of Two 的全部三条;沙箱、按会话的权限、可恢复、保护测试。
10. 常见误区 · Common pitfalls
- “模型越强,脚手架越不重要。” SWE-agent 的消融里,同一个模型换一套 ACI,从 18% 掉到 11%;RAG 式的一次生成只有 2.67%。
- “给智能体一个能执行任何命令的 shell 就够了。” 只给 shell 时没有护栏、没有简洁的反馈,编辑失败一次几乎就救不回来。
- “让它一次看到整个文件,信息越多越好。” 观察越长,成本越高,模型也越容易看漏;SWE-agent 的整文件查看器比 100 行的窗口差了 5 个多点。
- “测试通过就是修好了。” 测试只覆盖 issue 里的例子时,打特例也能通过;要有 PASS_TO_PASS、差分检查、代码审查。
- “智能体比写死的流程更好。” Agentless 用三段流程打败了当时所有开源的智能体,而且便宜得多。
- “pass@k 很高,说明它很可靠。” pass@4 90.5% 的智能体,pass^4 只有 44.4%。
- “benchmark 上的每一个失败都是智能体的错。” 有的任务描述不充分、有的测试判错正确的解法(SWE-bench 里分别是 38.3% 和 61.1% 被标出)。
- “长任务交给压缩就行。” 压缩不一定能把清楚的指示传给下一个会话;要有特性清单、进度文件和 git。
- “CLAUDE.md 写得越详细越好。” 太长了重要的规则会被淹没;必须每次都执行的事用 hook。
- “有检查点就不用 git 了。” 检查点只追踪编辑工具的修改,不追踪 shell 命令。
11. 练习 · Exercises
练习 1(4 元组):为”给一个 Web 应用加一个’导出 CSV’的按钮”写一个编程任务的 4 元组,并说明 FAIL_TO_PASS 和 PASS_TO_PASS 各应该包含什么。
参考答案
请求:一段需求描述(在哪个页面、导出哪些列、文件名格式、空数据时怎样)。环境:仓库快照 + 能启动应用的容器(数据库种子数据)。停止条件:提交补丁或用完预算。判分器:FAIL_TO_PASS——一个端到端测试(打开页面、点按钮、下载的 CSV 列和行正确)+ 边界情况(空表、包含逗号和引号的字段要正确转义);PASS_TO_PASS——原有的全部测试(页面的其他功能、其他导出格式)。注意 FAIL_TO_PASS 不要只覆盖需求里举的那一个例子,否则打特例也能通过。练习 2(ACI 设计):你要为一个 Java 仓库设计编辑命令。参考 SWE-agent 的四条原则,你会怎样设计”编辑”和”编辑后的反馈”?
参考答案
动作简单:`edit ` + 新文本,或 search/replace(不用记行号,但要处理多处匹配)。护栏:编辑后对这个文件做增量编译 / 语法检查(javac 或语言服务器的诊断),新增的编译错误直接撤销并返回错误信息(文件名、行号、错误原文的前几行)。反馈简洁:成功时显示修改附近的窗口(带行号),而不是整个文件;失败时只显示新引入的错误,不显示原本就有的警告。再加一条:编辑失败时说明"文件没有被修改",免得智能体以为改坏了。练习 3(连锁失败):用本讲的数据解释,为什么”编辑后做语法检查”对没有语法检查时有编辑失败的运行影响这么大(0% vs 58%)。
参考答案
没有语法检查时,写错缩进的编辑被保存进文件;之后整个模块无法导入,复现脚本和所有测试都崩溃,报错信息指向 import 失败而不是"你上次的编辑"。智能体如果没意识到是自己的编辑造成的(模拟里以 p_notice 的概率意识到),就会认为"这个修法不对",转去别的行改,而坏掉的文件还在——之后的每一次尝试都失败,直到用完步数。有语法检查时,错误在发生的那一刻就被拦下、撤销,并且错误信息直接指出是这次编辑的哪一行,智能体重写一次即可。练习 4(智能体 vs 流程):你的团队每天收到 200 个小 bug 报告,大多数只涉及一两个函数。你会选智能体循环还是 Agentless 式的流程?怎样组合?
参考答案
先跑 Agentless 式的流程:便宜(本讲约 1/4.7 的 token)、可预测、容易并行和审计;对候选补丁用复现测试和回归测试验证。只有当没有候选通过验证时,再把任务交给智能体(本讲的接力把 63.2% 提到 83.2%)。同时记录哪些任务需要升级,分析原因(通常是定位失败),改进定位或把这类任务直接路由给智能体。最后都要有人审查合并。练习 5(作弊的补丁):你的评估发现某个智能体的 resolved 比例很高,但用户反馈修复经常”只对报告里的例子有效”。设计两种检测方法。
参考答案
① 差分 / 属性测试:把补丁后的函数和修复 PR 的实现(或参考实现)在大量扰动的输入上比较;或者写性质(例如"结果不超过上限""对称")随机测试。② 静态检查补丁:找"if 参数 == 常量: return 常量"这类模式、对测试文件的修改、新增的 try/except 吞掉异常。③ 加强 FAIL_TO_PASS:每个任务准备多组不同的输入,只公开其中一组作为 issue 的例子。④ 人工抽查一部分 resolved 的补丁(第 13 讲:读记录)。练习 6(说不清楚的任务):一个 issue 写着”round_price(2.675) 返回 2.67,应该是 2.68”。为什么这个任务可能”说不清楚”?你会怎样改 issue 或测试?
参考答案
有多种都能让这个例子通过、行为却不同的修法:改用十进制的四舍五入(ROUND_HALF_UP)、加一个很小的 epsilon、改成向上取整……它们在别的输入(例如 2.665、负数、恰好是 .5 的情况)上结果不同。如果隐藏测试检查的是其中一种特定的行为,其他同样满足 issue 的修法会被判错。改法:在 issue 里写清楚规则("金额按十进制、四舍五入、.5 向远离零的方向进")并给出几个边界例子;或者让测试只检查 issue 写明的行为。练习 7(pass@k):一个编程智能体 pass@1 = 60%、pass@5 = 85%。什么情况下你会报告 pass@5?什么情况下必须报告 pass@1 或 pass^k?
参考答案
pass@5 有意义的前提是有一个可靠的、不依赖隐藏测试的验证器,能从 5 次尝试里自动挑出对的那一个(例如完整的回归测试 + 复现测试,或者人只需要挑一个)——这时用户实际体验到的接近 pass@5。如果智能体只跑一次就交给用户(例如自动提交 PR),或者没有可靠的验证器,就报告 pass@1;如果关心"每次都靠得住"(例如在 CI 里无人值守地跑),报告 pass^k。无论哪种,都要说明算法和成本(5 次 = 5 倍的成本)。练习 8(长时间运行的 harness):你要让一个智能体在一周内把一个 Python 2 的项目迁移到 Python 3(2,000 个文件)。按本讲的方法设计 harness。
参考答案
初始化:生成"待迁移文件清单"(JSON,每个文件一条,`migrated: false`、`tests_pass: false`),写 `init.sh`(建虚拟环境、跑测试),初始提交,建进度文件。每个会话:读进度文件和 git 日志 → 跑冒烟测试(确认主分支是好的)→ 取下一批(例如同一个包里的 10 个文件)→ 迁移 → 跑这些文件相关的测试 → 通过才标 `migrated: true` 并提交 → 写进度(遇到的模式、坑)。不允许修改测试来让测试通过。可以并行:每个会话一个 worktree、一个包,最后合并(Claude Code 文档的 fan-out 模式:`claude -p` 加 `--allowedTools`)。练习 9(权限):为一个”每晚自动修复 lint 错误并开 PR”的无人值守编程智能体设计权限。
参考答案
在沙箱 / 容器里跑;只放行需要的工具:读写工作区的文件、运行 linter 和测试、`git commit`、开 PR 的命令(例如 `gh pr create`);禁止网络访问(除了推送到指定的远端)、禁止修改 CI 配置和测试文件、禁止推送到主分支;其他一切需要确认的动作一律拒绝(无人值守,没有人可以确认)。PR 必须经人审查合并。这是第 14 讲的最小权限 + Rule of Two:它读的是自己仓库的代码(相对可信),能改状态,但拿不到凭证之外的敏感数据、不能对外通信。练习 10(开放题:评估你的编程智能体):为你常用的编程智能体(或你自己写的)设计一个小评估:任务从哪来、怎样判分、报告哪些数字、怎样防止钻空子。
参考答案(思路)
任务:从你自己的仓库造(SWE-smith 式注入 bug + 你的测试),再加几个真实的历史 bug(从 git log 里找"fix"提交,回退修复、用修复带来的测试做 FAIL_TO_PASS)。判分:FAIL_TO_PASS + PASS_TO_PASS + 差分检查;在容器里跑,用原始的测试文件。报告:pass@1、pass^k(多跑几次)、成本(token、时间)、按失败原因分类(定位 / 编辑 / 验证 / 超时)、配对比较(换脚手架时)。防钻空子:隐藏测试多组输入、静态检查补丁、人工抽查、过滤说不清楚的任务。最后读一读失败的轨迹。12. 延伸阅读 · Further reading
本讲的阅读材料
- J. Yang, C. E. Jimenez, A. Wettig, K. Lieret, S. Yao, K. Narasimhan, O. Press, SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering, NeurIPS 2024.
- X. Wang et al., OpenHands: An Open Platform for AI Software Developers as Generalist Agents, ICLR 2025.
- Claude Code 文档:Best practices for Claude Code(code.claude.com/docs/en/best-practices)。
- J. Young, Effective Harnesses for Long-Running Agents, Anthropic Engineering, 2025-11-26.
- C. E. Jimenez et al., SWE-bench: Can Language Models Resolve Real-World GitHub Issues?, ICLR 2024.
相关
- C. S. Xia, Y. Deng, S. Dunn, L. Zhang, Agentless: Demystifying LLM-based Software Engineering Agents, 2024.
- OpenAI, Introducing SWE-bench Verified, 2024.
- J. Yang et al., SWE-smith: Scaling Data for Software Engineering Agents, 2025(第 11 讲 §5)。
- X. Wang et al., Executable Code Actions Elicit Better LLM Agents(CodeAct),ICML 2024.
13. 下一讲预告 · Next lecture
第 16 讲 主动式智能体(11/30)。到目前为止,所有的智能体都在等你开口:你提一个 issue,它去修;你问一个问题,它去查。下一讲讨论反过来的情形:智能体先猜你需要什么,再决定要不要、什么时候说出来。这需要一个关于你的模型:Shaikh et al. 的通用用户模型(GUM)从你用电脑的观察里推断出带置信度的命题;他们后续的下一动作预测(NAP)试图预测你接下来要做什么。有了用户模型,还要决定什么时候打扰你:这是 Horvitz 的混合主动原则,用期望效用权衡帮对了的好处、打扰错了的代价和你此刻的注意力。我们也会看开源的主动式个人助手(OpenClaw),以及隐私与信任:一个整天看着你屏幕的助手,知道什么、该说什么。配套代码会从模拟的行为日志里长出一个用户模型,做下一动作预测,并模拟”要不要打扰用户”的决策。