意图对齐:Vibe Coding 为什么失败
如果你曾经有过当"甲方"外包工作给其它团队的经验,无论是软件开发、还是新家装修,你大概率会有一个明确的感受:无论你请到的外包团队水平有多么高超,外包任务的推进仍然会缓慢而痛苦,因为瓶颈从来就不在外包团队的能力,而是确保团队完全理解了你的意图。在不同的人之间进行意图对齐是一件极其困难的事情,任何的疏漏,偏差,甚至对一些简单”常识"的理解不同,都可能导致灾难性的后果。而AI Coding这件事情,与其说是软件开发,反而更像是找了一位技术高超的软件外包。你需要通过结构化的手段确保自己的意图尽可能无损的传递给Agent,它才有可能给你想要的结果。在这一章中,我们从诊断这个问题开始,建立信息分层和结构化维度的方法论框架,然后讲怎么用这个框架迭代出一份可以交给 Agent 执行的 spec,最后用一个真实项目的工作流把所有内容串起来。
对话模式的三种失败
你大概经历过这样的阶段。打开 Agent,用自然语言说一个需求,几分钟后代码就出来了。跑一下,改两轮,能用。你开始把越来越多的任务交给它,一个下午搞定了过去要写两天的东西。Vibe coding 的开局体验确实很好。
问题不是一下子出现的。
你在第五轮对话里提了一个约束:"所有 API 返回值必须包含 error code 字段。"Agent 照做了。到了第二十轮,你让它新加一个接口,生成的代码里 error code 字段消失了。你测试的时候才发现这个问题。你回去翻对话记录,那条约束确实还在,但中间隔了十五轮关于别的功能的讨论。Agent 生成第二十轮代码的时候,第五轮的那条约束在它的注意力分配中已经排不上号了。
这是 vibe coding 最常见的失败模式之一:早期的指令被后续的对话内容挤出了 Agent 的有效注意力范围。你自己记得那条约束,你默认 Agent 也记得。但 Agent 的注意力不是均匀分布在整个对话上的。研究表明,LLM 在处理长文本时对开头和结尾的关注度较高,中间部分容易被忽略(这个现象被称为 lost in the middle)。你的约束如果恰好被夹在大段的代码输出和后续讨论之间,Agent 可能实际上"看不见"它。
第二种失败更隐蔽。你一开始说用 SQLite 做存储,做到一半发现需要支持并发写入,改口说用 PostgreSQL。这对你来说是一个清晰的决策变更:后面的指令取代前面的。但 Agent 看到的不是一条时间线上的两个决策点。它看到的是一个 flat 的 token 序列,里面同时存在"用 SQLite"和"用 PostgreSQL"两条指令,没有哪条比另一条优先级更高。
结果可能是 Agent 在后续的代码里混用两种数据库的写法。连接池的配置按 PostgreSQL 来,但某个辅助脚本里还在调 SQLite 的 API。更麻烦的是,你很难在 review 的时候发现这类问题,因为每段代码单独看都是合理的,矛盾只在整体上才能看出来。
对人来说,对话有时间线,有上下文,有隐含的优先级关系。后说的覆盖先说的,这是人际沟通的基本默认。对 LLM 来说,这些隐含规则都不存在。整个对话就是一串 tokens,前面的和后面的在注意力机制里地位平等。当两条指令冲突时,Agent 没有一个可靠的机制来判断应该听哪条。
第三种失败跟对话长度直接相关。当你跟 Agent 的对话足够长,context 接近上限时,Claude Code、Codex 这类工具会自动做一件事:compaction。系统把早期的对话压缩成摘要,只保留它认为重要的内容,为新的对话腾出空间。
Compaction 的问题在于,决定什么重要、什么不重要的是系统,不是你。你花了二十分钟在对话前期讲清楚的一个架构约束,在 compaction 之后可能被压缩成一句话,也可能完全消失。而你不会收到任何通知说"你之前提到的某某约束已经从 context 里移除了"。你继续对话,继续提需求,Agent 继续生成代码。直到你发现产出的代码违反了那个约束,你才意识到信息丢了。
这跟前面说的注意力衰减还不同。注意力衰减是信息还在 context 里,只是 Agent 没注意到。Compaction 是信息被直接删除了,Agent 想注意也注意不到。一个是看不清,一个是看不见。
这三种失败模式看起来是不同的现象:约束被忽略、指令冲突、信息丢失。但它们有一个共同的根源。
你的意图活在对话里。而对话是一个会膨胀、会自相矛盾、会主动丢弃内容的载体。每多一轮对话,早期的意图就被挤得更远。对话足够长的时候,系统还会主动把一部分内容删掉。你跟 Agent 之间的意图对齐,建立在一个不断流失的基础上。
Context 限制下的信息架构
引言里讲过 Agent 有三个结构性特征:非确定性、无持久记忆、无法自我验证。对于"怎么把意图传达给 Agent"这个具体问题来说,最核心的限制是其中两个的组合:context 有限(无持久记忆的延伸)和注意力不均匀(影响信息的实际接收质量)。
如果这两个限制不存在,意图对齐就是一个简单问题。你把项目的全部文档、所有代码、整个 git 历史、每一次设计讨论的记录全部倒给 Agent,它全部消化,完美执行。你只需要在项目开始时对齐一次。
现实中你做不到这一点。当前主流模型的 context window 从 128K 到 1M tokens 不等,看起来很大。但一个中等规模的项目,几万行代码加上文档和配置,轻松就到几十万 tokens。再加上对话历史和 Agent 自己的思考过程,空间很快就吃紧了。而且前面讲过,即使信息在 context window 以内,注意力的分布也不均匀。你加进去的信息越多,每条信息实际获得的注意力就越少。这个关系不是线性的:加到某个点之后,再加信息不但不能帮助对齐,反而会干扰对齐,因为真正重要的信息被淹没在噪声里。
Ryan Yang 在开发他的 AI agent 平台 AnyClaw 时,最先碰到的就是这个问题。他在同一个 context 里处理多个功能点,发现功能之间的信息会互相干扰。一个功能的 spec 里提到的模块名、变量名、设计约束,会"泄漏"到另一个功能的实现里。Agent 的注意力在多个功能点之间来回跳转,每个功能分到的有效注意力都不够。他的描述是:"虽然上下文窗口越来越大,但过多冗余信息下注意力还是不集中,然后漂移。"
这个问题在小项目中可能不明显。几百行代码、两三个模块,所有信息加起来可能只占 context 的一小部分,Agent 的注意力绑绑有余。但一旦项目达到一定规模,几千行代码、几十个模块、跨越多个关注点,你就会开始观察到同样的现象:Agent 做着做着就开始"走神",前面的约束被后面的信息冲掉,产出开始偏离你的预期。
这个限制把意图对齐从一个沟通问题变成了一个工程问题。你不能把所有意图一次性灌给 Agent 然后期待它全部消化。你需要做一系列的设计决策:什么信息放进 context,什么留在外面。什么信息在开头(注意力较高的位置),什么放在后面。什么信息每次都加载,什么只在当前任务用到时才加载。什么粒度的信息给 Agent:太粗它理解不准确,太细它注意力被分散。
这些决策合在一起,构成一个信息架构。这个架构的目标是在有限的 context 和不均匀的注意力下,让 Agent 在每个执行环节都能接收到它真正需要的意图信息。
目前社区里主流的 spec 驱动开发框架,尽管在方法论上差异很大,但在这一点上做了惊人一致的设计决策:它们都对加载到 context 里的信息做了硬性限制。OpenSpec 把项目上下文限制在 50KB。AILock-Step 限制在 200 行。BMAD 要求每次只加载当前步骤的文档,禁止同时加载多个步骤。Spec Kit 在每个阶段只加载该阶段需要的产出物。四个独立演化的系统,做了几乎相同的决策。这说明 context 管理是这个领域一个绕不开的核心问题。
后面的内容就是围绕这个信息架构展开的。下一节从最基础的一步开始:把意图从对话中外化成文档,然后用结构消除歧义。