如何搭建一个长运行 Agent Harness
引言
过去一段时间,很多人对 AI Agent 的期待已经从“帮我写一段代码”,变成了“帮我把一个完整功能甚至一个完整应用做出来”。
这个期待并不过分。今天的模型已经能读代码、改代码、运行命令、调用浏览器、写测试、修 bug,也能在一次对话里完成相当复杂的开发任务。但只要你真的把它放进一个持续数小时的任务里,就会发现一个很现实的问题:模型能力变强以后,失败不再主要表现为“它完全不会做”,而是表现为“它做着做着失去工程秩序”。
它可能一开始写得很快,界面也很像样;但再往下推进,状态开始混乱,功能清单不完整,某些核心路径没有跑通,半成品被当成完成品,后续修复又引入新问题。最后你得到的不是一个可交付应用,而是一个看起来接近完成、实际需要大量人工收尾的项目。
Anthropic 关于 long-running agents 的两篇工程博客,真正值得学习的地方就在这里:它们没有只讨论“模型有多强”,而是把问题推进到了工程系统层面。也就是说,长时间运行的 Agent 需要 Harness,需要一套外部流程来帮它拆任务、保存状态、验证结果、引入评审,并在失败后回到正确轨道。
本文会用一个完整实战案例,把这套 Harness 的设计方法讲清楚。
案例目标:
做一个个人任务看板,支持任务创建、状态流转、截止日期、搜索筛选和基础统计。
不会一上来就让 Agent 直接写代码,而是搭出一个可长期运行的开发 Harness。
模型变强了,瓶颈却换了位置
早期使用 AI 写代码时,瓶颈很直接:模型经常不会写、写不对、理解不了项目结构。所以我们主要关心提示词怎么写、上下文怎么喂、代码片段怎么补。
但到了更强的编码模型阶段,瓶颈发生了迁移。
很多时候,模型已经能完成单个局部任务。它会实现一个组件,会写一个接口,会修一个报错,也会根据错误日志继续调试。真正困难的是:当任务跨度变长、功能数量变多、上下文不断变化时,它能不能持续保持正确方向。
长运行任务里的核心瓶颈,通常变成了这些:
- 目标管理:它是否始终知道最终要交付什么。
- 范围控制:它是否能一次只推进一个可闭环的小目标。
- 状态交接:新一轮上下文是否能准确理解上一轮进度。
- 质量判断:它是否能发现“看起来完成但实际没通”的问题。
- 恢复能力:当一轮实现失败时,它是否能回到稳定状态继续修。
所以,模型越强,越不能只把它当成一个聊天窗口。它更像一个能力很强、但仍需要工作流约束的工程师。
Harness 的作用,就是把这些工作流约束外部化:用规格文件承载目标,用功能清单承载验收项,用进度记录承载交接,用 Sprint Contract 限制范围,用独立 Evaluator 承担 QA,用 git commit 保留可恢复状态。
也就是长运行 Agent 的竞争力,不只来自模型本身,也来自模型外面的工程系统。
1. What Harness
Harness 可以理解为 Agent 外面的一层“工程约束系统”。
裸 Agent 的工作方式通常是:
用户给一句需求
Agent 开始写代码
Agent 写了一堆文件
Agent 自己判断完成
用户打开后发现一堆核心路径没通
长运行 Harness 的工作方式是:
用户给一句需求
Initializer 搭好环境和交接文件
Planner 把需求扩展成产品规格
Builder 每次只实现一个明确功能
Evaluator 像真实用户一样验收
失败就回到 Builder 修复
通过后再进入下一个功能
它解决的不是“模型不会写代码”,而是这些更实际的问题:
- Agent 想一次做太多,最后留下半成品。
- 上下文越来越长后,Agent 忘记早期目标。
- 新一轮 Agent 不知道上一轮做到哪里。
- 功能没真实跑通,就被标记成完成。
- Agent 自评过于乐观,明明有 bug 也会说“整体可用”。
Anthropic 两篇文章的核心可以浓缩成一句话:
长任务不能只依赖模型记忆和自觉,必须把目标、进度、验证和评审外部化。
2. 实战案例:个人任务看板 TaskFlow
我们要构建的应用叫 TaskFlow。
它不是一个复杂 SaaS,但足够覆盖长运行 Agent 的典型问题:
- 有多个功能模块。
- 有 UI 交互。
- 有状态持久化。
- 有端到端验收路径。
- 容易出现“看起来做了,实际没通”的假完成。
最终用户应该能做到:
- 创建任务。
- 编辑和删除任务。
- 在“待办、进行中、已完成”之间切换任务状态。
- 给任务设置截止日期。
- 搜索任务。
- 按状态和日期筛选任务。
- 查看总任务数、已完成数、逾期数、完成率。
这个需求如果直接丢给一个 Agent,它很可能会先做一个漂亮界面,然后遗漏持久化、边界校验、筛选联动或统计准确性。
所以我们先搭 Harness。
3. 创建工作区结构
建议先创建这样的目录:
taskflow-agent-harness/
app/
harness/
SPEC.md
feature_list.json
progress.md
sprint_contract.md
qa_report.md
prompts/
initializer.md
planner.md
builder.md
evaluator.md
每个文件的作用如下:
| 文件 | 作用 |
|---|---|
SPEC.md |
产品规格,说明要做什么、用户路径是什么 |
feature_list.json |
可验证功能清单,每项都有验收步骤和通过状态 |
progress.md |
长期交接记录,告诉下一轮 Agent 做到哪里 |
sprint_contract.md |
当前 Sprint 的完成约定 |
qa_report.md |
Evaluator 的验收报告 |
prompts/initializer.md |
初始化 Agent 提示词 |
prompts/planner.md |
规划 Agent 提示词 |
prompts/builder.md |
开发 Agent 提示词 |
prompts/evaluator.md |
验收 Agent 提示词 |
这里最重要的是:这些文件不是文档摆设,而是 Agent 的外部记忆。
每轮 Agent 开始前先读它们,每轮结束后更新它们。
4. 第一步:Initializer 先搭现场,不急着开发
Initializer 的任务是搭建长期可接力的工作现场。
它不应该一开始就实现所有功能。它应该先创建项目骨架、功能清单、运行脚本和交接文件。
可以把下面内容保存为 harness/prompts/initializer.md:
# Initializer Agent Prompt
你是 Initializer Agent,负责为长时间运行的软件开发任务搭建工作环境。
用户原始需求:
做一个个人任务看板,支持任务创建、状态流转、截止日期、搜索筛选和基础统计。
你的任务:
1. 创建 app/ 目录,并初始化一个最小可运行的前端项目。
2. 创建 harness/SPEC.md,扩展完整产品规格。
3. 创建 harness/feature_list.json,将需求拆成可端到端验证的功能项。
4. 创建 harness/progress.md,记录初始化结果和后续工作规则。
5. 创建 harness/sprint_contract.md 空模板。
6. 创建 harness/qa_report.md 空模板。
7. 初始化 git 仓库并提交初始状态。
约束:
- 不要一次性实现全部功能。
- feature_list.json 中每个功能都必须包含 id、description、steps、passes。
- 所有 passes 初始值必须为 false。
- 后续 Agent 只能在真实验证通过后,把 passes 从 false 改成 true。
- 不要删除 feature_list.json 中的功能项。
Initializer 生成的 feature_list.json 应该像这样:
[
{
"id": "task-create",
"category": "functional",
"description": "用户可以创建新任务",
"steps": [
"打开任务看板页面",
"点击新建任务按钮",
"输入标题、描述和截止日期",
"点击保存",
"确认任务出现在待办列表",
"刷新页面后确认任务仍然存在"
],
"passes": false
},
{
"id": "task-status-change",
"category": "functional",
"description": "用户可以改变任务状态",
"steps": [
"创建一个新任务",
"将任务从待办移动到进行中",
"将任务从进行中移动到已完成",
"刷新页面",
"确认任务仍然处于已完成状态"
],
"passes": false
},
{
"id": "task-search-filter",
"category": "functional",
"description": "用户可以搜索和筛选任务",
"steps": [
"创建多个不同标题和状态的任务",
"输入关键词搜索",
"确认只显示匹配任务",
"选择状态筛选",
"确认搜索和状态筛选可以同时生效"
],
"passes": false
}
]
注意,功能清单里不要只写“支持任务管理”。要写成真实用户可以执行的步骤。
5. 第二步:Planner 把一句话扩展成产品规格
Planner 的价值是防止 Builder 低估需求。
如果没有 Planner,Agent 很容易直接开写,最后做出一个“有按钮、有卡片、有三列”的浅层 Demo。Planner 要先把产品目标、用户路径、阶段规划和验收边界讲清楚。
保存为 harness/prompts/planner.md:
# Planner Agent Prompt
你是 Planner Agent,负责把用户的简短需求扩展成可执行产品规格。
开始前必须阅读:
- harness/SPEC.md
- harness/feature_list.json
- harness/progress.md
你的任务:
1. 检查 SPEC.md 是否完整覆盖用户目标。
2. 将产品拆成 3 到 5 个开发阶段。
3. 每个阶段都要有可验证的用户价值。
4. 补充遗漏的功能清单项,但不要过度设计。
5. 不要规定过细的技术实现,除非它影响产品行为。
6. 更新 progress.md,说明你做了哪些规划变更。
输出要求:
- SPEC.md 中必须包含产品目标、核心用户路径、非目标、开发阶段。
- feature_list.json 中每个功能都必须可以端到端验证。
Planner 产出的阶段规划可以是:
## 开发阶段
### Phase 1:基础任务 CRUD
- 创建任务
- 编辑任务
- 删除任务
- 本地持久化
### Phase 2:看板状态流转
- 待办、进行中、已完成三列
- 任务可在状态之间移动
- 状态刷新后保留
### Phase 3:搜索、筛选和排序
- 按关键词搜索
- 按状态筛选
- 按截止日期排序
- 搜索和筛选可以组合使用
### Phase 4:统计视图
- 总任务数
- 已完成任务数
- 逾期任务数
- 完成率
Planner 不需要把每个 React 组件怎么写都指定出来。太细的技术规格反而会把错误提前固化。
它应该把“做什么”和“做到什么程度算完成”讲清楚。
6. 第三步:Builder 每次只做一个小闭环
Builder 是真正写代码的 Agent。
但它不能随便写。它每一轮都要遵循固定流程:
读交接记录
读功能清单
读产品规格
启动项目
做基础冒烟测试
选择一个未完成项
写 Sprint Contract
实现功能
自测
通过后更新 passes
写 progress
提交 git
保存为 harness/prompts/builder.md:
# Builder Agent Prompt
你是 Builder Agent,负责实现 TaskFlow 应用。
开始前必须:
1. 读取 harness/progress.md。
2. 读取 harness/SPEC.md。
3. 读取 harness/feature_list.json。
4. 查看最近 git log。
5. 启动应用,并确认基础页面可以打开。
工作规则:
- 每轮只实现一个功能,或者一个紧密相关的小功能组。
- 开发前必须更新 harness/sprint_contract.md。
- sprint_contract.md 必须写清楚本轮目标、包含范围、不包含范围、完成标准、验证步骤。
- 如果 harness/qa_report.md 显示上一轮失败,本轮只能修复失败项,不能开发新功能。
- 只有真实验证通过后,才能把 feature_list.json 对应 passes 改为 true。
- 每轮结束必须更新 progress.md。
- 每轮结束必须 git commit,提交信息要说明本轮完成了什么。
不要做:
- 不要删除 feature_list.json 的功能项。
- 不要把未测试功能标记为通过。
- 不要在一个 Sprint 中顺手做大量无关功能。
- 不要因为 UI 看起来完成就跳过端到端验证。
一个好的 sprint_contract.md 示例:
# Sprint Contract
## 本轮目标
实现任务创建功能。
## 包含范围
- 新建任务按钮
- 创建任务表单
- 标题、描述、截止日期字段
- 标题必填校验
- 保存后任务出现在待办列
- 刷新页面后任务仍然存在
## 不包含范围
- 编辑任务
- 删除任务
- 拖拽状态流转
- 搜索筛选
- 统计视图
## 完成标准
- 用户可以通过 UI 创建任务。
- 空标题不能保存。
- 保存后页面立即显示新任务。
- 截止日期显示在任务卡片上。
- 刷新后任务仍然存在。
## 验证步骤
1. 打开应用首页。
2. 点击新建任务。
3. 不填写标题直接保存,确认出现校验提示。
4. 填写标题、描述和截止日期。
5. 保存任务。
6. 确认任务出现在待办列。
7. 刷新页面。
8. 确认任务仍然存在。
Sprint Contract 的作用是把“实现任务创建”这种模糊目标,变成一组可检查的行为。
7. 第四步:Evaluator 不写代码,只负责验收
Evaluator 是独立 QA。
它不能因为页面漂亮就放行,也不能被 Builder 的解释说服。它只看实际行为。
保存为 harness/prompts/evaluator.md:
# Evaluator Agent Prompt
你是 Evaluator Agent,负责验收 Builder 的工作。
开始前必须阅读:
- harness/SPEC.md
- harness/feature_list.json
- harness/progress.md
- harness/sprint_contract.md
你的任务:
1. 启动应用。
2. 像真实用户一样操作页面。
3. 严格按照 sprint_contract.md 的完成标准逐项验证。
4. 检查是否存在核心路径失败、状态错误、刷新后丢失、UI 不可理解等问题。
5. 输出 harness/qa_report.md。
评审原则:
- 不要因为代码存在就判定功能完成。
- 不要因为 UI 看起来不错就判定功能完成。
- 如果核心用户路径失败,本轮必须失败。
- 如果功能只是 stub、假数据或展示壳,本轮必须失败。
- 反馈必须具体到可修复的问题。
qa_report.md 必须包含:
- 结论:通过或失败
- 验证过的步骤
- 失败项
- 通过项
- 修复建议
一次失败的 QA 报告可能是:
# QA Report
## 结论
失败。
## 验证过的步骤
1. 打开首页。
2. 点击新建任务。
3. 测试空标题保存。
4. 创建包含标题、描述、截止日期的任务。
5. 刷新页面检查数据是否保留。
## 失败项
1. 空标题任务仍然可以保存。
2. 创建任务后刷新页面,任务消失。
3. 截止日期没有显示在任务卡片上。
## 通过项
1. 新建任务按钮可以打开表单。
2. 表单可以输入标题、描述、截止日期。
3. 保存后任务会临时出现在待办列。
## 修复建议
优先修复本地持久化,其次修复表单校验和任务卡片字段展示。
这个报告会直接决定 Builder 下一轮做什么。
8. 第五步:失败后不要开新功能,只修当前问题
这是很多 Agent 任务失败的关键点:一轮没通过,Agent 却继续做下一个功能。
Harness 要明确规定:
如果 QA 失败,下一轮 Builder 只能修复 QA 报告中的失败项。
修复轮的 Builder 提示可以这样写:
你是 Builder Agent。
harness/qa_report.md 显示上一轮没有通过。
本轮任务:
1. 只修复 qa_report.md 中列出的失败项。
2. 不开发新功能。
3. 不扩大范围。
4. 修复后重新按照 sprint_contract.md 自测。
5. 只有全部通过,才更新 feature_list.json 的 passes。
6. 更新 progress.md。
7. git commit。
这样开发循环就变成:
写 Contract
实现功能
QA 验收
失败则修复
复验通过
进入下一个功能
这个闭环比“让 Agent 一直写”稳定得多。
9. 一次完整运行示例
假设我们按上面的 Harness 跑 TaskFlow,合理的轮次如下:
第 0 轮:Initializer
创建项目、规格、功能清单、进度文件、初始提交。
第 1 轮:Planner
补全产品规格,规划 4 个开发阶段。
第 2 轮:Builder
实现任务创建。
第 3 轮:Evaluator
验收任务创建,发现刷新后数据丢失。
第 4 轮:Builder
修复本地持久化和表单校验。
第 5 轮:Evaluator
复验任务创建,通过。
第 6 轮:Builder
实现任务编辑和删除。
第 7 轮:Evaluator
验收编辑删除。
第 8 轮:Builder
实现状态流转。
第 9 轮:Evaluator
验收状态流转和刷新后状态保留。
这个流程看起来慢,但它换来的是稳定。
长任务真正浪费时间的地方,通常不是“多写了几份 Markdown”,而是 Agent 在错误状态上继续堆代码,最后整个项目难以收拾。
10. 关键设计:为什么要用 JSON 做功能清单
Anthropic 的文章里提到,他们实验后更偏向用 JSON 存功能清单。
原因很简单:模型更不容易随手改坏 JSON。
Markdown 对人友好,但对 Agent 来说太容易“顺手重写”。它可能会把未完成项删掉,把验收步骤简化,或者把失败项改成看起来完成。
JSON 更适合做状态文件。
推荐字段:
{
"id": "task-create",
"priority": "P0",
"category": "functional",
"description": "用户可以创建新任务",
"steps": [
"打开任务看板页面",
"点击新建任务按钮",
"输入任务信息",
"点击保存",
"确认任务出现",
"刷新后确认任务仍然存在"
],
"passes": false
}
并且要在提示词里强约束:
只能修改 passes 字段来标记完成。
不能删除测试项。
不能简化验收步骤。
不能把没有真实验证的功能标为 true。
这类约束看起来啰嗦,但对长任务很有用。
11. 主观质量也可以被 Harness 化
功能可以测试,但设计质量、产品手感、交互流畅度怎么办?
Anthropic 的第二篇文章给了一个重要方法:不要问“好不好看”,而是拆成可评分标准。
对 TaskFlow,可以给 Evaluator 增加这几个维度:
## 产品与设计评分维度
### 功能完整性
核心用户路径是否真的可用,而不是只有静态展示。
### 信息架构
用户是否能快速理解任务在哪、下一步可以做什么。
### 交互效率
创建、移动、筛选任务是否顺手,是否需要多余步骤。
### 视觉清晰度
状态、优先级、截止日期、逾期信息是否容易辨认。
### 原创性与克制
界面是否避免模板化堆卡片、默认组件感、过度渐变和无意义装饰。
这样 Evaluator 就不会只检查“按钮能不能点”,还会检查“这个应用是否真的像一个可用产品”。
主观质量不能完全自动化,但可以被结构化地改进。
12. 什么时候需要完整多 Agent,什么时候不需要
Harness 不是越复杂越好。
你可以用这个标准判断:
| 任务类型 | 推荐 Harness |
|---|---|
| 改一个按钮、修一个小 bug | 单 Agent 即可 |
| 实现一个页面或一个模块 | Builder + feature_list + progress |
| 多模块功能、需要持续开发 | Initializer + Planner + Builder |
| 完整应用、质量要求高 | Initializer + Planner + Builder + Evaluator |
| 有大量 UI 和端到端交互 | 必须加入浏览器自动化验收 |
| 主观质量很重要 | 加入评分标准和独立 Evaluator |
每个 Harness 组件都是一个假设:
- 需要 Planner,说明模型容易低估需求。
- 需要 Sprint,说明模型容易一次做太多。
- 需要 Evaluator,说明模型自评不可靠。
- 需要 progress 文件,说明跨会话状态容易丢。
- 需要 git commit,说明要能恢复到稳定点。
当模型能力提升后,要重新检查这些组件是否仍然必要。
好的 Harness 不是堆复杂度,而是只保留真正承重的结构。
13. 可以直接复用的最小模板
如果你只想快速落地,可以先用这个最小版本:
harness/
SPEC.md
feature_list.json
progress.md
sprint_contract.md
qa_report.md
每轮 Builder 固定执行:
1. 读 progress.md
2. 读 SPEC.md
3. 读 feature_list.json
4. 选一个 passes=false 的功能
5. 写 sprint_contract.md
6. 实现
7. 自测
8. 更新 progress.md
9. git commit
每轮 Evaluator 固定执行:
1. 读 sprint_contract.md
2. 启动应用
3. 按真实用户路径操作
4. 写 qa_report.md
5. 通过才允许进入下一个功能
这是最小可用 Harness。
如果任务变复杂,再加入 Planner、多轮 QA、设计评分、自动化测试和更严格的状态文件。
14. 把 Agent 当作团队,而不是聊天窗口
长运行 Agent 的本质,不是一个更长的聊天窗口,而是一个迷你软件团队。
可以这样理解:
| Harness 角色或文件 | 对应真实团队能力 |
|---|---|
| Planner | 产品经理 + 架构师 |
| Builder | 工程师 |
| Evaluator | QA + Code Reviewer |
| SPEC.md | 产品规格 |
| feature_list.json | 验收清单 |
| sprint_contract.md | Sprint 交付契约 |
| progress.md | 交接记录 |
| git commit | 可恢复工作点 |
Agent 不是不能做长任务,而是不能在没有流程的情况下稳定做长任务。
所以 Harness 的核心不是“让 Agent 看起来更复杂”,而是让它:
- 更难跑偏。
- 更难假完成。
- 更容易接着做。
- 更容易被验收。
- 更容易从错误中恢复。
最后可以记住这一句:
不要期待 Agent 长期自己保持清醒。用 Harness 把清醒变成流程。
15. Harness 会随模型一起“瘦身”
最后还要补一个很重要的判断:Harness 不是固定形态,它会随着模型能力一起变化。
在 Anthropic 的实验里,Opus 4.5 时代的问题更明显:长上下文下容易出现 context 焦虑,所以需要拆 Sprint,需要 context reset,也需要更强的外部评审来对抗自评偏见。这个阶段的 Harness 会比较厚,因为它要补模型当前不稳定的地方。
到了 Opus 4.6,模型的规划能力和长上下文能力变强,一些原本承重的结构就可以被拆掉。比如文章里提到,Sprint 这个构件在某些任务上可以直接移除,Harness 变得更简单,但质量并没有下降。
这背后的原则是:
先找最简单的方案,需要时才增加复杂度。每个 Harness 部件,都是对“模型当前短板”的一个假设;模型一升级,就要回头拆掉不再承重的部分。
所以不要把 Planner、Builder、Evaluator、Sprint、context reset 当成永远必须存在的标准答案。它们都是工程工具,不是信仰。
更好的做法是定期问自己几个问题:
- Planner 还在显著提升需求完整度吗?
- Sprint 拆分还在减少失控,还是已经变成额外开销?
- Evaluator 还能抓到 Builder 自己抓不到的问题吗?
- context reset 还必要吗,还是自动 compaction 已经足够?
- 这套 Harness 的成本,是否仍然匹配它带来的质量收益?
模型越弱,Harness 往往越厚;模型越强,Harness 不一定消失,但会变轻、变窄、变得更有针对性。
最终成熟的 Agent 工程,不是堆出最复杂的多 Agent 系统,而是持续找到当前模型和当前任务之间最小但有效的支撑结构。