Vibe Coding 的幻觉与工程门槛

1Vibe Coding的快感

Vibe coding 最容易让人上头的时刻,通常在最开始的时候,我只说了一句话,AI 就写出了一个网页、一个工具。对很多没有开发背景的人来说,这个瞬间带来的刺激非常强:原来开发一个软件并没有想象中那么困难,想法可以被实现了。

春节的时候,有个十几年没有联系过的高中同学,突然找到咨询 vibe coding 的事,他本身不是技术出身的,想要做一个小程序。我的建议是,做个小应用是没问题的,但是如果要实现一套系统,挑战比较大,会让自己几乎把所有精力搭进去。vibe coding 的快感,会让人感觉一个人也能做出一家公司。

2熵增让系统失控

一个简单的网站、小工具,通常只有少量代码,基本上就是个 demo。用 AI 能快速堆叠功能也能快速准确修复bug。

demo 和真实项目的差别,远不止体量大小。demo 通常功能简单,代码量也少,就算出错,也比较容易看出问题在哪里。但是真实的要面向用户的项目不一样,它会不断迭代出新的新功能,有一整套的项目架构和前后端服务。每增加一个功能,系统里就多出一组关系,而这些关系会互相牵连。一个功能单独看很简单,十个功能放在一起,就可能开始互相依赖。页面改动会影响接口,接口调整会影响数据库,数据库字段变化又会影响更多的功能。

随着时间增长,功能的增加,代码量越来越大。如果没有完善的系统架构设计,没有系统化地管理代码,熵增在内部不断加剧。

这时,vibe coding 的体验会慢慢变味。AI 的上下文开始不够用,token 大量消耗,bug 接连冒出来以后。加一个功能,可能没有早期那样高的交付质量,修bug的时间远远比功能迭代的时间多。

此时,熵增处于强势局面。此时,我们已经开始被 AI 带着跑了。

3Vibe coding 的工程思维

如果要让我们写的程序能稳定迭代,AI 能保持高质量工作,需要有一套能驾驭 AI 的工程思维。

这些思维包括:项目管理思维、系统架构思维、Harness 思维。

项目管理思维:对开发工作流的管控,对项目文档的管理,对任务的交付顺序的要求。

系统架构思维:一个系统能长成参天大树,离不开坚固稳定的结构,不同的服务各司其职,就好比代码里的生态系统,才能正常运作。

Harness 思维:Harness 可以理解为包在大模型外层的一套运行时工程系统,它负责把模型的推理能力接到真实任务里,包括上下文管理、工具调用、执行环境、权限约束、状态记忆、编排流程、评估观测和反馈修正。它不是某个固定标准,更像一类让 Agent 稳定工作的工程设计方法。

4项目管理思维:让需求进入可控流程

项目管理思维的核心,在于把一个想法变成一组能执行、能分配、能验收的任务。需要提前决定什么先做、什么后做,不同的需求如何进行版本迭代。

缺乏这个思维,很容易把一整段“愿望”扔给 AI,然后期待它能自己完成需求理解、任务拆分、写代码做测试,然后完美的交付给你。

比如我想做一个后台管理系统,比较危险的说法是“帮我做一个完整后台”。这句话听起来很明确,实际上里面藏着大量没有拆开的工作:登录怎么做,权限规则是什么,前端有哪些页面,测试要覆盖哪些流程。

更合理的方式是把这些需求拆成一组互相衔接的任务,让 AI 每次只处理一个边界清楚的问题,并且每一步都留下可检查的结果。

以这个后台系统的例子,如果画成流程图,会更容易看出项目管理真正控制的是需求如何进入开发、如何被协作、如何被验收。

flowchart TD
  idea["想法:做一个后台管理系统"] --> scope["收口本轮需求:登录、用户列表、角色权限"]
  scope --> split["拆成任务:数据表、接口、页面、测试用例"]
  split --> backend["后端任务:接口输入输出、权限校验、错误返回"]
  split --> frontend["前端任务:页面状态、表单交互、异常提示"]
  split --> testing["测试任务:正常流程、异常流程、边界数据"]
  backend --> joint["前后端联调"]
  frontend --> joint
  testing --> joint
  joint --> verify{"是否符合验收标准"}
  verify -->|符合| next["进入下一组需求"]
  verify -->|不符合| adjust["回到对应任务修正"]
  adjust --> joint

5系统架构思维:先给系统安排骨架

系统架构思维的核心,在于代码大量生长之前,先给系统安排一个骨架。这个骨架不一定复杂,但必须能说明系统分成哪些层,数据从哪里来、经过哪里、最后到哪里去,未来新增功能时应该接在哪个位置。没有架构思维的项目,早期常常显得很快,因为所有东西一开始都能堆在一起;但中后期会变得很难受,因为任何改动都可能碰到一堆原本不该碰的东西,从而 bug 百出。

假如我们不知道如何设计系统架构,我们可以结合项目管理思维,先让 AI 帮我们设计架构。前提是我们要把产品的“形态”、“用户角色”、核心的能力要表达清楚,我们还要让 AI 基于我们的上线之后的设想,提出问题,比如用户规模、部署环境、技术选择。从而在信息逐渐明确的过程中,系统架构也逐渐收敛到清晰的状态。

拿一个简单的内容平台举例,系统有上传、审核、展示、推荐、统计这些功能。

让前端负责展示和交互,让接口层接收请求,让业务层处理规则,让数据库保存核心数据,让任务队列处理耗时任务,让文件存储管理图片和附件。这样未来要增加审核策略、推荐逻辑或数据报表时,系统仍然保有生长空间,每加一个功能也不必像在旧墙上硬凿一个洞。

flowchart LR
  user["用户操作"] --> frontend["前端:展示、交互、页面状态"]
  frontend --> api["接口层:接收请求、返回结果"]
  api --> service["业务层:规则、权限、校验"]
  service --> database["数据库:保存核心数据"]
  service --> queue["任务队列:处理耗时任务"]
  service --> storage["文件存储:图片、附件、素材"]
  database --> api
  queue --> service
  storage --> service 

6Harness 思维:把 Agent 的能力装进工程系统

当一个编程智能体已经能读代码、改代码、跑命令等全面能力之后,真正重要的问题会从“它能不能做事”,转向“它能不能在正确边界里持续做事”。Harness 的价值就在这里:把模型的推理、工具、工作区、权限、记忆和反馈装进一套可控系统,让 Agent 能够持续推进任务,同时减少熵增、越界操作和上下文失控。

Harness 要解决几个核心问题:

1、如何解决上下文爆炸问题?

2、如何让执行流程变得可控?

3、如何让 AI 记住项目知识?

4、如何让工具调用、代码执行和文件修改都处在安全边界内?

5、如何通过判断任务结果是否达标?

以下先用七类能力来解释 Harness,这里采用的是适合编程智能体的工程模型,不代表行业统一标准。

flowchart TD
  task["用户任务:修 bug、加功能、改页面"] --> context["上下文:给当前任务需要的信息"]
  context --> workspace["工具与执行环境:工具、文件、命令、沙箱、工作区"]
  workspace --> constraints["约束:权限、审批、Lint、结构测试、禁改边界"]
  constraints --> execution["编排与规划:调查、计划、实现、验证分开"]
  execution --> agent["Agent 落地:搜索、编辑、运行命令"]
  agent --> feedback["评估与观测:测试、日志、浏览器、截图、轨迹"]
  feedback --> decision{"结果是否成立"}
  decision -->|成立| memory["状态与记忆:更新规则、文档、测试和进度"]
  decision -->|不成立| revisit["回到编排与规划:根据证据调整方案"]
  memory --> entropy["熵管理:清理重复、过期文档和低质量代码"]
  revisit --> execution

上下文管理:只给当前任务需要的信息

很多人使用编程智能体时,会在两个极端之间摇摆。一种极端是只给一句话,让 Agent 自己猜项目背景;另一种极端是把大量文档、历史对话和工具说明一股脑塞进去,期待信息越多越聪明。真实情况通常没有这么简单。上下文太少,Agent 会凭感觉写;上下文太多,它会被无关信息拖慢,甚至开始误判。

上下文 Harness 的关键,是让 Agent 拿到当前任务所需的信息,并且在任务推进过程中持续控制信息进入上下文的节奏。

项目规则、目录边界、开发约定和常用命令适合作为会话入口。更细节的设计文档、接口说明、页面规范,则按任务需要逐步加载。这样做的好处,是让 Agent 先进入项目语境,又不让上下文被大量无关材料污染。参考 vizo 这类记忆系统设计时,重点也应放在“任务需要什么就取什么”,避免把所有历史材料都塞给模型。

举个例子,如果任务是修一个后台用户列表的分页 bug,Agent 不需要一开始读完整产品方案。它更需要先知道前端页面在哪、接口适配层在哪、分页参数的共享类型在哪、已有同类表格怎么实现。Harness 在这里像一个上下文分发器,把和本次任务相关的材料递给它,把暂时无关的信息挡在外面。

工具与执行环境:让 AI 的动作落在可控工作区

Harness 不能只管理 Agent 看见什么,还要约束 Agent 能做什么。工具调用、文件读写、浏览器操作、数据库访问和接口请求,都应该通过明确的工具层接入。工具要有明确的输入输出、权限边界、错误返回和使用说明,避免 Agent 误用工具。

对编程智能体来说,执行环境尤其关键。常用的做法,是让 Agent 在受控工作区、沙箱、临时分支或可回滚目录里操作,让它能够运行命令、查看日志、生成文件和验证结果,同时避免直接破坏生产环境或用户没有授权的文件。工具和执行环境共同决定了 Agent 的行动半径,行动半径越清楚,后续的权限、审计和问题回收就越容易落地。

约束:把规则写成约束

如果只把架构规则写在文档里,大部分时候约束不了 Agent。因为 Agent 在生成代码时,会优先完成眼前任务;如果没有自动化约束,它很容易把业务逻辑写进页面,把跨层调用塞进不该出现的位置,或者为了修一个局部问题破坏已有模块边界。

约束 Harness 要把这些边界变成可执行的规则,并注入到工作流中对应角色的上下文里。类型检查、Lint、结构测试、架构依赖检查、权限审批和禁改边界都会成为约束的一部分,项目里也要明确规定相关要求。

比如一个项目规定依赖方向只能从 UI 调用服务层接口,不允许直接访问数据库,那么页面直接访问数据库层的写法就应该被检查机制拦下来。规则文档负责告诉 Agent 原则,自动化检查负责在它偏离时立刻拉回来。

这也是为什么很多团队会把 Linter、CI、结构测试看成 Harness 的一部分。它们表面上是给人看的质量工具,同时也在给 Agent 提供实时反馈。一个好的错误提示,甚至能直接说清楚智能体违反了哪条规则,以及应该往哪个方向修。

编排与规划:把任务拆成“调查→计划→动手→验证”,别急着写代码

Agent 最常见的问题之一是,太急于动手实现了,给人一种干活很勤奋的感觉。尤其是早期的编程智能体,更容易表现出特别勤奋。

它看到一个需求后,很容易直接改文件,它看到一个报错后,很容易直接打一行补丁。这样的速度在小任务里很爽,在复杂项目里很危险,因为它跳过了整体分析和规划。

讲个故事,26年春节的时候,因为Claude无法使用,就订阅了GLM,跑在Claude code上。我的指令是大体是先出方案,但我没有约束不能改代码,发送指令后我就去吃饭了。回来一看,GLM告诉我代码已经提交git。我打开浏览器体验了一番,发现并非我原本想要实现的效果。

执行 Harness 要把一次任务拆成几个清楚的阶段。Agent 修 bug 时,合理的顺序通常是先理解问题,再搜索相关调用点,判断这是孤立问题还是模式性问题;随后整理根因和修复方案,等方向确认后,再动手修复。这样人审查的对象会从一大段生成代码,前移到一份更完整的实施计划。

比如“按钮权限显示错误”这个任务,如果 Agent 直接改当前页面,可能很快让这个按钮看起来正常;但执行 Harness 会要求它先查同类按钮、权限字段来源、接口返回结构、测试覆盖情况。最后它可能发现,真正的问题出在接口适配层对权限状态的转换,而非当前页面漏了一个判断。这个差别,决定了修出来的是一个补丁,还是一次更全面彻底的修复。

评估与观测:让AI亲眼看见、亲手测试

Agent 写完代码后,不能只靠“看起来合理”来结束任务。必须要把它重新还原真实运行环境,让测试结果、日志输出、浏览器表现和截图证据共同证明本次改动是否到位。

对编程智能体来说,这一层尤其重要。测试命令、终端错误、日志输出和页面交互一旦被接进工作流,就会形成一条从代码到运行结果的反馈链。后端任务通常要回到单测、接口测试或日志里确认,前端任务则要结合类型检查、页面运行、截图和浏览器检查。如果只有代码层面的判断,很多 UI 问题、真实交互问题都会漏掉。更成熟的 Harness 还会记录工具调用、模型响应、权限检查、人工确认和错误轨迹,让问题能够被回放、定位和复盘。

比如一个弹窗关闭后页面无法滚动的问题,Agent 只读代码未必能稳定发现原因。让它打开页面、操作弹窗、观察 DOM 状态和截图,反馈质量会完全不同。Harness 在这里提供的价值,已经超出“更多工具”本身,它让 Agent 拥有接近真实用户和真实运行环境的反馈。

状态与记忆:让任务状态可控,让经验沉淀

大模型的本身并没有记忆功能,智能体本身也可能不会主动记忆项目知识。今天这个会话知道的事情,明天的新会话未必知道;这次修 bug 踩过的坑,如果没有沉淀,下次还会再踩。

记忆 Harness 要把重要信息放进项目的长期记忆,持久化沉淀下来,不能只留在聊天记录里。这里的记忆不只包括项目规则和开发文档,也包括当前任务状态、执行轨迹、关键决策、生成产物、测试结论和后续待办。项目规则、开发文档、进度记录、测试用例,都适合承接不同类型的经验。如果某个问题反复出现,还应该进一步变成 Linter 或结构测试里的硬约束。简单错误适合沉淀成规则说明,重复错误应该升级成自动化检查。这样每一次失败都会留下可复用的改进,之后的 Agent 再遇到相似场景时,也能少走同一条弯路。

熵管理:定期清理 AI 留下的痕迹

一个项目在 AI 的长期工作下,会留下很多文档、备份或者注释掉的代码。这些内容都会导致整个项目的熵增。长此以往,这些痕迹叠在一起,项目就会越来越难维护。

随着 AI 生成代码的吞吐量提高,要定期清理和更新重复实现、过期文档、架构约束违规、无用代码和测试文件等。

以 Codex 为例:让每一层 Harness 都有抓手

在 Codex 里,Harness 会变成几套固定装置。项目入口放在 AGENTS.md 和任务引用里,外部信息通过 MCP 接进来;边界交给沙箱、权限审批、工具调用规则和命令约束;重复工作沉淀成 Skills;运行反馈来自终端、日志、in-app browser、diff 和 /review;长期经验则回到文档、测试和规则更新里。代码里不断积累的重复实现、过期说明和临时补丁,也要被纳入定期清理。

项目入口先放进 AGENTS.md。根目录写仓库结构、运行命令、测试命令、代码约定、禁改边界和验收方式;前端、后端、支付、数据处理这类子目录,放更贴近局部工作的规则。没有这份文件时,先用 /init 起草,再把空泛内容删掉,改成项目真实约定。任务开始时,把目标、相关文件、限制和验收条件写清楚;外部接口文档、设计稿、错误监控和内部知识库,则通过 MCP 接进来,让 Codex 拿到当前任务真正需要的材料。

边界不能只写在文档里。沙箱和权限审批管住访问范围和需要确认的动作,命令规则规定哪些操作允许执行、哪些操作需要询问、哪些操作直接禁止。如果运行环境支持 Hooks,也可以在工具调用前后、权限请求和停止前插入检查。比如禁止无确认地运行破坏性命令,要求改到 API 目录后必须跑接口测试,或者在 Codex 停止前检查它是否写明验证结果。规则进入执行过程之后,才真正有机会拦住越界行为。

高频任务沉淀到 Skill 里。一个 SKILL.md 应该写清楚触发场景、输入要求、调查顺序、禁止事项、验证命令和最终输出。比如“修 bug”这个 Skill,会要求 Codex 先搜索同类实现和调用点,判断这是孤立问题还是模式性问题,再给出根因、修改范围和验证方式;真正动手时,只围绕根因小步修改,并在需要隔离风险时使用 Worktree。用户只需要交代本次任务的现象、期望和相关入口,重复性的工程动作交给 Skill 承接。

验证也要进入 Codex 的日常动作。后端任务让它跑测试、看日志、读终端输出;前端任务让它启动页面,通过 in-app browser 操作界面,必要时用截图确认真实表现。最后再用 diff 面板和 /review 看改动有没有越界、有没有重复逻辑、有没有漏掉验收条件。人不需要盯着每一行生成过程,但要在计划、验证和 diff 这几个位置接管判断。

完成后的经验要回到项目本身。修完一个分页 bug,如果发现“页码从 0 开始还是从 1 开始”没有统一说明,就把规则写进文档、类型注释或测试里;如果同类错误反复出现,就更新 AGENTS.md,或者把规则升级成 Lint、结构测试、Hook。Codex 做得越多,越要安排它定期清理重复实现、过期文档和临时补丁,否则速度会把项目推向更高的维护成本。

flowchart LR
  context["上下文"] --> contextTool["AGENTS.md / 相关文件 / MCP / 验收条件"]
  workspace["工具与执行环境"] --> workspaceTool["工具调用 / 文件读写 / 命令 / 沙箱 / Worktree"]
  constraint["约束"] --> guardTool["权限审批 / 命令规则 / Lint / 结构测试"]
  execution["编排与规划"] --> execTool["Skill / SKILL.md / 搜索同类实现 / 任务计划"]
  feedback["评估与观测"] --> feedbackTool["终端 / 日志 / in-app browser / diff / review / trace"]
  memory["状态与记忆"] --> memoryTool["文档 / 测试 / AGENTS.md / 任务状态 / 决策记录"]
  entropy["熵管理"] --> entropyTool["清理重复实现 / 过期说明 / 临时补丁"]