从 0 设计一个可长跑的 AI 开发 Harness:状态编排、会话锁与验证闭环实战

8 阅读16分钟

一、这篇文章解决什么问题

如果你已经开始让大模型参与真实项目开发,很快就会遇到几个非常现实的问题:

  1. 单次会话上下文不够,模型做着做着就忘了前面约束。
  2. 多个会话同时改同一个仓库,互相踩状态。
  3. 模型会“以为自己完成了”,但没有留下可验证证据。
  4. 任务跨天、跨窗口、跨模型以后,状态很难接力。
  5. 环境没起好、配置没配对、服务没就绪时,AI 会在错误基础上继续工作。

这类问题本质上不是“提示词不够强”,而是你缺少一个真正的 Harness

这里的 Harness,不是某个特定产品名,也不是某个 Agent 框架的专属功能,而是一套围绕 AI 长时间工程执行而设计的:

  • 工作目录隔离机制
  • 状态真相源
  • 会话互斥机制
  • 环境预检机制
  • 任务选择与验证机制
  • 可恢复的接力协议

它的目标不是让 AI “看起来更聪明”,而是让 AI 在真实工程里 更稳定、更少返工、更容易接管


二、什么是长跑型 AI 开发 Harness

一句话定义:

长跑型 AI 开发 Harness,就是给 AI 编码过程加上一层工程操作系统,让它不再依赖单次聊天记忆,而是依赖可落盘、可恢复、可验证的外部状态运行。

它通常具备 6 个核心特征:

  1. 专用工作空间:AI 不直接在主工作树乱改,而是在专用 worktree 或隔离目录工作。
  2. 显式状态文件:任务、进度、决策、证据都写入磁盘,而不是只留在对话里。
  3. 互斥锁:同一份任务状态同一时间只能被一个活动会话占用。
  4. 环境门禁:没有通过预检,就不允许进入实现阶段。
  5. 一步一验:每个最小增量都要验证、留证、回写。
  6. 可接力:任何时间中断,下一轮都能从状态文件恢复,而不是重新靠人解释。

三、设计目标与非目标

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 不应该承担这些职责:

  1. 不负责替代你做产品决策。
  2. 不负责自动发明任务边界。
  3. 不负责掩盖工程本身的混乱。
  4. 不负责让任何模型都“无限自动正确”。
  5. 不应该变成另一个难维护的复杂框架。

结论很简单:

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 随便改:

  • id
  • priority
  • category
  • title
  • description
  • source_refs
  • acceptance_criteria
  • dependencies
  • verification

可变字段

这些字段允许在执行中更新:

  • passes
  • status
  • evidence
  • last_verified_at

这是非常关键的边界。否则模型在实现过程中会把任务定义也改掉,最后变成“任务完成了,因为任务被改简单了”。

设计重点二:状态要足够少

推荐只保留这几个状态:

  • pending
  • in_progress
  • completed
  • blocked

状态越多,维护成本越高,误判越多。

设计重点三:验证要求要写进任务,而不是靠口头约定

任务不是“做完代码就行”,而是“完成定义 + 验证方式一起定义”。


八、为什么需要 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

追加式的好处

  1. 不会因为覆盖而丢历史。
  2. 新模型接手时能理解为什么不是另一个方案。
  3. 代码评审时可以追溯设计依据。

十、工作空间为什么必须隔离

这是 Harness 成败的第一道门。

不隔离会发生什么

如果 AI 直接在主工作树跑:

  • 它可能混入用户未提交的修改
  • 它可能误把其他分支状态当当前事实
  • 它可能把本应试验性的改动污染主线

推荐方案:Git Worktree

最实用的方式不是复制仓库,而是用 Git worktree:

git worktree add ../example-harness -b harness/example-v1

这样可以同时获得:

  • 独立目录
  • 独立分支
  • 共享对象库
  • 极低切换成本

worktree-guard.sh 应该检查什么

至少检查三件事:

  1. 当前目录是否在 Git 仓库中。
  2. 当前分支是否符合约定,例如 harness/*
  3. 当前目录是不是 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

锁脚本应该具备哪些能力

  1. acquire_lock
  2. release_lock
  3. check_lock
  4. clean_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 无法通过

这类问题不应该重试,因为重试只会浪费时间。

推荐的预检内容

  1. Docker 是否存在
  2. Docker Compose 是否存在
  3. docker compose config 是否通过
  4. 数据库是否可连接
  5. Redis 是否可连接
  6. 后端是否响应
  7. 前端是否响应

十三、入口脚本应该如何编排

13.1 init.sh 的推荐流程

推荐顺序:

  1. 校验 worktree
  2. 获取会话锁
  3. 执行环境预检
  4. 必要时启动服务并重试
  5. 生成任务清单
  6. 初始化进度文件
  7. 初始化决策日志
  8. 写入基线提交

13.2 coding-session.sh 的推荐流程

推荐顺序:

  1. 校验 worktree
  2. 获取会话锁
  3. 执行环境预检
  4. 加载 progress.md
  5. 加载 feature_list.json
  6. 展示最近提交和任务摘要
  7. 进入会话执行

13.3 trap 设计要点

这是 Shell 脚本里最容易出坑的地方。

推荐写法:

trap cleanup EXIT
trap 'trap - EXIT; cleanup; exit 130' INT
trap 'trap - EXIT; cleanup; exit 143' TERM

为什么要 trap - EXIT

因为否则 INTTERM 触发 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 内部可能跨多个文件,它也必须是一个完整、可验证、可回写的最小闭环。

为什么这条规则重要

  1. 出问题时容易定位。
  2. 状态回写简单。
  3. 验证范围收敛。
  4. 更适合中断恢复。
  5. 更适合自动循环执行。

十六、如何设计自动循环执行

当 Harness 稳定以后,可以再叠加一个 auto-loop.sh

它的职责不是替代 Harness,而是重复调用 Harness。

自动循环脚本应该做什么

  1. 接收要跑多少轮
  2. 每轮调用一次 AI
  3. 每轮只拿一个新任务
  4. 固定日志目录
  5. 固定权限模式
  6. 可选预算控制
  7. 可选失败继续

它不应该做什么

  1. 不要自己维护另一套任务系统
  2. 不要绕过 Harness 真相源
  3. 不要直接在循环里写复杂业务逻辑

正确的分层关系

auto-loop.sh
    ↓
coding-session.sh
    ↓
harness/lib/*
    ↓
feature_list.json / progress.md / decision-log.md

也就是说:

auto-loop 是调度层,不是事实层。


十七、一个通用 Harness 的最小可落地模板

如果你要快速做一个最小版,可以先实现这 7 个文件:

  1. harness/init.sh
  2. harness/coding-session.sh
  3. harness/feature_list.json
  4. harness/progress.md
  5. harness/decision-log.md
  6. harness/lib/worktree-guard.sh
  7. harness/lib/session-lock.sh

然后逐步增加:

  1. harness/lib/env-preflight.sh
  2. harness/lib/verify-dispatcher.sh
  3. harness/artifacts/

这就是一套可用的最小骨架。


十八、最容易踩的坑

坑 1:让状态只存在聊天里

后果:一断会话就失忆。

坑 2:让任务定义和执行状态混在一起随便改

后果:AI 会通过修改任务定义来“完成任务”。

坑 3:没有专用 worktree

后果:主仓库污染,和人工改动互相踩。

坑 4:没有互斥锁

后果:两个会话同时更新状态文件,最后很难恢复。

坑 5:预检退出码不分层

后果:配置错误也一直重试,浪费大量时间。

坑 6:验证不落盘

后果:最后只能靠自然语言自我汇报,没有证据。

坑 7:一轮会话做太多

后果:看起来很忙,实际上无法稳定闭环。

坑 8:Harness 自己过度复杂化

后果:维护 Harness 的成本比维护项目本身还高。

所以一个很重要的原则是:

Harness 要严格,但不能重。


十九、如何把这套设计迁移到不同类型项目

Java/Spring Boot 项目

重点在:

  • mvn test
  • mvn compile
  • API smoke
  • 数据库迁移校验

Node.js/React/Vue 项目

重点在:

  • npm run build
  • npm 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,可以按下面的顺序开始:

  1. 先建立专用 harness/* worktree。
  2. 新建 harness/ 目录。
  3. 先实现 worktree-guard.sh
  4. 再实现 session-lock.sh
  5. 再实现 env-preflight.sh
  6. 定义 feature_list.json 结构。
  7. 定义 progress.md 结构。
  8. 定义 decision-log.md 结构。
  9. init.sh
  10. coding-session.sh
  11. verify-dispatcher.sh
  12. 最后再考虑自动循环脚本。

这个顺序很重要。

因为先把边界、锁、环境和状态做对,后面的自动化才不会建在沙地上。