一、这篇文章解决什么问题
如果你已经开始让大模型参与真实项目开发,很快就会遇到几个非常现实的问题:
- 单次会话上下文不够,模型做着做着就忘了前面约束。
- 多个会话同时改同一个仓库,互相踩状态。
- 模型会“以为自己完成了”,但没有留下可验证证据。
- 任务跨天、跨窗口、跨模型以后,状态很难接力。
- 环境没起好、配置没配对、服务没就绪时,AI 会在错误基础上继续工作。
这类问题本质上不是“提示词不够强”,而是你缺少一个真正的 Harness。
这里的 Harness,不是某个特定产品名,也不是某个 Agent 框架的专属功能,而是一套围绕 AI 长时间工程执行而设计的:
- 工作目录隔离机制
- 状态真相源
- 会话互斥机制
- 环境预检机制
- 任务选择与验证机制
- 可恢复的接力协议
它的目标不是让 AI “看起来更聪明”,而是让 AI 在真实工程里 更稳定、更少返工、更容易接管。
二、什么是长跑型 AI 开发 Harness
一句话定义:
长跑型 AI 开发 Harness,就是给 AI 编码过程加上一层工程操作系统,让它不再依赖单次聊天记忆,而是依赖可落盘、可恢复、可验证的外部状态运行。
它通常具备 6 个核心特征:
- 专用工作空间:AI 不直接在主工作树乱改,而是在专用 worktree 或隔离目录工作。
- 显式状态文件:任务、进度、决策、证据都写入磁盘,而不是只留在对话里。
- 互斥锁:同一份任务状态同一时间只能被一个活动会话占用。
- 环境门禁:没有通过预检,就不允许进入实现阶段。
- 一步一验:每个最小增量都要验证、留证、回写。
- 可接力:任何时间中断,下一轮都能从状态文件恢复,而不是重新靠人解释。
三、设计目标与非目标
3.1 设计目标
一个合格的 Harness,至少要满足以下目标:
目标 1:防止 AI 在错误环境上继续工作
例如:
- 不在 Git 仓库里
- 不在专用 worktree
- 依赖服务没启动
- 必填环境变量缺失
- 前后端端口没起来
这些问题如果不在入口处挡住,后面所有输出都会变成噪音。
目标 2:让任务状态脱离聊天上下文
真正可靠的状态,必须落盘,而不是依赖:
- 对话历史
- 模型短期记忆
- “上一轮大概做到了哪”
目标 3:让中断恢复成本极低
AI 会话中断是常态,不是例外。Harness 的设计必须默认:
- 会掉线
- 会切模型
- 会换窗口
- 会跨天继续
目标 4:让验证比“自信宣称完成”更重要
任何任务完成都必须以证据为准,而不是以模型的语言表达为准。
目标 5:让系统可迁移
这个 Harness 不应该只适用于某一个项目。它应该可以迁移到:
- Java/Spring Boot 项目
- Node/React/Vue 项目
- Python 后端项目
- 全栈 monorepo
- GIS、数据平台、SaaS、后台系统等不同领域
3.2 非目标
Harness 不应该承担这些职责:
- 不负责替代你做产品决策。
- 不负责自动发明任务边界。
- 不负责掩盖工程本身的混乱。
- 不负责让任何模型都“无限自动正确”。
- 不应该变成另一个难维护的复杂框架。
结论很简单:
Harness 不是魔法层,它是约束层。
四、总览架构:推荐的双阶段模型
一个非常实用的通用设计,是 双阶段模型。
阶段 A:Initializer
只在首次初始化或环境变化后运行,负责:
- 检查是否在专用 worktree
- 获取会话锁
- 做环境预检
- 初始化任务清单
- 初始化进度文件
- 初始化决策日志
- 记录基线提交点
阶段 B:Coding Session
每次真正编码前运行,负责:
- 再次检查 worktree
- 获取会话锁
- 再次预检环境
- 读取进度与任务状态
- 展示最近上下文
- 准备当前会话执行
一个简化版架构图如下:
┌─────────────────────────────┐
│ Dedicated Git Worktree │
│ branch: harness/* │
└──────────────┬──────────────┘
│
▼
┌─────────────────────────────┐
│ harness/ │
│ init.sh │
│ coding-session.sh │
│ feature_list.json │
│ progress.md │
│ decision-log.md │
│ artifacts/ │
│ lib/*.sh │
└──────────────┬──────────────┘
│
▼
┌─────────────────────────────┐
│ Runtime Guards │
│ worktree-guard │
│ session-lock │
│ env-preflight │
│ dirty-check │
│ verify-dispatcher │
└──────────────┬──────────────┘
│
▼
┌─────────────────────────────┐
│ Agent Execution │
│ one feature per session │
│ verify -> evidence -> sync │
└─────────────────────────────┘
五、目录结构应该怎么设计
推荐一个足够通用、但不复杂的目录:
harness/
├── init.sh
├── coding-session.sh
├── feature_list.json
├── progress.md
├── decision-log.md
├── artifacts/
│ └── YYYY-MM-DD/<feature-id>/
├── prompts/
│ ├── initializer-prompt.md
│ └── coding-agent-prompt.md
├── templates/
│ ├── feature-template.json
│ └── progress-template.md
└── lib/
├── worktree-guard.sh
├── session-lock.sh
├── env-preflight.sh
├── dirty-check.sh
├── git-helper.sh
└── verify-dispatcher.sh
目录职责说明
init.sh
只负责一次性初始化,不承担长期业务执行。
coding-session.sh
是日常会话入口。任何新的 AI coding session 都从这里进入。
feature_list.json
任务清单真相源。它负责回答:
- 还有哪些任务
- 哪些已完成
- 哪些依赖未满足
- 当前状态是什么
- 每个任务该如何验证
progress.md
会话级进度真相源。它负责回答:
- 这轮会话正在做什么
- 上一轮做到哪里
- 当前分支和提交点是什么
- 下次该从哪里继续
decision-log.md
决策真相源。它负责回答:
- 为什么这么设计
- 为什么不是另一个方案
- 哪些架构判断已经定了
artifacts/
证据目录。它负责回答:
- 你真的验证了吗
- 验证用了什么脚本
- 日志、截图、报告在哪
六、最关键的设计原则:真相源分层
很多人做 Harness 最大的问题,是把所有状态都堆到一个文件里。这样很快就会失控。
更合理的方式是分层:
第一层:任务真相源
文件:feature_list.json
负责:
- 任务定义
- 优先级
- 依赖关系
- 验证要求
- 当前完成状态
第二层:会话真相源
文件:progress.md
负责:
- 当前会话焦点
- 最新提交点
- 上次完成项
- 接力说明
第三层:架构真相源
文件:decision-log.md
负责:
- 决策背景
- 决策内容
- 备选方案
- 后果
第四层:证据真相源
目录:artifacts/
负责:
- 测试结果
- 验证脚本输出
- 构建日志
- 截图或 HTML 报告
这四层分离后,系统会明显稳定很多,因为:
- 任务定义不会被会话说明污染
- 会话总结不会承担架构论证
- 架构判断不会混进测试输出
- 证据不会只停留在自然语言描述里
七、任务清单该怎么设计
feature_list.json 是整个 Harness 的核心之一。
一个推荐结构如下:
{
"schema_version": "1.0.0",
"generated_at": "2026-04-07T10:00:00+08:00",
"baseline": {
"branch": "harness/example-v1",
"head": "abc1234"
},
"features": [
{
"id": "AUTH-001",
"priority": "P0",
"category": "backend",
"title": "实现登录态读取接口",
"description": "提供当前登录用户信息接口",
"source_refs": [
"docs/specs/auth.md#me-api"
],
"acceptance_criteria": [
"未登录返回 401",
"已登录返回 code/message/data 结构",
"data 中包含 userId 和 role"
],
"dependencies": [],
"verification": {
"mode": "backend-smoke",
"required": [
"unit-test",
"api-smoke"
],
"scripts": [
"scripts/ops/auth-me-smoke.sh"
]
},
"passes": false,
"status": "pending",
"evidence": {
"summary": null,
"artifacts_dir": null
},
"last_verified_at": null
}
]
}
设计重点一:区分不可变字段和可变字段
不可变字段
这些字段一旦初始化,就不要让日常 coding session 随便改:
idprioritycategorytitledescriptionsource_refsacceptance_criteriadependenciesverification
可变字段
这些字段允许在执行中更新:
passesstatusevidencelast_verified_at
这是非常关键的边界。否则模型在实现过程中会把任务定义也改掉,最后变成“任务完成了,因为任务被改简单了”。
设计重点二:状态要足够少
推荐只保留这几个状态:
pendingin_progresscompletedblocked
状态越多,维护成本越高,误判越多。
设计重点三:验证要求要写进任务,而不是靠口头约定
任务不是“做完代码就行”,而是“完成定义 + 验证方式一起定义”。
八、为什么需要 progress.md
很多人会问:既然已经有 feature_list.json,为什么还要 progress.md?
因为这两个文件回答的问题不同。
feature_list.json 回答的是:
整个项目还有哪些任务。
progress.md 回答的是:
当前这轮会话到底干到了哪。
推荐用 Markdown + YAML frontmatter 的形式:
---
last_updated: 2026-04-07T11:20:00+08:00
current_session: 12
current_branch: harness/example-v1
current_head: abc1234
active_feature: AUTH-001
active_status: in_progress
worktree_status: clean
---
# Harness Progress
## Current Status
当前特性、当前状态、开始时间、证据路径
## Last Completed
上一项已完成任务、验证结果、提交点
## Resume Checklist
下轮接力时该先做什么
## Notes
补充说明
为什么 frontmatter 很有用
因为它同时兼顾:
- 机器可解析
- 人类可阅读
Agent 可以快速读 frontmatter 得到核心状态,人类可以读正文理解上下文。
九、为什么 decision-log.md 必须是追加式
AI 多轮开发里,一个非常常见的问题是:
模型今天用 A 方案,明天忘了,又改成 B,后天又绕回 A。
所以决策日志不能是“当前版本说明”,而应该是 Append-only 的决策历史。
推荐格式:
## 2026-04-07 11:30 | AUTH-001 | 会话续期改为滑动过期
**Context**: 登录后接口高频访问,需要降低频繁重新登录
**Decision**: access token 短期有效,refresh token 负责续期
**Rationale**: 降低安全风险,同时兼顾交互体验
**Alternatives Considered**:
- 长期 access token:风险太高
- 完全服务端 session:部署成本更高
**Consequences**:
- 前端需要处理 token 刷新
- 后端需要补充 refresh 逻辑
**References**:
- docs/specs/auth.md
追加式的好处
- 不会因为覆盖而丢历史。
- 新模型接手时能理解为什么不是另一个方案。
- 代码评审时可以追溯设计依据。
十、工作空间为什么必须隔离
这是 Harness 成败的第一道门。
不隔离会发生什么
如果 AI 直接在主工作树跑:
- 它可能混入用户未提交的修改
- 它可能误把其他分支状态当当前事实
- 它可能把本应试验性的改动污染主线
推荐方案:Git Worktree
最实用的方式不是复制仓库,而是用 Git worktree:
git worktree add ../example-harness -b harness/example-v1
这样可以同时获得:
- 独立目录
- 独立分支
- 共享对象库
- 极低切换成本
worktree-guard.sh 应该检查什么
至少检查三件事:
- 当前目录是否在 Git 仓库中。
- 当前分支是否符合约定,例如
harness/*。 - 当前目录是不是 linked worktree,而不是主工作树。
一个通用判断思路:
GIT_DIR=$(git rev-parse --git-dir)
GIT_COMMON_DIR=$(git rev-parse --git-common-dir)
if [ "$GIT_DIR" = "$GIT_COMMON_DIR" ]; then
echo "当前是主工作树,不允许作为 harness 工作区"
exit 1
fi
十一、为什么会话锁必须存在
你可能觉得“我又不会同时开两个 AI 会话”。现实里经常发生的是:
- 你以为上一个窗口停了,其实还活着
- 一个自动循环脚本还在后台跑
- 另一个模型也拿到了同一工作区
所以必须有互斥锁。
推荐实现:flock
不要自己发明 PID 文件协议,直接用 flock。
原因很简单:
- 内核级锁
- 简单
- 稳定
- 文件描述符关闭后自动释放
推荐做法:
exec {LOCK_FD}>".lock"
flock -n "$LOCK_FD"
配套再写一个 .lock.info,给人看:
- pid
- host
- started_at
- branch
锁脚本应该具备哪些能力
acquire_lockrelease_lockcheck_lockclean_stale_lock
一个细节:不要在 source 时自动抢锁
很多人会把锁脚本写成:
source session-lock.sh
# 一 source 就自动 acquire
这会让调用方失去控制权,也容易导致 trap 和清理逻辑混乱。
更好的方式是:
source session-lock.sh
acquire_lock
调用方显式获取和释放,职责更清晰。
十二、为什么环境预检要有明确退出码
环境问题不能都算一类错误。
推荐至少区分 3 类退出码:
0:环境就绪1:服务未就绪,但可重试10:配置错误,必须立即失败
为什么不能只用 0 和 1
因为这两类问题的处理逻辑完全不同:
服务未就绪
例如:
- 前端端口还没起来
- 后端健康检查还没通过
- Redis 正在启动
这类问题可以:
- 自动启动
- 重试等待
配置错误
例如:
- Docker 没装
docker compose不可用.env缺关键变量docker compose config无法通过
这类问题不应该重试,因为重试只会浪费时间。
推荐的预检内容
- Docker 是否存在
- Docker Compose 是否存在
docker compose config是否通过- 数据库是否可连接
- Redis 是否可连接
- 后端是否响应
- 前端是否响应
十三、入口脚本应该如何编排
13.1 init.sh 的推荐流程
推荐顺序:
- 校验 worktree
- 获取会话锁
- 执行环境预检
- 必要时启动服务并重试
- 生成任务清单
- 初始化进度文件
- 初始化决策日志
- 写入基线提交
13.2 coding-session.sh 的推荐流程
推荐顺序:
- 校验 worktree
- 获取会话锁
- 执行环境预检
- 加载
progress.md - 加载
feature_list.json - 展示最近提交和任务摘要
- 进入会话执行
13.3 trap 设计要点
这是 Shell 脚本里最容易出坑的地方。
推荐写法:
trap cleanup EXIT
trap 'trap - EXIT; cleanup; exit 130' INT
trap 'trap - EXIT; cleanup; exit 143' TERM
为什么要 trap - EXIT?
因为否则 INT 或 TERM 触发 cleanup 后,退出时还会再执行一次 EXIT,造成双清理。
十四、验证与证据系统怎么设计
AI 做工程,如果没有证据层,最后一定会退化成“嘴上完成”。
推荐的验证分发器
可以设计一个 verify-dispatcher.sh,统一负责:
- 接收 feature id
- 接收验证模式
- 执行多个脚本
- 汇总结果
- 生成
verification-summary.json
推荐的验证模式
backend-smoke
适用于后端接口、小范围业务逻辑。
frontend-e2e
适用于前端联调、页面回归、浏览器级验证。
manual
适用于必须人眼检查或外部系统参与的场景。
推荐的证据目录
harness/artifacts/2026-04-07/AUTH-001/
├── auth-api-smoke.log
├── auth-api-smoke.result.json
└── verification-summary.json
核心原则
任务状态只能在验证通过后推进为 completed。
不是代码写完就完成,而是:
代码完成 -> 验证通过 -> 证据落盘 -> 状态回写
十五、推荐的“单会话只做一个最小增量”原则
这是 Harness 稳定性的关键。
很多失败案例不是因为模型能力不够,而是因为一轮会话想做太多:
- 一边改前端
- 一边改后端
- 一边补文档
- 一边做发布
最后什么都半吊子。
推荐规则:
一轮会话只做一个 Feature
即使这个 Feature 内部可能跨多个文件,它也必须是一个完整、可验证、可回写的最小闭环。
为什么这条规则重要
- 出问题时容易定位。
- 状态回写简单。
- 验证范围收敛。
- 更适合中断恢复。
- 更适合自动循环执行。
十六、如何设计自动循环执行
当 Harness 稳定以后,可以再叠加一个 auto-loop.sh。
它的职责不是替代 Harness,而是重复调用 Harness。
自动循环脚本应该做什么
- 接收要跑多少轮
- 每轮调用一次 AI
- 每轮只拿一个新任务
- 固定日志目录
- 固定权限模式
- 可选预算控制
- 可选失败继续
它不应该做什么
- 不要自己维护另一套任务系统
- 不要绕过 Harness 真相源
- 不要直接在循环里写复杂业务逻辑
正确的分层关系
auto-loop.sh
↓
coding-session.sh
↓
harness/lib/*
↓
feature_list.json / progress.md / decision-log.md
也就是说:
auto-loop 是调度层,不是事实层。
十七、一个通用 Harness 的最小可落地模板
如果你要快速做一个最小版,可以先实现这 7 个文件:
harness/init.shharness/coding-session.shharness/feature_list.jsonharness/progress.mdharness/decision-log.mdharness/lib/worktree-guard.shharness/lib/session-lock.sh
然后逐步增加:
harness/lib/env-preflight.shharness/lib/verify-dispatcher.shharness/artifacts/
这就是一套可用的最小骨架。
十八、最容易踩的坑
坑 1:让状态只存在聊天里
后果:一断会话就失忆。
坑 2:让任务定义和执行状态混在一起随便改
后果:AI 会通过修改任务定义来“完成任务”。
坑 3:没有专用 worktree
后果:主仓库污染,和人工改动互相踩。
坑 4:没有互斥锁
后果:两个会话同时更新状态文件,最后很难恢复。
坑 5:预检退出码不分层
后果:配置错误也一直重试,浪费大量时间。
坑 6:验证不落盘
后果:最后只能靠自然语言自我汇报,没有证据。
坑 7:一轮会话做太多
后果:看起来很忙,实际上无法稳定闭环。
坑 8:Harness 自己过度复杂化
后果:维护 Harness 的成本比维护项目本身还高。
所以一个很重要的原则是:
Harness 要严格,但不能重。
十九、如何把这套设计迁移到不同类型项目
Java/Spring Boot 项目
重点在:
mvn testmvn compile- API smoke
- 数据库迁移校验
Node.js/React/Vue 项目
重点在:
npm run buildnpm run test- dev server smoke
- Playwright/Cypress
Python 项目
重点在:
pytest- migration check
- lint/type check
- API smoke
GIS/数据平台项目
额外增加:
- 数据源连通性预检
- GeoServer/地图服务可用性检查
- 大体积资源的证据管理
- 前端地图和后端空间服务的联合验证
多服务 monorepo
建议扩展:
- 分模块 feature category
- 分模块 verification mode
- 分模块 artifacts
但核心设计不需要变。
二十、我推荐的实践口诀
如果你只记住这篇文章最核心的东西,记住下面这几句就够了:
口诀 1
先隔离,再执行。
口诀 2
先预检,再编码。
口诀 3
先验证,再宣称完成。
口诀 4
状态写文件,不写脑子。
口诀 5
一轮一增量,一增量一证据。
口诀 6
任务定义不可漂移,执行状态可以推进。
口诀 7
Harness 是约束层,不是炫技层。
二十一、结语:为什么未来的 AI 工程一定离不开 Harness
未来真正有价值的,不是“谁能让 AI 写一个页面”,而是:
谁能让 AI 在一个真实工程里稳定地连续工作很多轮,并且中断后还能恢复,出错后还能定位,完成后还能验证。
这背后靠的不是单条神提示词,而是系统化 Harness。
你可以把它理解成:
- 给 AI 加 Git 工作纪律
- 给 AI 加环境门禁
- 给 AI 加状态存储
- 给 AI 加验证闭环
当这些层搭起来以后,模型能力才真正能转化为工程生产力。
如果没有 Harness,AI 很容易只是一个“会说很多话的临时助手”。
如果有了 Harness,它才有机会变成“能持续接力的工程执行者”。
二十二、可直接复用的落地清单
如果你准备自己做一套 Harness,可以按下面的顺序开始:
- 先建立专用
harness/*worktree。 - 新建
harness/目录。 - 先实现
worktree-guard.sh。 - 再实现
session-lock.sh。 - 再实现
env-preflight.sh。 - 定义
feature_list.json结构。 - 定义
progress.md结构。 - 定义
decision-log.md结构。 - 写
init.sh。 - 写
coding-session.sh。 - 写
verify-dispatcher.sh。 - 最后再考虑自动循环脚本。
这个顺序很重要。
因为先把边界、锁、环境和状态做对,后面的自动化才不会建在沙地上。