ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从vibe coding到harness engineering:打造可靠的AI编程自动化工作流

从vibe coding到harness engineering:打造可靠的AI编程自动化工作流

把一段话丢给AI编程助手,让它自己写代码、自己跑测试、自己改、再跑,今天凌晨入睡前扔进去的任务,早上醒来已经在仓库里躺好一份完整的改动、一套测试报告、一段“我为什么这么改”的说明。这种感觉第一次出现的时候,我心里不是兴奋,是警惕:它真的理解我在要什么吗?它有没有在某个角落制造我根本不知道的问题?

但用了半年之后,我越来越确定一件事:vibe coding 的真正上限,从来不在于AI能写得多快,而在于你有没有一套工程系统接住它。没有工程兜底,vibe coding 是烟花;有了 harness engineering,它才是能稳定运转的“夜间员工”。这篇文章不聊大模型原理,只聊我这几个月是怎么把一个原本“随缘”的AI编码流程,改造成能按部就班、隔夜交付的自动化中台,以及那些深夜踩进去再爬出来的坑。

1. 从“随手 vibe”到“可委托工程”:我到底在调整什么

先把话说明白:vibe coding 不是贬义词,我甚至觉得它是这几年编程体验里最有突破感的部分。它压缩的复杂度远超想象,但这也正是问题所在——它把本来由人脑承担的“语义调度”交给了概率生成,而你如果不在周边建立起约束,结果就是随机的。

1.1 vibe coding 的真正价值不在代码,而在意图压缩

我做一个功能时,多数时间消耗在哪里?不是敲键盘,而是把一堆模糊想法掰开揉碎,变成可实现的拆解。以前写代码,是在跟语言细节搏斗;vibe coding 之后,这种搏斗变成了“描述—生成—修正”的对话。我给它三句话,它给我一个可运行的原型,这种体验早期非常上头。

但上头之后很快就会遇到一个现实:你给的意图越模糊,它返回的结果就越发散。你可能想要的是“把支付回调里失败信息写得更容易排查”,它能给你重构成一个全新的内部框架;你可能只是想改个样式,它能顺手把你的构建配置也清理一遍。它不是不聪明,而是没有“边界感”。

所以我看 vibe coding 的重点从来不是“生成代码有多快”,而是“意图压缩”的有效性。什么叫有效?就是我在几行字里表达的隐性信息,能被完整翻译成可执行的要求,而不是被它们当做参考意见。

1.2 工程化要解决的三件事:意图、反馈、护栏

想通了这一点,我对自己要搭的“工程化”就有了方向。核心不是限制AI,而是给它三样东西:清晰的意图入口、可靠的反馈回路、刚性的安全护栏。

意图入口,解决的是“它到底要干什么”。这事不能靠自然语言自由发挥,必须落成结构化的任务描述,让AI每一次读取任务卡都处在同一个上下文。反馈回路,解决的是“它怎么知道自己干得对不对”。光靠代码能跑不够,还要有一堆可以自动断言的标准:测试过没过、覆盖率有没有跌、静态检查有没有新增告警、变更范围跟任务声明是否一致。

护栏,解决的是“哪些事绝对不能碰”。比如支付金额相关逻辑、数据库表结构、对外接口签名,这些在任务卡里直接写死,AI 看到的第一句话不是“去实现”,而是“你不许碰这些东西”。人可以被纪律约束,模型也一样,而且模型比人更需要白纸黑字的边界。

这三件事做到位,vibe coding 才能从“玩具”变成“工具”。下面就开始说具体架构。

2. 我的“24 小时劳动者”工作流架构拆解

我搭的这套东西,形态上不复杂,核心就是三个环节加一条人工干预线:任务池、代理循环、验收闸门。用一句话形容整体流程:任务进入池子,代理挨个取走,跑完一圈后把结果交到验收闸门前,闸门全开才进入待人工合并区,闸门没开就把失败上下文送回代理继续修。

2.1 整体形态:任务池 + 代理循环 + 验收闸门

任务池的作用是“削峰填谷”。白天我可以随时把想做的事写成任务卡丢进去,晚上系统按优先级处理,而不是我坐在屏幕前等着它出结果。每天晚上睡之前看一遍池子,确认明天的队列合理,然后关灯,接下来就交给循环自己跑。

代理循环是干活的主力。它会经历“读卡、计划、改代码、跑窄测试、读错误、修复、再跑”的循环,直到窄测试全通过。注意,这个环节我不要求它直接跑全量测试,因为全量测试一次可能几分钟,如果连窄范围都没过,跑全量就是浪费算力。让子弹飞一会儿,先打中靶心附近的区域。

验收闸门是最后一道卡口。窄测试过了之后,代理会自己触发一轮完整验证:全量构建、全量单测、静态检查、覆盖率对比、变更范围核对。这些全部通过,它才能生成交付报告,把改动推到 review 分支。到这里,一次完整的“夜间劳动”才算是闭环了。

2.2 任务蒸馏层:让大模型在深夜也能抓住重点

这个环节是整个工程里投入产出比最高的一块,因为大多数 vibe coding 翻车,不是AI能力不行,而是需求描述根本不适合机器执行。

我现在的任务卡是强制用固定格式写的,缺一项就视为无效任务,不进池子。下面是我常用的模板:

任务编号: T-20241008-01 目标: 修正支付回调模块,使失败日志中包含可读错误码 背景: - 涉及模块: src/recharge/callback.py, src/recharge/errors.py - 问题现象: 当前失败回调统一输出 generic_error,排查成本高 - 参考资料: docs/recharge/error-codes.md 验收条件: 1. 成功与失败回调中均新增 error_code 字段 2. error_code 取值符合 error-codes.md 中的枚举定义 3. 新增单元测试,覆盖成功回执与失败回执两类场景 4. 全量测试通过,静态检查无新增告警 禁止事项: - 不改动支付金额相关逻辑 - 不改动对外接口签名 - 不修改数据库表结构

注意几个细节。第一,目标必须“可验证”,不能写“让日志更友好”,要写“包含字段”“符合枚举”“新增测试”,这些词的意思是:我不想靠感觉判断,我想靠断言判断。

第二,背景里的“参考资料”很重要。大模型最大的问题是不知道项目里有哪些文档、哪些约定,你要主动喂给它路径,让它自己去看,而不是让它凭空猜测。

第三,禁止事项写出来不只是给AI看的,也是给我自己看的。因为我发现一个很有意思的现象:当我写任务卡时把“不能改什么”也列出来,等于逼自己再想一遍这次改动的边界,经常会在这一步发现自己原本的需求描述里藏着没想清楚的地方。

2.3 执行层:编码、运行、翻车再翻身

代理循环的每一步,我都尽量让它可以“被撤回”。它在改代码之前,会先记录当前文件状态的 hash;每完成一个小改动,就打一个带任务编号的提交。这样即使后面改坏了,也可以干净地回滚到某一个节点。

执行循环大致是这么走的:

  1. 读取任务卡,定位涉及模块,先读相关文档和现有代码,弄清现状。
  2. 生成一份简短的修改计划——改哪几个文件,各自改什么,改动影响哪块逻辑。
  3. 按计划改代码,每次改动范围尽量小,保证一个提交解决一个点。
  4. 跑窄测试,比如只跑回调模块相关的测试文件。
  5. 如果失败,读取失败日志,定位问题,修复后重跑。
  6. 窄测试通过后,触发全量构建与全量测试。
  7. 生成交付报告,包含变更文件、测试结果、遗留问题说明。

这个环节里最容易炸的是第5步。模型进入“修复模式”后,有时候会陷入一种死循环:它改了一个文件,导致另一个测试挂了;再改另一个文件,又把刚刚修好的那个破坏了。这种循环消耗算力不算可怕,可怕的是它会在循环里逐渐偏离最初的目标,越改越偏,到最后你看提交记录都不认识它在干什么了。

我现在的处理是给循环加阈值:同一任务最多允许 N 次失败重试,超过之后立刻停止当前路径,回到最近一次“全绿提交点”,重新规划方案。宁可慢一点,也不允许它在一堆局部修复里越陷越深。

2.4 验收层:用代码接管代码的质量评审

验收闸门是我最有安全感的一道工序。以前人工审查代码,最耗精力的就是“确认测试真的过了”“确认没有引入无关改动”,现在这些全部自动化,而且是以代码的形式固化的。

我的验收清单长这样:

检查项判定标准不通过时处置
全量构建编译通过返回执行层修复
单元测试全部通过返回执行层修复
静态检查无新增告警返回执行层修复
覆盖率不低于基线要求补充测试
变更范围不得超出任务卡声明文件人工介入确认
安全扫描禁止出现高危模式自动拦截并通知

这六条里,变更范围检查特别有用。因为我发现AI经常出现一种“顺手改一下”的行为,比如任务只要求改A模块,它发现B模块有个类似的写法,顺手也改了。这种“顺手”在小改动里看不出问题,积累起来就是灾难。把变更范围纳入自动校验之后,这类行为基本就绝迹了。

验收层本质上是把“人的判断”拆成了一堆可以自动执行的规则。规则越具体,AI越容易对齐;规则越模糊,AI就越容易发挥。很多人在 vibe coding 时觉得结果不可控,其实不是模型不可控,而是你的验收标准根本没有被结构化。

3. 这半年踩出的深坑合集,希望你绕开

说完了理想架构,再说点真实的翻车现场。这个系统不是第一天就这么顺的,我经历了大量惨烈的半夜翻车,早上起来看一堆莫名其妙的提交记录,想掐死昨晚那个“太信任AI”的自己。

3.1 自我修复的假循环:明明在改,其实在原地

第一次遇到这种循环的时候,我还以为系统很努力。日志显示代理一晚上执行了四十多轮修复,每一轮都“发现新问题—修复—再测试”,看起来很勤劳,但仔细看,它一直在同样两三个测试之间来回横跳:修好了A测试,B测试挂了;修好了B测试,A测试又挂了。

为什么会这样?因为模型在局部修复时,往往只顾得上眼前这个失败点,没有余力做全局判断。它会用一个补丁去填补当前报错,但这个补丁触发了另一个原本正常的逻辑,于是下一轮又开始补另一个洞。这种打地鼠式的循环如果没人打断,可以持续到资源耗尽。

我现在对付这个问题有两招。第一,设置重试阈值,最多5轮;超过之后自动重置到最近一个全绿提交点,重新规划。第二,要求每轮修复之后输出“本轮改动影响范围”,强迫模型在提交前多想一层——这个动作在对话式编程里很难强制,但在工程化循环里可以把它写进提示词并要求结构化返回。

3.2 上下文爆炸:它忘了自己在做哪个任务

这个坑是随着任务变复杂才出现的。刚开始跑小任务没问题,后来我开始在一个会话里让它连续处理三四个相关任务,它就逐渐表现出“遗忘”:前面任务里确定的接口约定,后面完全没遵守;在一个文件里改着改着,突然开始改另一个毫不相关的文件。

后来我想通了,这本质上是上下文超载。模型的上下文窗口再大,也不如人去提取的摘要紧凑。它面对一堆代码、日志、任务描述、历史修改记录,注意力被稀释,自然就会跑偏。

解决办法是给代理“断舍离”。现在我的流程里,每个任务都是独立的会话,任务之间不共享上下文;每次进入新阶段时,让模型先输出“当前状态摘要”,把冗长的历史压缩成几条关键结论;外部上下文存成结构化文件,需要时再读取,而不是一股脑全塞进提示词里。这样它永远是在“读重点”,而不是在“回忆全貌”。

3.3 未经过验证的“自信”:测试通过不代表需求达成

最坑的一次经历,是代理给了我一份全绿的测试报告,我心满意足地合入了。结果上线第二天就出问题——它把需求理解错了。测试全过,是因为它给这个错误理解写了一套自洽的测试,不是需求变了,而是测试和实现一起跑偏了。

这种情况在人工开发里也有,但AI场景下更明显。模型有一种强烈的“自圆其说”倾向:它不会跳出来说“我不确定这个需求是不是你说的那个意思”,它倾向于默认自己的理解正确,然后给这个理解写测试、写实现,最后给你一份看起来非常完整的交付。

我现在应对这件事靠两条腿。第一,验收条件必须可断言化,比如“错误码符合枚举定义”这句话,我会加强为“枚举里没有的值不得出现”,这种可计算的标准越多,跑偏空间越小。第二,引入“红队复审”:专门的另一个会话,不看实现、只看任务卡,独立列出验收清单和风险点,拿这份清单去对照实际交付。简单说,写代码的和检查代码的不是同一个判断,模型也一样,独立上下文下的复核比同上下文下的自证可靠得多。

4. 让机器在夜里干活之前,先立好规矩

很多人在搭自动化工作流时,第一反应是“让它跑得越快越好”。我的经验恰恰相反:先设计失败,再设计成功。你要是不知道它失败时仓库处于什么状态,不知道日志存在哪里,不知道从哪个提交点可以干净回滚,那你根本不该让它半夜干活。

4.1 可失败、可追踪、可回滚的跑板设计

我给自己定的铁律是:模型产出的每一个改动,都必须可以单点撤销。实现方式其实很简单:

  • 每一轮自动改动都产生独立提交,提交信息强制包含任务编号。
  • 改动只能发生在独立分支,不允许直接推主干。
  • 每次开始修改前,自动记录受改动文件的 hash 清单。
  • 验收不通过时,整个分支可以一键弃用,回到起始提交。

有了这套规则,夜间运行最坏的结果也就是“这个分支废了”,而不是“仓库历史乱成一锅粥”。我会在任务卡里给每个任务安排一个“最大运行时长”,到点未完成就自动暂停,绝不无限消耗算力。

这样做的好处不只是安全,还有心理上的放松。我在睡觉前只需要确认一点:就算它整晚折腾失败,我明天也能在三分钟内回到初始状态。这个安全感比任何优化都重要。

4.2 留给人看的“交接单”应该长什么样

机器在夜里干活,人早上来看结果,中间需要一份高质量的“交接单”。我现在的交付报告是强制的结构化输出,而不是自由发挥。

任务编号: T-20241008-01 状态: 部分通过 结论: 错误码结构已增加,但失败分支未完全覆盖 变更文件: - src/recharge/callback.py (+32 -18) - src/recharge/errors.py (+5 -1) 验证记录: - 单元测试: 7/9 通过(两个失败回调分支场景未覆盖) - 静态检查: 无新增告警 - 覆盖率: 目标模块 68%,较基线上升 3% 失败日志摘要: - callback.py:441 断言失败: expected="merchant_not_found" actual="payment_rejected" - 原因: 未携带 merchant_id 的请求在支付校验阶段提前退出 未处理事项: 无 下一步建议: - 补充 merchant_id 缺失场景分支 - 确认错误码优先级是否允许覆盖系统默认值

这份报告的意义在于:人可以在两分钟内完成“读结论、看风险点、给反馈”的决策动作,然后决定是合入、修改还是让机器今晚补跑。如果报告写得含糊,人就得亲自下场翻代码,那“24小时干活”就变成了“24小时找茬”,完全是两回事。

5. 若要启动你自己的工作流,从第一步开始这样准备

如果你看完上面的东西,想在自己仓库里搭一套类似的流程,我的建议是从小处起步,不要一上来就追求“全自动无人值守”。

5.1 起步清单:最小可用闭环

我给自己的第一步定了一个很低的目标:让系统能在有人监控的前提下,独立完成一个小任务的完整闭环。具体准备步骤是这样:

  1. 选一个仓库的副本分支,改坏了也不影响主线,建立沙盒环境。
  2. 准备两三个真实发生过的小 bug 修复任务,写成结构化任务卡。
  3. 先不接自动化触发,改成人工启动、人工确认结果,观察每一轮表现。
  4. 记录每次失败的类型,把高频失败原因追加到任务卡的“注意事项”里。
  5. 跑通一轮完整闭环后,再逐步延长无人值守时间。

这套做法的核心哲学是:先把反馈循环跑熟,再谈自动驾驶。不要一上来就让它全天候跑,因为它的行为模式你还不了解,你连它会在哪里跌倒都不知道,就把它放出去野跑,翻车概率极高。

5.2 什么样的需求适合丢给“夜间员工”

最后说说任务选型,这决定了你第一晚的体验是惊喜还是惊吓。我总结了一张分类表:

适合夜间自动处理不适合夜间自动处理
范围清晰的 bug 修复需求本身还模糊的新功能
有明确测试断言的变更涉及产品方向判断的任务
重复性技术栈迁移多轮用户沟通的设计
补充单元测试安全敏感的核心链路改动
错误码、日志规范类整改对外接口协议变更

第一次跑的时候,我建议只挑第一类任务,而且挑那种你完全知道正确答案的任务。这样就算它做得不对,你也一眼能看出来,能快速给出反馈。等这套流程稳定了,再慢慢扩大任务类型,一步步把更多工作“托管”出去。

另外再提醒一句:任何自动流程都不是“一次建好永久使用”。你的代码库在变,技术栈在变,任务形态也在变,所以每隔一段时间就要回头审视验收规则和任务卡模板,把新出现的坑补进去,把不合适的规则清掉。这个维护动作本身,也是 harness engineering 的一部分——它不是一次性的约束,而是伴随 vibe coding 一起生长的第二层工程体系。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进