别再教聪明模型做小学数学:从「过程编排」到「质量约束」的 Agent 范式演进
过去一年,我在 AI 编程这件事上绕了一个不小的圈。
最开始,我很喜欢给 Agent 加流程。
需求先澄清,澄清完写 Spec;Spec 确认之后拆 Tasks;开始改代码之前先出计划;改完再 Review,再验证,再归档。项目里的 Skills 越来越多,规则越来越细,甚至连模型在什么阶段应该输出什么格式,我都希望提前规定好。
当时这么做有很充分的理由。
早期模型确实经常乱来:看到一个报错就直接改,找到一个调用点就以为找到了根因,任务稍微长一点就开始丢目标。给它一条明确的轨道,往往比让它自由发挥稳定得多。
问题是,模型变了,而我的使用方式没有及时变。
后来我逐渐发现,有些过去很有价值的“护栏”,在更强的模型上反而开始产生摩擦。模型花很多篇幅写计划、填模板、汇报阶段进度,最后交出来的代码却未必因此更好。
也是在这个过程中,我意识到另一个比工具选择更麻烦的问题:
我把太多本该属于自己的思考也一起外包了。
一、真正让我警觉的,不是 AI 写错代码
以前写代码,我脑子里会自然走一遍这样的过程:
理解问题 → 建模 → 想方案 → 写代码 → 调试 → Review
用了很长一段时间 AI 之后,这个循环一度变成了:
描述需求 → 等 AI 生成 → 看能不能跑 → 下一个需求
刚开始没觉得有什么问题。
毕竟代码确实写得更快,很多重复劳动也消失了。
但过了一段时间,我发现自己读代码的状态变了。
以前看到一段陌生实现,我会下意识去想:
- 数据从哪里来;
- 状态在哪里发生变化;
- 为什么这里要多这一层;
- 哪些地方可能违反前置条件;
- 如果这里出错,影响会往哪里传。
后来面对 AI 一次生成的大段代码,我很容易直接进入一种被动阅读状态。
只要测试绿了,第一反应居然会变成:
应该差不多吧。
这其实挺危险。
真正退化的不是某个 API 记不住了,而是主动建立系统模型的习惯在变弱。
编程不只是“知道语法”和“记得框架怎么用”。大量能力都来自长期形成的模式识别:看到一个状态流就知道哪里可能失控,看到一个类承担了太多职责就会觉得别扭,看到异常处理出现在奇怪的位置就会继续往上追。
这些能力不用,同样会生疏。
所以后来我给自己定了一条很简单的边界:
AI 可以替我写很多代码,但我不能不知道这些代码为什么应该这样写。
样板代码可以让它生成,批量修改可以让它做,重复劳动当然也没必要自己硬敲。
但几个问题我必须能回答:
- 这个功能真正的不变量是什么?
- 关键状态在哪里流转?
- 这次修改碰到了哪些边界?
- 为什么选择这个方案,而不是另一个?
- 如果它错了,我有没有能力看出来?
这比“代码是不是我亲手敲的”重要得多。
二、很多重流程,本来就是为旧模型准备的
回头看那些复杂的 Agent 工作流,我并不觉得它们“从一开始就是错的”。
恰恰相反,它们当时解决的都是真问题。
模型容易局部修改,就要求它先搜索。
模型不会规划,就让它先写 Plan。
模型容易忘目标,就把工作拆成 Tasks。
模型修改之后不验证,就强制增加 Review 和 Test 阶段。
从工程角度看,这很合理:
模型内部做不好的事情,就暂时用外部流程补。
于是我们慢慢有了各种不同形式的东西:
需求
→ Spec
→ Design
→ Tasks
→ Implement
→ Review
→ Verify
→ Archive
有些工具更偏规格管理,有些更偏执行工作流,也有一些会用 Schema、状态文件甚至脚本,把每一步都固定下来。
在模型规划能力比较弱的时候,这类方法能明显降低随机性。
但这里有一个经常被忽略的问题:
工作流补偿的是模型当时的能力,而模型能力本身一直在变。
现在的强模型已经能自己完成不少过去必须显式提示的动作:
- 搜索代码;
- 沿调用链继续追踪;
- 根据测试失败调整判断;
- 在实现过程中发现原方案不成立之后重新规划。
这并不意味着它已经不会犯错。
它当然还会犯,而且长任务、跨上下文任务依然很难。
但问题的性质已经变了。
以前更像是:
不告诉它下一步做什么,它很可能不知道下一步做什么。
现在很多时候则变成:
它其实知道该怎么查,但我们要求它先完成一套规定动作。
这两者不是一回事。
三、我为什么开始减少工作流指令
我真正开始怀疑重流程,是因为碰到过很多次类似的情况:
Agent 的过程越来越“专业”,结果却没有同步变好。
它会输出很漂亮的计划。
会列风险。
会写 checklist。
会逐项告诉你某个阶段已经完成。
看起来一切井井有条。
但打开代码一看,真正的问题可能还在那里。
后来我觉得,这里面至少有三个原因。
1. 上下文不是免费的
模型的上下文越来越长,这是事实。
但长上下文不等于“往里面塞什么都没有代价”。
假如一个任务开始之前,模型已经要读:
- 很长的工作流说明;
- 固定输出模板;
- 状态迁移规则;
- Skill 使用协议;
- 多 Agent 协作约定;
- 各种重复的 MUST / MUST NOT。
这些东西就会和真正重要的信息混在一起:
- 业务规则;
- 类型定义;
- 调用关系;
- 测试;
- 历史兼容逻辑;
- 系统不变量。
我现在不太追求“让模型知道尽可能多”。
我更在意的是:
当前上下文里的信息,有多少是真的和这个任务有关。
很多时候,一份短但准确的项目说明,加上正确的代码入口,比几千 Token 的通用工作流更有用。
2. 软件排障本来就不是线性的
实际写代码的时候,很少真的按照一条直线走到底。
你可能先看到一个 API 报错,追进去之后发现不是 API 的问题,而是共享类型定义错了。
也可能看起来是状态管理的问题,最后发现根因只是某个边界没有维护不变量。
有时候应该先跑测试。
有时候甚至不用跑,先把同类实现搜出来就已经能确认设计意图。
真正有效的探索路径往往是:
看一眼 → 得到新证据 → 改变方向 → 再验证
这本质上是一棵动态展开的搜索树。
如果外部流程规定模型:
先完整分析
→ 再生成设计
→ 再拆任务
→ 再逐项实施
就可能出现一种很奇怪的情况:
为了遵守流程,模型不得不把一个原本十分钟能确定的问题,写成一套半小时的工作报告。
流程本来是为了减少无效动作,最后自己却成了无效动作的一部分。
3. 过程完成,不等于工程完成
这是我现在最在意的一点。
流程一旦太重,Agent 很容易开始优化一个错误目标:
怎样顺利完成当前阶段?
而不是:
怎样让系统恢复到正确状态?
这时候就很容易出现一些熟悉的代码味道。
比如本来应该追根因,最后却加了几个 fallback,把异常状态藏起来。
本来应该拆开职责,最后因为“当前文件改起来最方便”,直接把业务逻辑塞进 Controller。
或者一个简单函数就能解决的问题,为了让设计显得完整,又多出 Factory、Adapter、Manager 之类的一层。
代码能跑。
任务卡也打勾了。
但架构比修改之前更差。
所以现在我看 Agent 输出,越来越少关心它有没有漂亮地执行完流程,反而更关心几个朴素的问题:
根因到底解决了吗?
系统不变量还成立吗?
修改有没有制造新的债务?
证据在哪里?
四、与其规定路径,不如把结果标准说清楚
这也是我现在最大的使用变化。
以前我喜欢告诉模型:
第一步做什么。
第二步做什么。
第三步输出什么。
现在我更倾向于告诉它:
什么东西不能破坏。
什么条件必须满足。
什么结果才算完成。
两者的区别很大。
| 维度 | 过程编排 | 质量约束 |
|---|---|---|
| 重点 | 模型怎么执行 | 最终结果是否正确 |
| 上下文 | 流程、模板、状态 | 业务、代码、不变量 |
| 模型自主性 | 较低 | 较高 |
| 人类职责 | 审批过程 | 判断边界和风险 |
| 可靠性来源 | Prompt | 类型、测试、静态检查、CI |
比如我不太在意模型到底:
- 先搜文件还是先跑测试;
- 先改类型还是先改实现;
- 是否中途推翻了原来的判断。
这些都可以根据实际情况调整。
但下面这些事情,不能靠“自由发挥”:
- 不能悄悄破坏公共 API;
- 不能为了消灭异常绕开系统不变量;
- 不能让类型安全倒退;
- 不能没跑测试却说“已经验证”;
- 修复 Bug 之后最好留下可以复现问题的回归证据。
我的理解逐渐变成了:
模型负责寻找路径,工程系统负责判断这条路径最后有没有走对。
五、能交给工具检查的东西,就别只写在 Prompt 里
这件事想明白之后,我对 AGENTS.md 的看法也变了。
以前很容易把所有规则都塞进去:
不允许循环依赖。
不允许跨层调用。
某模块不能依赖某模块。
API 必须兼容。
DTO 必须满足某种结构。
这些话不是没用。
问题在于,Prompt 从来都不是最可靠的约束执行器。
模型可能漏掉,也可能误解,还可能觉得“这里特殊,可以例外”。
所以现在我的原则是:
只要规则能够机器验证,就尽量不要只靠模型自觉。
类型问题,让类型系统报错。
格式和静态问题,让 linter 拦。
架构边界,能写 architecture test 就写 test。
行为正确性,用 unit test、integration test、contract test、e2e test 去验证。
真正不能退让的东西,最后放进 CI。
这样一来,AGENTS.md 就不用承担一个不现实的职责:
靠一大段文字保证模型永远不犯错。
它更适合做几件事:
- 告诉模型这个项目怎么看;
- 说明真正重要的系统边界;
- 写清楚哪些决策风险太高,不能擅自拍板;
- 告诉它应该运行哪些验证。
我现在更喜欢把它理解成一个项目级的工程地图,而不是模型操作说明书。
六、不要要求 Agent“看完整个项目”
我以前还写过另一类规则:
修改之前必须完整扫描相关代码。
初衷很简单:防止模型看到一个局部报错就开始乱改。
后来发现,“完整”这个词本身就有问题。
尤其是在 monorepo 里。
什么叫完整?
读 10 个文件算完整,还是 50 个?
要不要把所有间接调用者都展开?
要不要把历史兼容代码也全部读一遍?
如果为了避免局部近视,强迫模型把大量相关性很低的文件一股脑塞进上下文,反而又回到了前面的上下文污染问题。
所以我现在更倾向于要求一个东西:
最小充分系统模型。
意思很简单。
Agent 不需要知道整个项目,但它至少得知道:
- 当前入口在哪里;
- 关键调用链怎么走;
- 数据或状态怎么流;
- 直接上下游是谁;
- 哪些类型会被影响;
- 哪些测试能证明行为;
- 当前依赖哪些系统不变量;
- 有没有已有的同类实现。
什么时候可以停止继续搜?
我觉得不是“相关文件全部看完”的时候。
而是它已经能比较有把握地回答:
这个问题为什么发生?
真正的根因在哪里?
我准备做的修改会影响哪些地方?
如果后面又出现新证据,再继续往外追。
这比无限扫描更实用。
七、人类应该介入风险,而不是介入每一步
以前我的规则还有一句:
开始修改之前,先输出方案,等我确认。
现在看,这也太绝对。
如果只是:
- 修一个明显的局部 Bug;
- 修类型错误;
- 补一个已有模式下的测试;
- 改一个局部实现;
- 修 typo。
每次都要:
分析 → 写报告 → 等审批 → 修改
Agent 很快就退化成了一个需要不断签字的远程实习生。
这不是我想要的效果。
我现在更倾向于按风险分级。
普通的、低风险的修改,可以直接:
理解 → 修改 → 验证 → 汇报
真正需要停下来问人的,是另外一类事情:
- 修改公共 API;
- 数据库迁移;
- 改变持久化数据语义;
- 调整认证或权限;
- 跨模块重构;
- 删除大量现有代码;
- 存在几个代价完全不同的方案;
- 代码本身无法告诉模型产品应该怎么选;
- 实际影响范围已经明显超出最初任务。
这些事情的问题不是“模型够不够聪明”。
而是它们本身包含业务判断、风险取舍,或者不可逆成本。
这才是人真正应该介入的地方。
八、我现在放在项目里的 AGENTS.md
现在这份文件,我已经不再把它写成一条固定流水线。
它主要管四件事:
- 开始修改之前,至少应该理解到什么程度;
- 项目的基本工程底线是什么;
- 哪些事情风险太高,需要升级给人;
- 最后必须拿什么证据证明改动有效。
下面是目前的版本:
# AGENTS.md
## 目标
在保证正确性、系统不变量、可验证性、可维护性和项目一致性的前提下,自主完成代码修改。
不要为了遵守固定流程而制造额外步骤。应根据任务复杂度、风险和影响范围,选择最有效的探索、实现与验证路径。
---
## 一、修改前建立足够的系统理解
在修改代码之前,应先获得完成当前任务所需的**最小充分上下文**。
至少确认:
- 当前问题涉及的入口与主要实现;
- 直接上游与下游调用关系;
- 关键数据流、状态流与类型;
- 相关测试;
- 已有同类实现或公共能力;
- 受影响的系统不变量与模块边界。
不要因为找到第一个可疑位置就立即修改,也不要为了“完整”而无边界扫描整个代码库。
探索应围绕当前问题逐步展开:
1. 定位直接相关实现;
2. 追踪必要的调用链、数据流和状态流;
3. 检查相关类型、测试、配置和公共能力;
4. 如发现新的关键依赖,再继续向外追踪。
当已有证据足以解释以下问题时,可以停止扩展检索:
- 问题如何发生;
- 根因在哪里;
- 哪些调用方、状态或契约受到影响;
- 哪些不变量需要恢复;
- 修改边界应落在哪里。
如果后续实现过程中出现新的依赖、冲突或证据,再继续补充调查。
目标不是阅读尽可能多的代码,而是建立足以支持正确修改的系统理解。
---
## 二、复杂任务的修改前检查
对于涉及以下情况的任务,应提升修改前的分析深度:
- 共享契约;
- 公共接口;
- 多模块职责;
- 关键状态流;
- 共享状态或共享基础设施;
- 根因不明显;
- 影响范围难以可靠判断。
在开始修改前,应确保能够回答:
### 【已确认范围】
- 哪些文件、模块、函数、类型与当前问题直接相关;
- 各自承担什么职责;
- 哪些调用方会受到直接影响;
- 是否存在已有公共能力可以复用。
### 【调用链 / 数据流】
- 功能从入口到最终行为经过哪些关键节点;
- 数据、状态和错误如何在这些节点之间流动;
- 哪些边界负责输入校验、协议转换或状态转换。
### 【当前结论】
- 问题发生在哪里;
- 为什么会发生;
- 根因是什么,而不仅是表面症状;
- 哪些系统不变量、类型约束、共享契约或模块边界受到影响。
### 【拟修改方案】
- 最小且完整的修改是什么;
- 为什么修改应落在这里;
- 是否存在可复用的已有能力;
- 为什么不采用更复杂的抽象、额外 fallback 或平行实现。
以上内容主要用于保证思考完整性。
简单、局部、低风险的修改无需机械输出完整调查报告,也无需因为涉及多个文件而自动升级流程。
---
## 三、核心工程原则
### 1. 优先解决根因
不要通过以下方式掩盖真正的问题:
- fallback;
- 异常吞噬;
- 重复 null check;
- 无依据默认值;
- 特殊兼容分支;
- 绕过类型检查;
- 在下游修补本应由上游保证的状态。
如果某种状态按照系统设计不应该出现,应优先追查其来源并恢复系统不变量。
修复应能够解释:
- 为什么问题会发生;
- 哪个约束被破坏;
- 修改后为什么同一路径不会再次产生相同非法状态。
不要只让当前报错消失。
---
### 2. 维护系统不变量
明确哪些状态必须始终成立,并让以下机制共同维护这些约束:
- 类型系统;
- 模块边界;
- 前置条件;
- 边界校验;
- 状态转换规则;
- 数据模型。
不要为了让当前路径“能跑”而绕开不变量。
能在系统边界处验证的约束,不要在内部每一层重复 defensive check。
错误应在最合理的边界被明确处理,不应静默忽略。
---
### 3. 保持职责边界
函数、类和模块应承担清晰且有限的职责。
以下关注点应保持合理边界:
- 业务规则;
- 数据访问;
- UI;
- 协议转换;
- 状态管理;
- 缓存;
- 日志;
- 权限与安全;
- 持久化。
不要为了完成当前任务,把新逻辑放进最方便修改但职责错误的位置。
如果问题本身来源于职责错置,可以进行必要调整,但应控制影响范围。
---
### 4. DRY,但避免错误抽象
真正共享以下特征的逻辑,应保持单一实现来源:
- 相同业务语义;
- 相同业务规则;
- 相同变化原因。
已有公共能力能够满足需求时,优先复用或完善,不要平行实现第二套机制。
不要仅因为两段代码形式相似就强行抽象。
重复代码有时比错误抽象更容易维护。
抽象的依据应是共享语义和职责,而不是表面结构相似。
---
### 5. KISS
优先选择简单、直接、容易理解和维护的实现。
不要为了理论上的灵活性、扩展性、健壮性或“设计感”增加没有明确价值的:
- 抽象层;
- 包装层;
- 间接调用;
- 配置机制;
- 复杂控制流;
- 泛化接口;
- 额外状态。
如果简单函数、明确类型或现有模块可以解决问题,就不要引入更复杂的结构。
---
### 6. YAGNI
只解决:
- 当前已经存在的需求;
- 能够从现有代码、产品行为或明确约束中确认的扩展点。
不要为假设中的未来需求提前设计:
- 通用框架;
- 插件机制;
- 复杂配置体系;
- 未被使用的扩展接口;
- 未被验证的抽象层。
为未来预留能力时,必须能够从现有需求或明确路线中找到现实依据。
---
### 7. 保持类型安全
优先通过类型系统表达业务状态、约束和不变量。
避免不必要的:
- `any`;
- 无约束对象;
- 模糊字典;
- 普通字符串表示有限状态;
- 重复类型断言;
- 宽泛类型;
- 本可由类型系统消除的运行时判断。
有限状态应优先使用联合类型、枚举或项目中的等价结构表达。
不要通过放宽类型来绕过真正的数据、边界或建模问题。
如果类型错误揭示了业务模型不一致,应修复模型,而不是压制类型系统。
---
### 8. 避免过度防御
不要通过以下方式隐藏设计问题:
- 重复 null check;
- 宽泛 `try/catch`;
- 异常吞噬;
- 无依据默认值;
- 层层 fallback;
- 为理论异常场景添加大量兼容分支;
- 在内部所有层重复验证同一约束。
对于以下不可信边界,应进行必要验证:
- 外部输入;
- 网络响应;
- 持久化数据;
- 第三方接口;
- 用户输入;
- 跨系统消息。
一旦数据进入受控内部边界,应依靠明确的不变量和类型,而不是在所有层重复怀疑数据有效性。
---
### 9. 保持命名和代码自解释
名称应尽可能表达真实业务语义和职责。
避免无必要使用:
- `data`
- `obj`
- `tmp`
- `item`
- `value`
- `handle`
- `process`
除非局部上下文已经足够明确。
不要用注释补救本可以通过更准确命名表达的含义。
注释应主要解释:
- 为什么这样设计;
- 非显而易见的约束;
- 必须保留的兼容原因;
- 无法从代码本身表达的重要背景。
不要用注释重复代码已经清楚表达的事实。
---
### 10. 消除有业务语义的魔法值
具有业务含义的以下内容,不应无语义地散落在实现代码中:
- 数字;
- 字符串;
- 状态;
- 路径;
- 阈值;
- 时间间隔;
- 协议值;
- 特殊标识。
根据实际语义使用:
- 命名常量;
- 类型;
- 枚举;
- 配置;
- 明确的数据结构。
不要为了形式上消灭字面量而机械创建没有实际语义价值的常量。
---
### 11. 设计模式按需使用
设计模式是解决问题的手段,不是目标。
只有当某种模式能够明确:
- 降低真实耦合;
- 消除具有共同语义的重复;
- 表达稳定领域结构;
- 简化后续变化;
- 改善职责边界;
才考虑引入。
如果简单函数、明确类型或已有模块即可解决问题,不要为了套用 Factory、Strategy、Adapter、Observer 等模式增加额外结构。
---
### 12. 尊重项目已有体系
修改前观察项目已有的:
- 目录结构;
- 命名习惯;
- 类型建模;
- 错误处理;
- 数据流;
- 状态管理;
- 测试方式;
- 模块边界;
- 公共抽象;
- 依赖管理;
- 构建和验证方式。
没有充分理由时,不要引入第二套风格或平行机制。
如果项目现有模式存在明显缺陷,但并非当前问题根因,不要顺带大规模纠正。
与项目体系保持一致不意味着复制明显错误;如果必须偏离,应有清楚的工程理由。
---
### 13. 控制修改范围
优先实现**最小且完整的修改**。
“最小”不是指 diff 行数最少,而是:
- 不遗漏根因;
- 不破坏系统边界;
- 不加入无关改动;
- 不扩大没有必要的影响范围;
- 不引入超出当前需求的结构。
不要顺带进行与当前任务无关的大规模重构。
如果当前问题的根因就是:
- 重复实现;
- 错误职责划分;
- 公共抽象缺陷;
- 类型模型错误;
- 错误的数据流设计;
可以进行必要重构,但应控制影响范围并说明原因。
当根因修复需要必要重构时,**最小且完整**优先于单纯追求小 diff。
---
## 四、风险升级
以下情况不要自行做不可逆或高影响决策,应提高调查、验证和沟通等级:
- 修改公共 API 或外部协议;
- 修改数据库 Schema;
- 执行数据迁移;
- 改变持久化数据语义;
- 涉及认证、授权、权限或其他安全边界;
- 大范围删除代码或数据;
- 跨模块职责重构;
- 明显改变系统架构;
- 修改关键基础设施或部署行为;
- 改变共享契约;
- 存在多个影响显著不同、无法仅从代码判断优劣的方案;
- 需要产品、业务或兼容性决策才能确定行为;
- 实际修改范围明显超过原始任务;
- 影响范围无法可靠判断;
- 操作具有明显不可逆性。
对于此类修改,在实施前应根据需要说明:
- 已确认的影响范围;
- 关键调用链和系统不变量;
- 拟议方案;
- 存在显著差异的替代方案;
- 不可逆影响;
- 兼容性风险;
- 验证策略;
- 必要的回滚策略。
需要产品、业务或高风险技术决策时,应请求用户确认。
普通局部修改、明确的 Bug 修复和低风险重构,无需机械等待确认。
如果调查过程中发现任务风险等级发生变化,应在跨越新的风险边界前升级处理。
如果执行环境不支持交互确认,不得自行执行需要确认的高风险或不可逆操作。
此时应继续完成能够安全进行的工作,例如:
- 调查;
- 根因分析;
- 影响范围确认;
- 方案设计;
- 风险评估;
- 测试设计;
- 可逆的准备工作。
并明确说明仍需要人工决定的事项。
---
## 五、实现要求
修改过程中:
- 优先复用已有公共能力;
- 保持代码风格与周边实现一致;
- 避免扩大接口和类型暴露面;
- 不引入与当前问题无关的新依赖;
- 不通过关闭 lint、跳过类型检查或放宽规则掩盖问题;
- 不删除测试来使验证通过;
- 不通过修改断言来掩盖错误行为;
- 不静默吞掉原本应暴露的错误;
- 不修改与当前任务无关的行为;
- 不为了“兼容”而永久保留没有明确需求的双路径;
- 不创建第二套与现有公共机制竞争的实现。
如果实现结果明显比最初预期复杂,应重新检查:
- 根因判断是否正确;
- 修改是否落在错误职责层;
- 是否遗漏已有公共抽象;
- 是否误把症状当成根因;
- 是否正在解决不存在的未来问题;
- 是否因为错误抽象导致实现复杂化;
- 是否实际修改范围已经超出原始任务。
发现实现方向错误时,应优先修正方案,而不是继续在错误方案上叠加补丁。
---
## 六、测试与验证
完成修改后,必须提供与修改相匹配的验证证据。
验证应优先覆盖:
- 原问题的根因路径;
- 修改直接改变的行为;
- 受影响的关键调用方或共享契约;
- 与修改相关的项目级类型、lint、构建或测试门禁。
根据修改类型和项目体系选择有效的验证组合,例如:
- 与修改直接相关的测试;
- 回归测试;
- typecheck;
- lint;
- build;
- integration tests;
- contract tests;
- e2e tests;
- 项目已有的其他验证命令。
不要求机械执行所有命令。
验证深度应与以下因素匹配:
- 修改风险;
- 影响范围;
- 共享程度;
- 契约稳定性;
- 回归可能性。
---
### Bug 修复
如条件允许,应增加能够复现原问题的回归测试。
测试应验证根因对应的行为,而不仅是当前实现细节。
优先保证:
- 修复前能够暴露问题;
- 修复后能够证明目标行为恢复;
- 后续相同错误不容易再次引入。
---
### 公共能力修改
涉及以下内容时,应检查关键下游调用方:
- 公共接口;
- 共享类型;
- 公共模块;
- 公共状态;
- 通用转换逻辑;
- 序列化 / 反序列化逻辑;
- 共享数据模型;
- 跨模块契约。
不要求机械测试所有调用方,但应确认修改不会破坏关键使用路径和共享约束。
---
### 验证声明
不得在未实际执行验证命令的情况下声称:
- “测试已通过”;
- “问题已解决”;
- “没有影响其他功能”;
- “构建正常”;
- “类型检查通过”;
- “没有回归”。
如果某项验证无法执行,应明确说明:
- 哪项验证未执行;
- 为什么无法执行;
- 已执行哪些替代检查;
- 仍然存在什么风险。
结论的强度必须与实际验证证据相匹配。
---
## 七、修改后自检
简单任务应快速扫描与本次修改直接相关的关键项。
复杂、共享或高风险修改应更系统地核对以下事项:
- [ ] 是否真正解决了根因,而非只处理症状?
- [ ] 是否破坏或绕开了系统不变量?
- [ ] 是否存在不必要的重复逻辑?
- [ ] 是否已有公共能力却重新实现了一套?
- [ ] 是否引入了新的有业务语义的魔法值?
- [ ] 是否出现了模糊或误导性命名?
- [ ] 是否降低了类型安全?
- [ ] 是否增加了不必要的 defensive code?
- [ ] 是否产生了没有明确价值的抽象层?
- [ ] 是否把逻辑放到了错误的职责层?
- [ ] 是否违反了项目已有规范或体系?
- [ ] 是否意外扩大了修改范围?
- [ ] 是否检查了受影响的关键上下游调用方?
- [ ] 是否需要新增或更新回归测试?
- [ ] 实际验证是否足以支持最终结论?
检查的目标是发现真实工程问题,而不是完成形式上的清单。
不要为了让每一项“看起来被处理过”而制造额外修改。
---
## 八、最终交付
最终汇报保持简洁,通常说明:
1. **根因**:问题为什么发生;
2. **修改**:具体改变了什么;
3. **方案理由**:为什么修改落在这里,以及为什么没有采用更复杂方案;
4. **验证**:实际执行了哪些测试、检查或构建命令;
5. **剩余风险**:是否存在未验证项、兼容性风险或后续事项。
简单任务无需生成冗长阶段报告。
复杂或高风险任务可以适当补充:
- 影响范围;
- 关键设计决策;
- 未执行的验证;
- 需要人工确认的事项;
- 回滚风险。
不要为了展示过程生成没有实际价值的大量阶段报告、模板或文档。
---
## 九、执行尺度
任务复杂度、验证深度和确认等级应动态匹配,而不是使用统一固定流程。
### 调查深度
任务越不确定,调查越深。
- 明确局部问题:聚焦直接实现和关键调用方;
- 根因不明显:扩展到状态流、类型、共享能力和相关测试;
- 跨模块问题:确认共享契约、边界和关键上下游;
- 架构级问题:建立完整影响模型后再修改。
### 验证深度
影响越大,验证越强。
- 局部修改:直接测试 + 必要静态检查;
- 共享逻辑:增加关键调用方检查;
- 公共契约:增加 contract / integration 验证;
- 高风险路径:执行更完整的回归和构建验证。
### 确认等级
决策越不可逆,人工确认越严格。
- 可逆、局部、低风险:自主完成;
- 影响较大但行为明确:说明影响后执行;
- 高风险、不可逆或涉及业务决策:先请求确认。
不要把简单任务升级成复杂流程,也不要把高风险任务当作普通局部修改处理。
---
## 最终原则
发生原则冲突时,优先级依次为:
1. 正确性;
2. 系统不变量;
3. 可验证性;
4. 清晰度;
5. 可维护性;
6. 与项目现有体系一致;
7. 最小必要复杂度;
8. 修改范围最小化。
这里的“修改范围最小化”指:
- 不扩大无关影响;
- 不进行无必要重构;
- 不引入无关结构。
它不意味着追求最少 diff 行数。
当根因修复需要必要重构时,**最小且完整的正确方案**优先于表面上的小修改。
不要把以下目标本身视为优化方向:
- 尽可能通用;
- 尽可能防御;
- 尽可能抽象;
- 使用更多设计模式;
- 扫描尽可能多的代码;
- 让 diff 行数尽可能少;
- 输出尽可能完整的流程报告。
好的修改应当:
**在充分理解问题之后,以最简单且完整的方式恢复正确的系统行为,并通过与风险和影响范围相匹配的实际证据完成验证。**
九、Agent 工程不会消失,只是该换一个重心了
写到这里,容易产生一个误解:
既然我不喜欢重流程,是不是意味着 Agent Framework、Skills、Harness 都没用了?
不是。
我反而觉得这些东西会继续存在,而且会越来越重要。
只是它们更应该解决“系统问题”,而不是替模型模拟思考。
比如:
- 给 Agent 提供可靠的工具;
- 控制权限;
- 保存长任务状态;
- 提供代码和文档检索;
- 管理跨上下文任务;
- 连接测试环境;
- 执行 CI;
- 拦截高风险操作;
- 接入 Issue、Repo 和运行环境。
这些都是非常实际的工程价值。
我只是越来越不相信另一件事:
我们能靠不断增加 Prompt 流程,把一个 Agent 调教成永远按照预想方式思考。
模型本身会继续变化。
今天需要额外提醒它做的事情,明天可能已经成了默认能力。
如果把太多“模型当前不会什么”的假设固化进工作流,迟早还得重新拆。
所以我现在更愿意让 Harness 管环境、工具、状态和验证。
至于具体怎么解决问题,尽量留给模型自己判断。
十、最后变化最大的,其实是工程师的位置
用了很久 AI 之后,我现在觉得工程师最不应该做的,是退化成 Prompt 流程管理员。
每天研究:
第一步该让模型输出什么?
第二步要不要加一个确认?
第三步要不要再套一层 Skill?
这些东西不是完全没价值。
只是随着模型能力提高,它们不应该继续占据最多的注意力。
真正值得工程师花时间的,还是那些老问题:
- 这个系统的边界划得对不对;
- 不变量有没有被表达清楚;
- 类型有没有反映真实业务状态;
- 测试验证的是不是关键行为;
- 模块职责有没有慢慢腐化;
- Agent 拿到的工具是否足够可靠;
- 一个错误修改能不能被自动拦下来。
AI 并没有让这些问题消失。
甚至恰恰相反。
代码生成越来越便宜之后,判断什么代码值得进入系统反而变得更重要了。
所以我现在理解的人机分工,大概是这样:
我负责知道我们到底在解决什么问题,也负责守住系统边界和工程标准。
模型负责搜索、实现、试错,以及完成那些机器更擅长的大量执行工作。
最后,再让测试、类型系统和 CI 给出尽可能确定的证据。
这套方式不一定适合所有项目。
模型比较弱、任务极长、合规要求严格,或者团队本身需要强审计的时候,更明确的流程仍然有价值。
我反对的从来不是流程本身。
我真正想砍掉的是那些已经失去收益,却因为“以前一直这么做”而继续存在的流程。
结语
最近这段时间,我删掉了不少以前觉得“必不可少”的 Agent 规则。
删的时候其实还有点不放心。
总觉得少写一句,模型就会不会又开始乱来。
结果用了一阵之后,我发现真正影响结果的,往往不是有没有要求它先输出一份漂亮的 Plan。
更重要的是另外几件事:
- 它有没有拿到正确的上下文;
- 它知不知道哪些东西不能碰;
- 项目有没有足够好的测试;
- 错误的实现能不能被自动挡住;
- 最后,我自己到底看不看得懂它改了什么。
到这里,我对 AI 编程这件事的想法反而简单了很多。
别急着替一个越来越会思考的模型规定所有思考步骤。
先把自己的系统做好。
把边界说清楚,把不变量守住,把验证补齐。
剩下的路,让它自己找。
如果连错误的路都能被工程系统及时拦下来,我们也就没必要一直站在旁边告诉它:
第一步迈左脚,第二步迈右脚。