我的 AI-Native 软件开发工作流
时隔半年,更新一下我的 AI 原生工作流。
在早期需要给 AI 加很多约束,随着模型变强,过重的 Harness 又会变成上下文噪音。现在更值得讨论的,已经不是某个 Coding Agent 的功能清单,而是怎样把不同能力的模型和工程流程组合起来。
我的工作流已经稳定下来了。模型和工具会继续换,但编排、判断、执行、审查、验收这几个环节基本不变。
模型选择
我的主模型会选 Opus 级别的模型,负责理解全局、拆解问题和编排工作。它要看懂需求、约束、依赖和当前状态,处理的是判断密度高的任务。
Fable 级别的模型放在 advisor 位置,通过工具接口按需调用。主模型需要帮助时,把问题和相关执行记录交给它,拿到建议后继续判断和执行。
比如架构选择迟迟定不下来,或者审查意见互相冲突,可以请 advisor 提供另一种判断。这个组合保留了强模型的帮助,也避免让所有执行步骤都等待多个模型讨论。
执行模型选择 DeepSeek-V4-Flash 这一类快速、便宜、能持续干活的模型。它处理已经说清楚的 ticket,按任务说明修改代码,运行检查,返回结果和证据。执行模型不负责重新定义需求,也不应该因为实现困难就偷偷修改验收标准。
代码审查再用 GPT-5.6-Terra-Max 或 GPT-5.6-Sol 这一类模型。审查模型和执行模型分开,最好使用干净上下文。审查的目标不是让模型把 diff 重新描述一遍,而是找出行为错误、安全问题、并发问题、遗漏的验收条件和不必要的复杂度。
需求
开发从 grill 开始,逐轮追问会影响实现的未知项:用户是谁,边界在哪里,哪些情况明确不做,成功怎样判断,现有系统有哪些约束。
讨论结果要沉淀到 CONTEXT.md。需要记录的不是对话过程,而是已经作出的决定、术语、约束、开放问题和暂时没有选择的方案。后面的模型不应该依赖某一次聊天的记忆,而应该读取这份项目事实。
原型
沟通梳理完需求,我会先生成一个能实际操作的 live-demo 原型。文字里说清楚了,不代表双方想象的是同一个产品。把页面、操作和状态变化做出来,才能确认用户从哪里进入、怎样完成任务、失败时看到什么。
这个阶段先看交互是否符合需求。比如一个审批功能,文字里只有提交和审批两个动作,实际操作原型时,才容易发现提交后能否撤回、驳回后怎样修改、不同角色分别看到什么。这些问题应该在正式拆票前确认,避免执行模型各自补出一套业务解释。
原型暴露的新问题继续讨论,确认的决定写回 CONTEXT.md,再更新原型。live-demo 用于确认产品行为,如果暂时用了演示数据或模拟接口,要明确标出,不能把原型可操作当成真实业务已经完成。
等需求和原型对齐,再用 to-spec 生成实施规格,写清行为、影响范围、依赖、风险和验证方式。后续实现同时对照 spec 和已确认的原型:一份约束业务行为,一份让交互有可见的参照。
spec
我会按里程碑组织多份 spec,再把每个里程碑拆成足够细的 tickets。里程碑说明这一阶段能交付什么,优先级决定先做哪些价值更高的工作,依赖决定哪些工作必须先完成。高优先级的票也不能跳过前置条件。
每张票都要能独立说明目标、验收断言、项目命令和证据位置。Touches 记录允许修改的路径,Blocked by 记录前置任务。拆得够细的判断是:一个没参加需求讨论的执行 Agent,拿到任务说明就能开始,并且有办法证明自己做对了。只拆成数据库、后端、前端三张大票,通常还不够。
Touches 是并发准入的依据之一。除了修改路径不相交,还要确认任务依赖、接口约定和共享运行资源没有冲突,比如数据库、测试账号和端口。Touches 不明确时,应当按冲突处理,保守地串行执行。依赖关系写进 Blocked by,调度器只领取依赖已经满足的票。
这样拆出来的票,才适合交给不同执行 Agent。每个 Agent 在独立 worktree 和分支里工作,完成后返回执行回执,进入 Review,而不是直接宣布整项任务完成。合并时仍然要处理基线变化和 rebase。
实施
执行阶段遵守测试驱动开发,也就是 TDD。先写能暴露预期缺失的检查,确认它失败,再做最小实现,最后重构。每次只推进一个可以判断的行为。
VDD 是我采用的验证驱动开发方法,贯穿整个流程。写需求时先定义可反驳的断言,先确认检查真的能证明这件事,再实现并收集证据。检查通过不一定意味着功能正确。测试可能被跳过,可能收集到零个测试,也可能只有空断言,甚至还在检查已经退出使用的旧对象。
因此每个关键检查都要做一次反证:已知正确的输入能通过,故意制造一个目标缺陷后能因为正确原因失败,恢复后再次通过。只看绿色退出码,不足以证明门禁可信。
review
每张 ticket 实现后,先由独立的 code-review 找问题,确认问题后修复,再审查修复结果,通过后推进下一张可执行的票。单独处理一组审查问题时可以用 review-fix-loop;走 implement 的任务编排时,就遵守它自己的审查和合并门禁,不额外叠一套状态流程。执行者不能自己批准自己的代码。
所有 tickets 完成后,再做一次整体 Review。整体 Review 看跨票影响、接口一致性、数据和状态流转、权限边界、性能代价,以及实现是否仍然符合 spec 和 CONTEXT.md。逐票审查解决局部问题,整体审查解决组合问题,两者不能互相替代。
多模型交叉审查可以减少单一模型的盲区,但不能把最终判断外包给模型。几个模型都没有发现问题,只能说明这些审查路径没有发现问题,不能证明代码一定正确。
验收
测试、类型检查和构建各自只能证明一部分事情。用户能不能完成一个任务,要通过真实界面、CLI 或 API 走一遍。网页用 browser 做真实操作,API 用 curl 调用,命令行就启动真实程序观察输出和状态。
我对 user-validate-flow 的要求,是覆盖完整的用户验收条件,不局限于仓库里已有的 E2E 用例。E2E 本来也可以跑真实浏览器,覆盖是否更全要看实际断言,不能靠技能名字判断。
比如一个带权限的编辑功能,除了保存成功,还要验证无权限用户被拒绝、刷新后内容仍然存在、失败时没有产生半份修改。这是验收设计的例子,不是多点几次页面就自然获得的覆盖。每条断言都要留下命令结果、截图或状态证据,必要的稳定路径再固化成回归用例。
如果 API 返回 200,但返回内容不符合用户目标,验收仍然失败。如果页面能打开,但用户无法完成关键动作,也不能算通过。实现错误回到修复,需求歧义回到讨论,缺少凭据或权限则记录阻塞原因。
开发方式
一种方式是远程 issue 和 PR:issue 记录任务,PR 提交改动,CI 跑检查,Review 和合并留下可追溯记录。团队协作时,这套方式比较自然,但远程操作和状态同步也会拉长每轮等待。
另一种方式是本地文件:spec 和 tickets 放在仓库里,执行者直接读取文件推进。这时需要自己区分开发、测试和生产环境,隔离并行任务的数据、端口和进程,并配置本地检查与 CI 入口。把任务放进 Markdown,不会自动获得这些能力。
为了保留 issue、PR 的协作方式,又减少远程往返,我开发了 git-forge。它把 issue、PR 等记录保存在本地 Git 的 refs/forge/* 事件链中,PR 使用不可变快照,合并需要 Review 批准和 CI 成功。它运行项目配置的本地检查命令,不是远程 CI 平台的完整替代品,forge 状态也不会随普通的 push、pull 自动同步。
两种方式的差别主要是基础设施,不是工程纪律。远程有 CI,不代表可以跳过验收;本地没有远程,也不代表可以直接改主分支。
Harness
harness-init 会在项目开始前搭好仓库控制面:AGENTS.md、项目命令、知识入口、权限边界、检查入口和验收约定。规则、检查和门禁是三件事。把规则写进文档,只能说明规则存在;检查要实际运行到目标文件;门禁还要真的阻止后续操作。
这也解释了为什么我不把 Harness 理解成一大段 system prompt。它还包括可找到的项目知识、可操作的运行环境、确定性的 hook、可观察的状态、可信的验证器,以及把重复错误写回项目规则的机制。模型变强后,可以删除无效的提示词,但不能删除真正承担边界和证据的工程约束。
Loop
我的 loop engineering 负责按任务状态推进开发:领取依赖已满足的票,执行、审查、修复、验证,通过后更新记录,继续处理下一张可执行的票。独立任务可以并发,有依赖的任务等待前置结果。
流程中断后,需要能从记录里看出哪些票已验收、哪些还在修、哪些缺外部条件,不能只靠模型记住上一轮对话。模型说完成了但没有验证证据,就不能推进任务;缺凭据或需要人作业务决定时,也应该明确停下来,不能无限重试。
上一阶段被接受的产物,成为下一阶段的输入。意图进入设计,获批的规格进入构建,带审查结论的 PR 进入流水线,运行中的问题再形成下一条需求。
工作流
工具会变,软件工程基本功不会一起消失。模型能力提升后,可以减少一部分流程约束,但权限、发布和验收边界仍然需要保留,需求判断、系统理解、验证设计和人工 Review 也需要有人负责。
边界清楚的小改动,可以直接由主 Agent 执行,不必每次都组织多个模型。复杂任务才需要里程碑、隔离执行和独立验收。下一次换模型时,我会拿相同的真实任务比较完成质量、总成本、等待时间和人工介入次数,决定替换哪个位置,而不是连工作流一起推倒重来。