📑 本页目录(点开跳转)
12 · Harness:长时任务的骨架
⏱ 28 分钟 | ⭐⭐⭐ 本教程最重要的一节
🎯 一句话
Harness = 把原始模型智能变成可用 Agent 的那一整套东西(循环 + 工具 + 上下文管理 + 护栏)。 而这一节最重要的一句话是:
「Harness 的每个组件都编码了一个『模型自己做不到某事』的假设—— 而这些假设会随模型进步而过时。」
🧠 一、长任务为什么会失败
让 Agent 连跑几小时到几天(做完整 App、迁移代码库),朴素做法必撞的五头怪:
| 失败模式 | 表现 | 根因 |
|---|---|---|
| 过度野心 | 想在一个上下文窗口干完全部,留下一堆半成品和破损状态 | 没有分解机制 |
| 过早宣告完成 ⭐ | 下个会话看到部分功能能跑,就宣布"项目完成!" | 没有客观完成标准 |
| 上下文焦虑 | 感知到接近 token 上限,开始草草收尾、偷工减料 | 模型的自我保护行为 |
| 目标漂移 | 跑几十轮后,做的事和最初目标越偏越远 | 目标没有被反复重申 |
| 状态丢失 | 会话重启后不知道做到哪,重复劳动或互相破坏 | 状态只存在上下文里 |
所有 harness 设计都是在对付这五头怪。 记住它们,你就知道每个组件为什么存在。
🏗️ 二、Harness 的四个组成
- 什么时候继续、什么时候停、怎么恢复
- 它能碰到什么(第 7 章)
- 压缩、外置状态、子 Agent(第 4 章)
- max_steps、超时、预算、权限(第 15 章)
💡 模型生成文本,harness 决定这些文本能触碰什么。
📉 三、Harness 减法的三种模式
核心心态:harness 设计不只是「我还该加什么」,更重要的是「我可以停止做什么」。
模式一:依靠模型,而非 harness
用 Claude 已深度掌握的【通用工具】,而不是发明专用工具
📌 Claude 3.5 Sonnet 仅用 bash + 文本编辑器
就在 SWE-bench Verified 上达到 49%
—— Claude Code 本身也主要靠这两个工具
💡 为什么通用工具更好:模型在训练中见过海量 bash/文件操作的例子, 而你发明的
my_custom_edit_tool它一次都没见过。熟悉度本身就是能力。
模式二:给 harness 做减法
| 让 Claude 自己做什么 | 怎么做 | 实测效果 |
|---|---|---|
| 编排自己的动作 | 给代码执行工具,让它自行过滤/管道化输出 | BrowseComp 45.3% → 61.6% |
| 管理自己的上下文 | Skills 渐进式披露 + context editing | 子 Agent 再加 2.8% |
| 持久化自己的上下文 | 压缩 + 记忆文件夹 | BrowseComp-Plus 60.4% → 67.2% |
模式三:谨慎设置边界
| 边界 | 说明 |
|---|---|
| 缓存友好的上下文设计 | 稳定内容在前、动态在后;不要中途换模型(缓存是模型专属的) |
| 声明式工具服务于安全与可观测 | bash 杠杆大但可观测性差;专用工具的类型化参数便于拦截和记录 |
| 可逆性准则 ⭐ | 难以撤销的动作值得用户确认 |
🧬 四、长时运行架构的三代演进
这是本章最有价值的部分——它展示了 harness 是怎么随模型进步而变化的。
第一代:初始化 Agent + 编码 Agent
要解决:过度野心 + 过早宣告完成
【初始化 Agent】(只跑第一次)
├─ 生成 init.sh 启动脚本
├─ 生成 claude-progress.txt 进度文件
├─ 初始 git commit
└─ ⭐ JSON 格式的完整功能清单
(示例项目 200+ 项,初始全标 failing)
【编码 Agent】(后续所有会话)
├─ 一次只做一个功能
├─ 每次变更都提交
└─ 宣布完成前严格端到端测试
两个关键细节:
| 细节 | 为什么 |
|---|---|
| 用 JSON 而非 Markdown ⭐ | 模型更不容易随意修改或覆盖 JSON 文件(格式刚性提供保护) |
| 像人一样测试 | 给浏览器自动化工具,让 Agent 像真人用户一样点击验证——能发现纯代码审查发现不了的 bug |
第二代:GAN 式生成-评估分离
核心洞察:
⭐ 模型评估自己的工作时有系统性正面偏差,主观任务上尤其明显。 把生成与评估分离,远比让生成者自我批判有效。
【规划 Agent】把 1–4 句需求扩展成详细产品规格
↓
【生成 Agent】按 Sprint 迭代实现,交 QA 前先自检
↓
【评估 Agent】通过浏览器自动化测试运行中的应用
↑ 实现前先协商「Sprint 契约」定义成功标准
⭐ 让主观评估可行的关键技巧:
❌ 「这个设计美吗?」 ← 无法回答,评估会流于形式
✅ 「这个设计是否符合以下四项标准?」
· 设计质量 · 原创性 · 工艺 · 功能性
💡 把主观判断【转化成可回答的具体条目】——这是所有 LLM 评估的通用技巧
成本对比(复古游戏制作器案例):
| 方案 | 耗时 | 成本 | 结果 |
|---|---|---|---|
| 单 Agent | 20 分钟 | $9 | 玩法损坏、流程僵硬、输入不可用 |
| 完整 harness | 6 小时 | $200 | 10 个 Sprint 覆盖 16 项功能、UI 精致、游戏可玩 |
💡 20 倍成本买的是「能用 vs 不能用」,不是「好 vs 更好」。
⚠️ 但要注意演进:对某一代模型必不可少的 Sprint 级分解, 到下一代反而成了多余开销。
🔑 「有用的 harness 设计空间不会随模型进步而缩小,而是会移动。」
第三代:模型自己写 harness
Claude 可以为当前任务现场写出专属的多 Agent 系统(JavaScript 文件 + 派生子 Agent 的函数)。
要对抗的三种失败:
| 失败 | 说明 |
|---|---|
| Agent 惰性 | 提前收工 |
| 自我偏好偏差 | 偏袒自己的结果 |
| 目标漂移 | 多轮后原始目标失真 |
六种常用模式:分类后执行、扇出后综合、对抗式验证、生成后过滤、锦标赛、循环到完成。
⚠️ 什么时候别用: - 没有真正并行需求的常规编码 - 不值得多 Agent 协调开销的任务 - 一个上下文窗口就能解决的问题
🧰 五、可以照抄的最小 harness
# 一个能对付"五头怪"的最小骨架
class MinimalHarness:
def __init__(self, model, tools, goal):
self.goal = goal
self.state_file = "progress.json" # ⭐ 对付"状态丢失"
self.max_steps = 50 # ⭐ 护栏
self.token_budget = 100_000
def run_session(self):
state = self.load_state() # 从文件恢复,不靠上下文
# ⭐ 对付"目标漂移":每个会话开头重申目标 + 当前状态
system = f"""目标:{self.goal}
当前进度:{state['done']} / {state['total']} 项已完成
下一项:{state['next_task']}
规则:
1. 一次只做【一个】功能项,做完立即更新 progress.json 并提交
2. 宣布完成前必须运行验证脚本并通过 ← 对付"过早宣告完成"
3. 不要开始新功能,直到当前这个通过验证 ← 对付"过度野心"
"""
for step in range(self.max_steps):
if self.tokens_used > self.token_budget * 0.75:
self.compact() # ⭐ 对付"上下文焦虑"
...
self.save_state(state) # 会话结束前落盘
这个骨架里每一行都对应一头怪——这就是 harness 设计的思维方式。
⭐
progress.json有个正式名字:工作记忆(working memory)。 它是04b · Agent 记忆四层结构里的一层——只服务于当前这个任务,任务做完就该整个丢掉。 知道它是「哪一层」很有用,因为四层的写入和淘汰规则完全不同,混在一起就会烧钱或者出错:
这里的东西 是哪一层 任务结束后 progress.json里的next_task/done工作记忆 ⭐ 整个丢掉,留着只会污染下一个任务 「这个项目用 pnpm 不用 npm」 长期记忆 留下,下次开工直接生效 「上次改这个文件把 CI 跑挂了」 情景记忆 留下,是下次的避坑依据 ⚠️ 最常见的错法是把三者全塞进同一个
progress.json——文件越滚越大, 每个会话开头都要整个读回上下文,Token 成本随任务时长线性上涨,而其中大部分早就没用了。 04b 讲的就是怎么分层、怎么判断一条该不该写、以及过期的怎么忘掉。
🔁 六、Harness 的定期体检
每次模型升级后,问这五个问题:
① 哪些提示词是为了绕过旧模型的缺陷写的? → 试着删掉,跑评测
② 哪些专用工具可以换成通用工具? → 模式一
③ 哪些编排逻辑可以交给模型自己做? → 模式二
④ 哪些分解粒度可以放粗? → Sprint 级 → 功能级
⑤ 哪些护栏还有必要? → 但安全护栏别乱删 ⚠️
⚠️ 注意第 ⑤ 条:功能性的变通可以删,安全边界不能删(第 15 章)。 「模型现在更聪明了所以不用沙箱了」是错误的推论—— 安全护栏防的是外部攻击,不是模型能力不足。
🕳️ 七、六个常见坑
| 坑 | 症状 | 修 |
|---|---|---|
| 状态只存在上下文里 ⭐ | 会话一断全丢 | 外置到文件(JSON) |
| 没有客观完成标准 | Agent 说完成了其实没有 | 功能清单 + 验证脚本 |
| 目标只在第一条消息里说过 | 跑几十轮后跑偏 | 每个会话开头重申 |
| harness 从不做减法 | 越堆越复杂,模型升级后成累赘 | 定期体检五问 |
| 让生成者自评 | 永远说自己做得好 | 生成-评估分离 |
| 主观标准无法执行 | 「做得好看点」→ 评估流于形式 | 转成可回答的具体条目 |
📚 延伸阅读
👉 这一节对应的框架:LangGraph 主攻的正是这里最难的半截 —— 状态怎么外置、断了怎么接着跑。对照关系在 16b · 框架这层皮。
✅ 检查点
- Harness 是哪四部分组成的?
- 长任务失败的五头怪是什么?
- 本章最重要的那句话是什么?它的实践含义是什么?
- 减法的三种模式分别是什么?
- 为什么长时任务的状态文件推荐 JSON?
- 为什么要把生成和评估分离?
- 「这个设计美吗」该怎么改写才能让评估可行?
- 模型升级后哪些 harness 组件可以删,哪些不能?
👀 答案
- 循环、工具、上下文管理、护栏。
- 过度野心、过早宣告完成、上下文焦虑、目标漂移、状态丢失。
- 「harness 的每个组件都编码了一个『模型做不到某事』的假设,而假设会过时」。含义:要定期问「我可以停止做什么」,而不只是「还该加什么」。
- ①依靠模型而非 harness(用通用工具如 bash)②给 harness 做减法(让模型自己编排/管理/持久化上下文)③谨慎设置边界(缓存友好、声明式工具、可逆性准则)。
- 模型更不容易随手修改或覆盖 JSON——格式刚性提供了保护。Markdown 太"软"容易被整体重写。
- 模型自评有系统性正面偏差,主观任务尤其明显。分离比让生成者自我批判有效得多。
- 转成可回答的具体条目:「是否符合以下四项标准:设计质量/原创性/工艺/功能性」。
- 功能性的变通可以删(为旧模型缺陷写的提示、过细的分解);安全边界不能删——它防的是外部攻击,不是模型能力不足。
🛑 可以停在这里
⚡ 走神救援
Harness=循环+工具+上下文管理+护栏。⭐核心命题:每个组件都编码了一个「模型自己做不到某事」的假设,而假设会随模型进步过时 → 要定期问「我可以停止做什么」。五头怪:过度野心、过早宣告完成、上下文焦虑(快到上限就草草收尾)、目标漂移、状态丢失。减法三模式:①用通用工具而非自造——熟悉度本身就是能力,Claude 3.5 Sonnet只靠bash+文本编辑器就在SWE-bench Verified拿49%;②让模型自己编排/管理/持久化上下文——BrowseComp 45.3%→61.6%、子Agent再加2.8%、BrowseComp-Plus 60.4%→67.2%;③谨慎设边界——缓存友好(⚠️不要中途换模型)、可逆性准则。三代架构:一代=初始化Agent(JSON功能清单200+项、初始全标failing)+编码Agent(一次一个功能、每次变更就提交、完成前端到端测试);⭐用JSON不用Markdown,格式刚性让模型不容易覆盖;像真人一样点一遍能发现纯代码审查发现不了的bug。二代=生成-评估分离,因为⭐模型自评有系统性正面偏差;⭐把「这个设计美吗」改成「是否符合设计质量/原创性/工艺/功能性四项标准」;单Agent 20分钟$9但产物不可用,完整harness 6小时$200、覆盖16项功能——20倍买的是「能用vs不能用」。三代=模型自己写harness。⚠️上一代必不可少的Sprint级分解,下一代反成多余开销——设计空间不会缩小,只会移动。升级后做体检五问(删旧提示、专用换通用、编排交还模型、粒度放粗),⚠️但安全护栏不能删:它防的是外部攻击,不是模型能力不足。
下一节 👉 13-多Agent协作.md