ai agent 真实项目开发工程实践

我为什么做 engineering-workflow

我开始认真用 AI agent 写代码之后,很快意识到一个问题:AI 的能力已经不只是"补全一段函数"了。它可以读仓库、改多处文件、写测试、跑命令、解释错误,甚至能在一个长任务里连续推进好几步。

这当然很有用。但越是把 AI agent 放进真实工程里,我越发现:真正危险的不是它不会写代码,而是它太容易跳过工程过程。

一、我的痛点:AI 写得快,但工程过程很容易被跳过

在 demo 或一次性脚本里,AI 写得快通常就是优点。但在真实项目里,快会把很多问题藏起来。

它能快速找到文件,快速补逻辑,快速写测试,也能用很确定的语气告诉你"已修复"。但真正把代码合进去、跑起来、过几天再改同一块逻辑时,问题会浮出来:

  • 需求里明明提到了边界条件,代码只做了 happy path。
  • 方案写得挺完整,最后实现漏了其中几条。
  • 测试看起来不少,但测的是内部 helper,不是用户真正触发的入口。
  • bug 没有稳定复现,agent 先猜了一个修复。
  • 线上问题只测了更低层函数,没有走真实 API / job / CLI 入口。
  • agent 说"完成了",但没有刚刚跑过 lint、test 或同路径验证。
  • 下一次会话重新开始,又要解释一遍项目术语、测试命令、哪些目录不能碰。

这些问题单独看都不新。人写代码也会犯。但 AI agent 的速度会放大它们:它可以在你还没来得及确认需求边界时就把实现写完,也可以在没有复现 bug 的情况下给出一个看起来合理的修复。

所以我真正想解决的不是"让 AI 更会写代码",而是让 AI 少猜、少漏、少自作主张,并且把每一步的工程证据留下来。

二、于是我做了 engineering-workflow

我把这套实践整理成了一个仓库:

https://github.com/Mickls/engineering-workflow

它是一个面向 Codex / AI agent 协作的工程工作流配置,主要包含两部分:

  • 一个全局 AGENTS.md 模板,用来定义常驻原则、指令优先级、工作产物边界、skill 路由和硬门禁。
  • 一个 engineering-workflow Codex plugin,里面按场景拆了多个 skills,比如项目初始化、需求分析、普通诊断、线上问题排查、原型、需求拆分、架构审查、编码规范、测试策略、工程交接和最终验证交付。

它不是一个框架,也不是一个代码生成器,更不是"神奇 prompt"。它做的事情更朴素:把我希望 AI agent 遵守的工程过程拆成可触发、可复用、可检查的流程。

这套工作流有几个基本判断:

  • 不是所有任务都需要重流程,但高风险任务必须自动变严。
  • 需求不能只停留在自然语言里,要被拆成可追踪的约束。
  • 实现不能只靠 agent 自己觉得合理,要对齐已确认的 design/plan。
  • 测试不能只测内部函数,要证明用户关心的行为。
  • bug 不能靠猜,先建立反馈循环和复现证据。
  • "完成"不能靠语气,要靠刚刚运行过的验证命令和覆盖核销。

下面具体说这套东西怎么组织,以及每个部分在防什么问题。

三、我不想让 AGENTS.md 变成一本巨大的工程圣经

这个项目分成两层:

  • global/AGENTS.md:全局常驻规则,只放原则、指令优先级、工作产物边界、skill 路由和硬门禁。
  • plugins/engineering-workflow/skills/*:具体场景的工作流程,比如需求、诊断、编码、测试、交付。

这样拆是有原因的。AGENTS.md 如果写得太厚,会变成所有任务都要背的一本大书;真正执行时,agent 反而抓不到重点。所以我的做法是:全局文件只负责"什么时候该进入哪条流程",具体细节交给 skill。

比如:

  • 新项目第一次协作,走 project-setup
  • 非轻量需求,编码前先走 requirements-workflow
  • 普通 bug 或失败测试,先走 diagnosis-workflow
  • 线上问题、数据误删、重复调用、并发、幂等,走 incident-debugging
  • 进入代码实现,走 coding-standards
  • 涉及测试,走 testing-policy
  • 最终交付,走 verification-delivery

这套路由的意义是:不要让 agent 每次凭感觉决定工作方式。任务类型不同,风险不同,应该触发的门禁也不同。

四、.codex/engineering-workflow/ 是 agent 的工程记忆

我不希望每个会话都从零开始解释项目。因此这套工作流默认把中间产物放在目标仓库的:

text 复制代码
.codex/engineering-workflow/
  project/
  issues/
  adr/
  prototypes/
  debug/
  reports/
  handoffs/

这里面放的不是生产代码,而是 agent 协作需要的工程记忆。

project-setup 会沉淀几类东西:

  • project-profile.md:项目类型、主要语言、关键入口、高风险任务定义。
  • commands.md:lint、test、build、format 命令,以及哪些测试不稳定、哪些依赖外部服务。
  • domain.md / context.md:项目自己的领域术语,避免用户说 A、代码里叫 B、agent 又发明 C。
  • issue-workflow.md:这个项目怎么维护需求、状态和确认门禁。
  • out-of-scope.md:已经明确不要做、或者 agent 很容易反复误提的方案。
  • adr/:只记录难逆转、反直觉、有真实取舍的决策。

这一步很朴素,但很有用。AI agent 的上下文窗口再大,也不是项目记忆。把项目协作信息写下来,后续需求、测试、诊断、交付都能复用同一套事实。

五、我把"需求"拆成可核销的约束

我之前最常遇到的问题是:design 写得挺像样,最后实现还是漏。

原因是自然语言太容易让关键边界藏在段落里。比如一句话里可能同时包含:

  • 必须支持已有配置。
  • 不能影响旧数据。
  • 手动触发和自动触发语义不同。
  • root 目录外的文件必须被拒绝。
  • 第二次调用不能重复删除。
  • 没有权限时不能返回部分数据。

如果这些只停留在段落里,agent 实现时很容易只抓主线。

所以 requirements-workflow 要求非轻量需求在 design.md 里写"关键约束覆盖表":

ID 类型 关键约束 影响入口/调用链 实现要求 验证意图
C-001 behavior 用户提交后应创建任务 HTTP handler -> service -> repository 复用现有创建流程 断言返回值和 DB 状态
C-002 safety 重复提交不能重复创建 同上 使用已有幂等键 连续两次调用同一入口
C-003 compatibility 旧配置不需要迁移即可读取 config loader 保持兼容读取 用旧 fixture 验证

然后 plan.md 必须把每个约束映射到实施步骤:

约束 ID 实施步骤 预计代码落点 测试/验证入口 状态
C-001 Step 2 handler/service/repository integration test planned
C-002 Step 3 service idempotency check 同一 API 连续调用两次 planned
C-003 Step 4 config loader old config fixture planned

这个表不是为了好看,而是为了防漏。后面编码、测试、交付都要回头核销它。没有实现落点、没有验证证据、没有解释为什么不适用,就不能说完成。

这也是我现在理解需求的方式:需求不是一段描述,而是一组必须被实现和证明的约束。

六、为什么非轻量需求写完 design/plan 后必须停

这套工作流里有一个很硬的门禁:非轻量需求创建或更新 design.md / plan.md 后,当前回合必须停下来,让用户 review。确认之前不能编码、不能写测试、不能生成 migration/proto,也不能安排实现类工作。

这个规则看起来麻烦,但它是从 AI 协作里的一个真实风险来的:agent 很容易把"自己刚刚写出来的方案"当成已经被确认的事实,然后立刻继续实现。

对简单文案、注释、格式化、小范围无行为改动,当然不需要这么重。但只要涉及下面这些内容,就不能把用户确认省掉:

  • 行为变化。
  • 跨模块调用链。
  • API、proto、schema 或数据库结构。
  • 权限、租户、幂等、并发。
  • 数据删除、状态迁移、异步任务。
  • UI 独立流程。
  • 线上问题修复。

我希望 agent 在这里承担的是"调查和整理方案"的角色,而不是替用户做产品和工程决策。真正进入实现前,用户必须有机会看见:本次到底做什么、不做什么、哪些边界被覆盖、哪些风险需要接受。

七、UI 原型不是为了炫,是为了提前暴露理解偏差

如果是独立 UI 需求,工作流要求在确认 design/plan 前生成静态 HTML 原型。

这个要求也不是为了把流程搞复杂,而是因为 UI 需求特别容易"文字上理解一致,画出来完全不是一回事"。

原型默认放在:

text 复制代码
.codex/engineering-workflow/issues/<REQ-or-EPIC>/prototype.html

它只需要回答需求确认问题,不进入生产目录,不接真实接口,不跑生产级 lint/test/build,也不反复打磨。它至少要表达:

  • 默认态。
  • 关键操作态。
  • 空态、错误态、成功态里和需求确认相关的状态。
  • 如果会在窄屏使用,要覆盖移动端风险。

我对原型的定位很明确:它不是生产实现,是用最低成本把理解偏差提前暴露出来。

八、编码时我最在意三件事:复用、边界、少造东西

coding-standards 不是某种语言规范,它主要约束 agent 的编码习惯。

第一,先复用,再实现。顺序是:

  1. 项目已有 helper、service、repository、client、组件。
  2. 标准库、运行时、框架原生能力。
  3. 项目已经引入的第三方库。
  4. 自己实现。

新增依赖不是不能做,但要解释为什么已有能力不够,是否影响 lockfile、license、体积、兼容性和维护成本。

第二,校验放在真实边界。比如:

  • HTTP/RPC handler。
  • CLI 命令入口。
  • event/job 接收处。
  • webhook。
  • 前端表单提交。
  • public/exported API。
  • 文件、网络、数据库、序列化边界。

内部函数不要为了"保险"到处重复判空、判枚举、判权限。这样写出来的代码看似稳,实际上会让调用契约变模糊,也会制造一堆永远到不了的分支。正确做法是追到数据来源,看上游是否已经校验和标准化。如果安全性依赖上游,就在 plan、注释或交付说明里写清楚校验来源。

第三,不为了局部传值造中间类型。AI 很喜欢新建 DTO、interface、struct、class,好像这样更"工程化"。但如果只是在一条局部链路上传几个值,局部变量和直接参数往往更清楚。只有跨模块、异步、序列化、稳定 API 或复杂状态真的需要命名结构时,再定义新类型。

这几条背后的原则是:代码应该尽量贴近当前项目,而不是展示 agent 的抽象能力。

九、bug 不能靠猜,先建立反馈循环

diagnosis-workflow 处理普通 bug、失败测试、性能回退和本地可排查异常。它第一步不是改代码,而是建立反馈循环。

反馈循环可以是:

  • 从真实入口出发的 failing test。
  • curl / HTTP 脚本。
  • CLI 命令加 fixture。
  • 浏览器自动化脚本。
  • 事件或日志 replay。
  • 最小 harness。
  • property / fuzz / stress loop。
  • differential loop。
  • git bisect run harness。
  • 人机协作脚本。

关键不在形式,而在质量:

  • 是否能观察用户描述的问题,而不是附近另一个失败?
  • 是否足够快,可以支持多轮验证?
  • 是否稳定?如果不稳定,是否提高了复现率?
  • 断言是否具体到用户可见结果、数据状态或副作用?
  • 是否从真实入口或等价顶层入口触发?

有了反馈循环之后,才进入假设验证。工作流要求修改生产逻辑前列出 3-5 个可证伪假设:

text 复制代码
如果 X 是根因,那么观察或改变 Y 会让 Z 发生。

每次只验证一个假设。可以加临时 instrumentation,但要带唯一前缀,比如 [DEBUG-a4f2],交付前 grep 确认清理掉。

这个流程能防止 agent 根据报错位置直接猜修复。很多 bug 的根因不在最显眼的地方,尤其是调用链长、状态复杂、异步或缓存参与时。

十、线上问题比普通 bug 更严格

incident-debugging 专门处理线上问题、生产 bug、数据误删、重复调用、幂等、并发、状态错乱和事故排查。

它有四个硬门禁:

  • 复现门禁:已经从真实入口或等价顶层入口复现。
  • 根因门禁:已经用调用链和状态变化证明具体根因。
  • 修复门禁:只改已证明根因,不做无关重构。
  • 回归门禁:修复后重新运行同一条复现流程。

比如问题是"两次调用同一个接口导致误删除",那复现必须包含:

  1. 构造能触发问题的数据。
  2. 连续两次调用同一个 API/RPC/CLI/job 入口。
  3. 比较第一次和第二次之后的真实持久化状态。
  4. 断言误删、重复删除或状态错乱确实发生。

不能直接调用内部删除 helper,不能 mock 掉 repository 到看不见数据库状态,也不能只断言某个 service 返回值。线上问题的证明对象不是"内部函数表现",而是"真实入口触发后的真实副作用"。

这条很重,但值得。因为线上问题最怕"修了一个看起来相关的地方",过几天同类事故又出现。

十一、测试不是越多越好,而是证明力越强越好

testing-policy 里我最看重的一句话是:一个有证明力的链路测试,优先于多个低价值内部测试。

测试应该优先从这些入口进入:

  • HTTP/RPC handler、route、controller、resolver。
  • event/job 接收处。
  • CLI 命令。
  • 页面或用户操作入口。
  • public package/module API。

低层 service 或 helper 不是不能测,但要满足一个条件:它本身就是稳定 contract,或者外层没有相关参数绑定、权限、租户、事务、副作用和错误映射,或者已有测试已经覆盖外层。

我不想要这种测试:

  • mock 掉核心逻辑,然后断言 mock 被调用。
  • 断言内部函数被调用次数。
  • 断言偶然排序、局部变量形状、日志文本。
  • 为了覆盖率把同一条路径拆成一堆没有独立风险的测试。
  • 在测试期望里复制生产逻辑,导致生产和测试一起错。

我想要的是这种测试:

  • 从真实入口或稳定 public contract 进入。
  • 能观察用户关心的返回值、错误、数据库状态、消息、副作用。
  • 能覆盖关键约束 ID。
  • 失败时能说明真实行为错了,而不是实现细节变了。

如果是 bugfix,回归测试还要先证明旧逻辑会失败。没有复现旧失败,就不要直接写一个"修复后应该通过"的测试来自我安慰。

十二、大需求要按用户行为切,不要只按技术层切

很多计划会自然拆成:

  • 改数据库。
  • 写 DAO。
  • 写 service。
  • 写 API。
  • 写前端。
  • 写测试。

这种拆法在执行上有用,但如果只有这种拆法,就会出现一个问题:每一步单独完成后都不能被用户验证,也不能说明某条需求已经闭环。

所以 issue-slicing 要求优先拆垂直切片。每个切片尽量经过完整调用链,完成后有可观察结果,并关联约束 ID。

例如不要只写:

text 复制代码
Slice 1: database
Slice 2: backend
Slice 3: frontend

而应该尽量写成:

text 复制代码
Slice 1: 用户可以创建草稿,覆盖 C-001/C-002,可通过 API 和 DB 状态验证
Slice 2: 用户可以提交草稿进入审核,覆盖 C-003/C-004,可通过状态机和消息副作用验证
Slice 3: 审核失败后用户可修改重提,覆盖 C-005/C-006,可通过页面流程和 API 验证

技术层任务可以作为支撑任务存在,但最终要服务于某个可验收的用户行为。

十三、架构审查不等于顺手重构

architecture-review 只在明确要求架构审查、模块边界分析、测试 seam 分析,或者某片代码反复难改时使用。它默认不修改生产代码。

它关注的问题很具体:

  • 模块 interface 是否清晰,还是只是浅层转发?
  • 复杂度是否集中在可测试 seam 后面?
  • 调用方是否需要知道太多实现细节?
  • 错误、事务、权限、状态机、副作用是否散落?
  • 改一个行为是否要同时动很多无关位置?
  • 测试是否只能测内部函数,无法从稳定入口证明行为?

输出也不是"大重构建议",而是候选项:

  • 涉及文件。
  • 当前摩擦。
  • 建议方向。
  • 预期收益:locality、leverage、testability。
  • 风险。
  • 推荐强度。

如果用户选择其中一个候选项,再进入需求流程或切片流程。这样可以避免 agent 在"审查"时顺手改一堆架构代码。

十四、交付时必须把证据说清楚

verification-delivery 解决的是最后一步:什么时候可以说完成。

我的规则是:没有刚刚运行过相关验证,就不要说"已完成""已修复""通过了"。

编码后至少要考虑:

  • lint:全项目太贵时,可以只跑改动文件或受影响 package,但要说明范围。
  • test:运行与本次行为变化相关的测试,优先覆盖调用链。
  • build/typecheck/compile:接口、依赖、生成代码、配置变化时必须考虑。
  • bugfix:需要修复前复现证据、根因证据、修复后同路径验证。
  • 非轻量需求:需要核销关键约束覆盖矩阵。

最终回复也不能只写"完成了"。应该包含:

  • 改了什么。
  • 关键文件路径。
  • 跑了哪些命令,结果是什么。
  • 哪些没验证,为什么。
  • 剩余风险。
  • 是否需要用户继续确认。

这不是官样文章。它是在保护协作信任。AI agent 真正可靠,不是因为它语气自信,而是因为它知道自己凭什么下结论。

十五、这套工作流实际改变了什么

如果只看表面,这套项目里有很多规则:skill 路由、design/plan、覆盖矩阵、原型、反馈循环、测试策略、交付门禁。

但它真正改变的是 AI 协作的默认姿势。

以前默认是:

text 复制代码
用户提需求 -> agent 找文件 -> agent 改代码 -> agent 写测试 -> agent 说完成

现在我希望默认变成:

text 复制代码
用户提需求
-> agent 判断任务类型和风险
-> 读取项目上下文和相似实现
-> 追踪入口、调用链、数据来源和副作用
-> 提炼关键约束
-> 写 design/plan 或 no-doc 最小约束清单
-> 必要时做原型或切片
-> 用户确认
-> 实现时逐条对齐约束
-> 从真实入口或稳定 contract 测试
-> 回填实现落点和验证证据
-> 交付时说明验证、未验证项和风险

这条链路看起来长,但不是每个任务都要完整跑一遍。轻量任务可以轻量处理。真正重要的是:一旦任务风险上来,流程要能自动变严。

十六、我对 AI 编码的理解

AI agent 最大的价值不是替我按键盘,而是把工程执行力放大。它可以快速阅读代码、整理调用链、生成候选方案、补测试、跑命令、做重复验证。但前提是,它不能在关键问题上悄悄替我猜。

所以我更关心的不是"怎么让 AI 一次写对",而是"怎么让 AI 写错时更早暴露,怎么让遗漏更难发生,怎么让完成声明必须有证据"。

这套 engineering-workflow 本质上是在给 AI 协作加几个工程护栏:

  • 用项目上下文减少重复解释和术语漂移。
  • 用需求约束表防止自然语言遗漏。
  • 用 plan 覆盖矩阵连接需求、实现和验证。
  • 用用户确认门禁阻止 agent 自我推进。
  • 用反馈循环和可证伪假设处理 bug。
  • 用真实入口测试证明用户行为。
  • 用交付验证约束"完成"这个词。

它不是为了让开发显得正式,也不是为了把所有任务都流程化。它只是承认一件事:AI 越快,越需要清晰的边界、证据和停顿点。

我现在越来越觉得,AI 时代写代码的能力,会从"我会不会实现这个函数",扩展成"我能不能设计一套让人和 agent 一起可靠工作的工程系统"。

代码最后当然还是要落到仓库里。但在代码落下之前,需要有人把需求变成约束;在代码落下之后,需要有人证明这些约束确实被满足。我的这套工作流,就是围绕这件事展开的。

相关推荐
CCC:CarCrazeCurator2 小时前
大模型核心注意力机制技术深度报告:MHA、MQA、GQA 与 MLA 技术原理、性能对比与场景适配
人工智能·机器学习·自动驾驶·transformer
卢卡上学2 小时前
CodeBuddy 与 WorkBuddy 完整联动方案,研发 + 办公双线提效!
人工智能·腾讯workbuddy·腾讯codebuddy
秋92 小时前
Python工程师面试常问提问和回答(AI工程化方向 · 2026版)
人工智能·python·面试
炎武丶航2 小时前
LeNet-5深度学习详解:从手写数字识别到代码实战
人工智能·python·深度学习·机器学习·ai·cnn·lenet
CIO_Alliance2 小时前
2026年度iPaaS集成平台及服务商综合能力评估与行业趋势分析(企业级AI化转型)
人工智能·ipaas·制造业·企业数智化转型·零售电商·ai+ipaas
星辰徐哥2 小时前
Python AI基础:Matplotlib与Seaborn数据可视化
人工智能·python·matplotlib
terry6002 小时前
2026滑动拼图验证码选型指南:AI对抗下的厂商对比与落地实测
大数据·人工智能·web安全·信息与通信·数据库架构
星辰徐哥2 小时前
Python AI基础:Python面向对象编程
开发语言·人工智能·python
可涵不会debug2 小时前
当AI学会了“讲故事“:我用魔珐星云做了一个沉浸式互动叙事Agent
人工智能