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 项目重新变成一个可理解、可验证、可约束、可持续演进的工程系统。

这,就是我目前理解的:

棕地式 Harness Engineering

posted @ 2026-10-04 21:10  0xMouise  阅读(7)  评论(0)    收藏  举报