AI 已经把项目写大了,才发现没有 Harness,怎么办?
AI 已经把项目写大了,才发现没有 Harness,怎么办?
——一个已经存在的 AI 项目,如何反向建立 项目 Harness
最近在学习 Harness Engineering 的过程中,我突然遇到了一个很现实的问题。
前面我们一直在讨论:
如果从 0 开始一个项目,
应该怎样让 AI 智能体 更可靠地开发?
比如:
先定义目标
↓
拆任务
↓
明确 架构
↓
定义 状态所有权
↓
寻找 不变量
↓
建立 测试 / 守卫 / 持续集成
↓
再让 Agent 开发
这套流程听起来很合理。
但问题是:
如果我的项目已经写了很久,而且过去大量代码都是 AI 写的怎么办?
更麻烦的是:
以前没有 架构 约束
没有 状态所有权
没有明确 不变量
没有 黄金端到端测试
没有统一文档
甚至自己都不知道现在真实 运行时 是什么
这种项目还能用 Harness Engineering 吗?
答案是:
可以。
只是这时候你做的已经不是 式 Harness Engineering,而更像:
逆向 Harness Engineering
也就是:
对一个已经存在的项目,反向把它真实的 项目 Harness 挖出来,再逐步补齐。
这篇文章记录的,就是我目前对这个过程的理解。
一、先搞清楚一个误区:不是重新设计架构
刚开始我很容易产生一个想法:
既然以前没设计好,
那现在让 AI 重新设计一个完整架构不就行了?
后来我发现,这可能是最危险的一步。
因为对于一个已经存在很久的项目来说:
“系统应该是什么样”
和:
“系统现在实际上是什么样”
是两件完全不同的事情。
如果我直接把代码交给一个 Agent,然后说:
帮我重新设计整个项目架构。
它非常可能根据:
最佳实践
设计模式
Clean 架构
领域驱动设计(DDD)
自己喜欢的结构
给我设计出一套新的“理想系统”。
但这并不能回答一个更重要的问题:
我现在这个项目到底是怎么运行的?
所以 棕地式 Harness Engineering 的第一原则应该是:
不要先设计 未来架构。
先发现 当前现实。
二、第一阶段不是 架构设计,而是 系统考古
对于一个已经存在的项目,我现在更喜欢用一个词:
架构考古。
也就是“架构考古”。
意思就是:
不要根据 README 猜。
不要根据旧文档猜。
不要根据文件夹名称猜。
不要根据类名猜。
从真实入口开始,
顺着代码往下追。
比如:
程序从哪里启动?
哪个模块注册 命令?
哪个模块注册 工具?
真正调用 工具 的是谁?
状态 在哪里写?
谁负责持久化?
哪些模块只是存在但根本没运行?
哪些代码只是旧版本残留?
于是第一份最重要的文档往往不是:
ARCHITECTURE.md
而应该是:
当前状态.md
回答的是:
这个项目现在真实存在什么?
三、文件存在,不等于功能存在
这是我觉得 AI 项目特别容易踩的坑。
AI 开发项目经常会出现:
文件写出来了
↓
测试也写了
↓
报告里说“功能完成”
于是我们天然认为:
这个功能已经存在。
但实际上可能只是:
代码存在
而没有:
运行时 接线
例如:
某个 图谱 Consumer 写好了
但从来没人注册
某个 事件总线 有 emit
但没有 订阅者
某个 工具 写好了
但 Agent 根本无法调用
某个 流水线 有完整测试
但生产入口从来没经过它
所以以后面对 AI 写出来的旧项目,我认为所有组件最好先分成几类:
运行时已确认
已实现但未集成
仅存在于文档
遗留
未知
这一步非常重要。
因为:
项目 Harness 必须建立在真实 运行时 上,而不能建立在“代码看起来存在”上。
四、不要一次理解整个项目,先找到一条 参考切片
另一个很重要的改变是:
不要试图一开始就搞懂整个项目。
如果一个项目已经几万行、几十万行:
命令
工具
存储
运行时
图谱
数据库
Events
Queue
API
UI
全部同时研究,很容易把自己搞崩。
更好的方式是:
先找到一条真正能完整跑通的 黄金参考切片。
例如一个通用项目可能是:
用户请求
↓
控制器
↓
服务层
↓
仓储层
↓
数据库
↓
响应
或者一个 Agent 项目:
Agent
↓
动作
↓
工具
↓
运行时
↓
Result
↓
状态
↓
输出
只追这一条。
而且每个箭头都必须问:
A 为什么会进入 B?
真正的 调用方 是谁?
真正的 被调用方 是谁?
文件在哪里?
函数在哪里?
最终得到:
一条被代码证据证明的运行链
这条链非常重要。
因为以后很多 Harness 规则,都是从这条 参考切片 里提炼出来的。
五、真正需要理解的架构,其实只有三个问题
以前我看到 架构 这个词会觉得很复杂。
什么:
领域驱动设计(DDD)
Clean 架构
Hexagonal 架构
事件驱动
命令查询职责分离(CQRS)
对于刚开始的人来说,很容易看晕。
但后来我发现,在逆向 项目 Harness 时,我一开始只需要理解三个问题:
边界
谁负责什么?
所有权
谁说了算?
调用方向
谁可以调用谁?
这三个东西已经足够发现大量问题。
六、边界:一个模块到底应该负责什么?
例如假设一个模块的职责是:
把 工具 原始输出
转换成标准结构
那么它正常应该负责:
原始结果
↓
规范化结果
但如果你发现它还顺手:
修改数据库
写 图谱
创建业务状态
发送通知
修改权限
那么很可能:
边界 正在泄漏。
边界 最重要的问题不是:
代码文件放哪个目录?
而是:
这个模块的职责在哪里结束?
七、所有权:某个 状态 到底谁说了算?
这个可能是 棕地式 Harness Engineering 里面最重要的问题。
例如系统里存在:
Order Status
User Session
Payment 状态
Evidence
Task 状态
图谱
Configuration
都应该问:
谁创建?
谁修改?
谁读取?
谁持久化?
谁是 唯一事实来源?
如果最后发现:
模块 A 可以写
模块 B 也可以写
模块 C 也保存了一份副本
那么这就值得重点调查。
注意:
多 写入者 不一定永远错误。
但必须明确:
为什么允许多个 写入者?
它们如何同步?
谁拥有最终 最终权限?
冲突时听谁的?
如果这些问题没有答案,那么 AI 后面继续开发时,很容易继续扩大混乱。
八、调用方向:谁能调用谁?
调用方向 其实非常直观。
比如目标架构可能是:
Planner
↓
动作
↓
运行时
↓
工具
如果某一天 AI 新写了一段代码:
Planner
↓
直接 工具
虽然功能可能仍然正常运行,但实际上:
运行时 被绕过了。
如果 运行时 负责:
权限
日志
审计
重试
限流
Trace
那这个“快捷调用”就已经破坏了系统。
所以 依赖方向 不是为了代码漂亮。
它往往是在保护:
安全性
一致性
可观测性
可靠性
九、在正式写 Harness 之前,先建立 可信基线
我现在觉得,“接手旧项目”最重要的第一阶段,可以叫:
可信基线
也就是:
先证明这个项目哪些地方值得相信。
注意不是:
先把所有 缺陷 修掉。
而是找:
影响可信度的关键风险
也就是:
会导致你无法相信系统真实状态的问题。
例如:
数据会不会丢?
同一个操作会不会执行两次?
权限是否能绕过?
状态 有没有多个冲突 写入者?
测试 是不是只测了 模拟对象?
运行时 是否真的经过测试里的那条路径?
错误发生后 状态 会不会被破坏?
这些问题和普通 UI 缺陷 不一样。
比如:
按钮颜色错了
不会影响你理解整个系统。
但:
数据库状态可能随机覆盖
就会影响整个项目的可信度。
所以对于 Brownfield 项目:
不是先修完所有 缺陷,而是先修会破坏 可信基线 的 缺陷。
十、为什么要先修 影响可信度的关键缺陷?
假设一个系统存在:
状态 可能丢失
但你现在马上开始写:
状态所有权.md
你可能会得到一个很漂亮的结论:
存储 A
=
唯一事实来源
问题是:
存储 A 自己的持久化行为都是坏的。
那么你后面的 架构 和 不变量 都建立在错误地基上。
所以更稳妥的顺序是:
发现风险
↓
静态确认
↓
动态复现
↓
最小修复
↓
回归测试
↓
再继续 Harness 逆向
这一点给我很大启发。
Harness Engineering 不是:
先写一大堆规则。
而是:
先通过证据理解系统,再把真实重要的东西固化成规则。
十一、缺陷 本身其实是 Harness 的原材料
这可能是我目前对 Harness Engineering 最大的理解变化之一。
以前遇到 缺陷:
缺陷
↓
修掉
↓
结束
现在应该多问一步:
为什么这个 缺陷 可以发生?
然后继续问:
系统缺什么保护?
比如一个通用例子:
两个模块同时修改同一个 状态
↓
发生数据覆盖
普通解决:
修当前代码
Harness Engineering 会继续问:
谁才应该是这个 状态 的 Owner?
未来能不能禁止第二个 写入者?
能不能加入 回归测试?
能不能加入 架构测试?
能不能通过 运行时守卫 限制?
于是一次 缺陷 会变成:
缺陷 Fix
+
回归测试
+
所有权 Rule
+
不变量候选
+
机器强制执行
这就是所谓:
失败 → Harness 改进
十二、不要从 缺陷 直接跳到 不变量
但这里也有一个坑。
发现一次问题以后,很容易立刻宣布:
以后整个项目必须只有一个 存储 实例。
这不一定对。
因为真正应该保护的可能不是:
只能存在一个实例
而是:
任何 写入者 都不能覆盖其他 写入者 已经提交的数据。
或者:
所有 mutation 必须经过统一 事务层。
这两个规则和:
只能创建一个对象
完全不是一回事。
所以更正确的路径应该是:
缺陷
↓
根因
↓
运行时证据
↓
所有权审计
↓
不变量候选
↓
验证
↓
正式 不变量
而不是:
缺陷
↓
凭感觉定规则
十三、旧文档不能直接信,也不能直接删
Brownfield 项目还有另一个很现实的问题:
历史文档特别多。
尤其 AI 开发项目,很容易出现:
AUDIT-REPORT.md
FINAL-REPORT.md
FINAL-REPORT-V2.md
ARCHITECTURE-REVIEW.md
FIX-REPORT.md
DISTILLATION.md
IMPLEMENTATION-SUMMARY.md
看起来信息很多。
实际上里面可能混着:
真实事实
旧设计
未来计划
已经废弃的实现
重复内容
AI 当时的推测
阶段性测试数字
这时候两个极端都不好。
极端一:全部保留
最后 Agent 搜索项目时:
找到 5 种不同的 架构 描述
它根本不知道信谁。
极端二:全部删除
又可能丢掉:
历史设计原因
重要 缺陷 根因
Legacy 解释
为什么某条规则存在
所以更好的流程是:
隔离
↓
分类
↓
提炼
↓
归档
↓
删除
十四、历史报告应该怎么分类?
我目前比较喜欢这些分类:
当前规范文档
历史决策
遗留参考
需要提炼的知识
临时报告
重复文档
过时或错误
未知
其中最重要的是:
当前规范文档
当前仍然有效,可以作为 Agent 的正式知识来源。
历史决策
虽然不是当前事实,但解释:
为什么当时这么设计。
遗留参考
描述仍然存在,但正在淘汰的旧路径。
需要提炼的知识
原报告不值得留,但里面有一两条重要知识。
把知识提炼出去以后,原文就可以归档。
临时报告
例如:
本轮改了 7 个文件
测试 621 通过
AI 完成阶段 3
这种一般不应该长期进入 智能体上下文。
十五、仓储层 不是 AI 聊天记录仓库
这一点我现在非常认同。
未来项目里的 仓储层 不应该变成:
AI 每做一次任务
↓
生成一份报告
↓
永久塞进 docs/
最后:
200 份报告
Agent 搜索时全部进入 Context。
这实际上是在制造:
上下文污染
仓储层 更应该成为:
未来 Agent 工作所需要的可信知识库。
所以留下来的应该是:
当前状态
ARCHITECTURE
状态所有权
不变量
遗留
决策记录
参考切片
而不是:
AI 的全部工作过程。
十六、从旧项目反向得到 项目 Harness
完成前面的调查以后,才真正开始形成 项目 Harness。
一个比较简单的结构可能是:
AGENTS.md
docs/
├── 当前状态.md
├── ARCHITECTURE.md
├── 参考切片.md
├── 状态所有权.md
├── 不变量.md
├── 遗留.md
│
├── decisions/
│
└── archive/
这里每一个文件职责不同。
当前状态.md
回答:
项目现在真实是什么?
ARCHITECTURE.md
回答:
系统长期怎么组织?
边界 是什么?
依赖方向 是什么?
参考切片.md
回答:
一条真实成功链路到底怎么跑?
状态所有权.md
回答:
哪个 状态 谁拥有?
谁可以写?
谁只是读?
谁是 Projection?
不变量.md
回答:
哪些事情无论以后怎么改都不能破坏?
遗留.md
回答:
当前还存在什么旧路径?
为什么没删?
未来怎么迁移?
decisions/
回答:
为什么当初做出某个重要架构选择?
十七、文档只是 Harness 的一半
即使把这些文档都写好了,也还不能算真正完成。
因为:
“请不要破坏 架构”
仍然只是文字。
真正成熟的 Harness 应该继续问:
哪些规则可以让机器自动验证?
比如:
Module A 不能 Import Module B
可以做:
架构测试
未经 Approval 不能执行某 动作
可以做:
运行时守卫
同一个请求不能产生两次副作用
可以做:
集成测试
核心业务闭环必须完整成功
可以做:
黄金端到端测试
所以真正的变化是:
人类可读规则
↓
机器可检查规则
十八、逆向 Harness 最终的完整路线
现在如果让我总结一个已经存在项目的 棕地式 Harness Engineering 流程,我会这样写:
1. Current 状态 Audit
↓
2. 找真实 运行时 Entry
↓
3. 建立 黄金参考切片
↓
4. 建立 可信基线
↓
5. 找 Trust-Critical Risk
↓
6. 必要时动态复现
↓
7. 修 P0/P1 基础一致性问题
↓
8. 加 回归测试
↓
9. Legacy Documentation 提炼ation
↓
10. 状态所有权 Audit
↓
11. 边界 / 依赖方向
↓
12. 不变量 Extraction
↓
13. 架构 Rule
↓
14. 机器强制执行
↓
15. 黄金端到端测试
↓
16. 正常继续开发
以后每一次重要失败再进入:
失败
↓
根因
↓
是否属于可重复的问题类型?
↓
是否存在 Harness 缺口?
↓
测试 / Guard / 工具 / Docs / 持续集成
于是项目的 Harness 会不断成长。
十九、以后接手其他项目,也必须完整走一遍吗?
不一定。
如果接手一个成熟项目,它已经有:
ARCHITECTURE.md
架构决策记录(ADR)
状态所有权
成熟测试
黄金端到端测试
持续集成门禁
可观测性
清晰 AGENTS.md
那没有必要:
从第一行源码重新考古。
可以先进行:
可信度评估
快速确认:
文档是否和代码一致?
几个核心 参考切片 是否真实?
测试是否有效?
关键 不变量 是否有 Enforcement?
状态所有权 是否明确?
如果都可信:
继续开发即可。
所以正确原则不是:
每接一个项目都完整审计。
而应该是:
接手一个陌生项目时,不默认相信它的 Harness;先建立足够的 Trust,再决定审计多深。
二十、Greenfield 和 棕地式 Harness Engineering
到这里,我觉得可以把 Harness Engineering 分成两个非常直观的场景。
Greenfield
新项目:
目标
↓
架构
↓
所有权
↓
不变量
↓
Harness
↓
实现
先建立基本规则,再开发。
Brownfield
旧项目:
现实
↓
Trust
↓
所有权
↓
不变量
↓
Harness
↓
继续开发
先反向理解真实系统,再建立规则。
它们最终都会汇合到同一个状态:
AI 可以高速开发
但系统知道:
什么可以做
什么不能做
什么必须成立
怎么证明完成
出了问题怎么发现
二十一、我现在对 项目 Harness 的理解
以前我会把 项目 Harness 理解成:
给 AI 写一些开发规则。
现在我觉得这个理解太浅了。
项目 Harness 更像是:
把这个项目里原本存在于高级工程师脑子里的知识、边界、约束、测试方法和判断标准,逐渐变成 Agent 可以读取、执行和验证的工程环境。
它不是一个文件。
不是:
AGENTS.md
也不是:
ARCHITECTURE.md
而是:
知识
+
架构
+
所有权
+
不变量
+
运行时守卫
+
测试
+
可观测性
+
持续集成
共同组成的系统。
结语
如果一个项目以前完全没有按照 Harness Engineering 的方式开发,并不意味着它只能推倒重来。
更现实的办法是:
先不要改
先看清
然后:
现实
↓
Trust
↓
所有权
↓
不变量
↓
Enforcement
一点一点把项目从:
“AI 写出来的一堆代码”
变成:
“AI 可以长期可靠维护的工程系统”
我现在觉得,这可能会成为未来一个非常常见的工程场景。
因为未来一定会出现越来越多这样的项目:
代码是 AI 写的
功能很多
开发速度很快
但没人真正知道系统已经变成什么样
而到那个时候,真正有价值的能力可能不只是:
继续让 AI 写更多代码
而是:
有能力把一个已经失去结构的 AI 项目重新变成一个可理解、可验证、可约束、可持续演进的工程系统。
这,就是我目前理解的:

浙公网安备 33010602011771号