别再教聪明模型做小学数学:从「过程编排」到「质量约束」的 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

现在这份文件,我已经不再把它写成一条固定流水线。

它主要管四件事:

  1. 开始修改之前,至少应该理解到什么程度;
  2. 项目的基本工程底线是什么;
  3. 哪些事情风险太高,需要升级给人;
  4. 最后必须拿什么证据证明改动有效。

下面是目前的版本:

# 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 编程这件事的想法反而简单了很多。

别急着替一个越来越会思考的模型规定所有思考步骤。

先把自己的系统做好。

把边界说清楚,把不变量守住,把验证补齐。

剩下的路,让它自己找。

如果连错误的路都能被工程系统及时拦下来,我们也就没必要一直站在旁边告诉它:

第一步迈左脚,第二步迈右脚。