返回文章

从能跑到可交付:一套前端 AI 协作系统的设计笔记

上篇《AI 写代码,最缺的不是 Prompt,而是一个能暴露错误的现场 》讲的是为什么:模型不是不会写,而是没有现场感,不知道自己错了,也没办法及时发现。

这篇讲怎么做。

过去一段时间,我把那套想法落成了一个能跑的系统:一套前端 AI 协作系统。它不是一个更长的提示词,而是一组互相咬合的设计:五层约束管住代码往哪写,八个阶段管住一件事怎么走完,评测管住什么叫做对了,编排器管住长流程别中途失忆。

先给结论,再展开。LangChain 在 Terminal Bench 2.0 上做过一个实验:模型不变,只改外围的 Harness,得分从 52.8% 涨到 66.5%,多了 13.7 个百分点,排名从三十名开外进了前五。

我的感受也类似。模型没有变聪明,是它犯错的空间变小了,发现错的速度变快了。

下面按五层约束、八阶段流水线、Skill 怎么放、评测怎么做、编排器怎么跑、踩过的坑、怎么从零搭、它的边界在哪,这个顺序讲。

一、五层约束:先把轨道铺好

我把前端的约束分成五层。自下而上是:语言、样式、状态、工程、美学。

每一层回答一个问题。语言层问错不错,样式层问稳不稳,状态层问准不准,工程层问通不通,美学层问对不对。顺序不能乱。代码都编译不过,谈视觉还原没有意义。

第一层,语言约束。 用 TypeScript 的严格模式钉死:显式类型,不许用 any 绕过去,不许用忽略类型的注释把报错盖住。类型即文档,函数签名要让调用方一眼看出会返回什么、可能出什么问题。

我把这层比作有刻度的尺子。写错了,尺子当场拦住,不需要等运行起来才发现。

第二层,样式约束。 颜色、间距、字号全部走设计变量(Design Token),不写死数值;组件样式用 CSS Modules 做作用域隔离。这些规则不靠评审时口头提醒,而是写进 Stylelint 和 ESLint,违反就报红。

这层像一个只有 8 种颜色的调色板。AI 选不了错的颜色,因为错的颜色根本不在板子上。

第三层,状态约束。 状态要有显式的结构定义:字段、类型、初始值、哪些可缺省,都写清楚。客户端状态和服务端数据分开,单一数据源。本地存储的键必须带命名空间,能一眼看出它属于哪个页面、哪类数据,而不是随手起个名字全站共用。

这层像一座城市只有一条公路从水厂到每家每户。你不需要猜水从哪来,打开龙头就有,出了问题也知道沿哪条路查。

第四层,工程约束。 目录结构、模块之间能不能互相引用,用架构 Lint 强制;文件大小和函数长度设上限——比如单个源文件不超过 300 行,函数也要控制在可读的长度内,超了就必须拆。再加上提交前钩子和 CI,把类型检查、Lint、构建、测试串成一条流水线,不通过就不让进。

这层是交通规则加红绿灯。不是建议你别闯,而是路口的设计让你闯不过去。

第五层,美学约束。 设计变量再往上走一层:颜色、间距、圆角、阴影、动效都沉淀成 Token,高频界面模式封装成组件,再用截图对比做视觉回归。

这层像乐高。错误尺寸的积木根本不存在,能拼错的余地就小了。

这里有个前端特有的判断,值得单独说。

LangChain 总结的通用 Harness 是六层:文件系统、沙箱、代码执行、记忆、上下文管理、编排。落到前端,我把前几样做了压缩:文件系统、沙箱、代码执行,本质上都被工程约束吸收了——构建失败就是沙箱失败,Lint 跑通就说明代码能执行;记忆在前端就是状态,归到状态层;编排单独拿出去,变成下一节的流水线。

压完之后空出来一层,我把它留给了美学。

这不是凑数。通用 Harness 的文献很少谈美学,因为多数任务的对错是二值的。但前端不一样。Omni-I2C 这类图文转代码基准里,当前最强的模型视觉还原能到七成多,细到参数级的精度只有四成左右,而且实体和布局错误占了所有错误的六成以上(来源:Omni-I2C 基准,2026)。也就是说,功能对了,页面仍然可能是不对的。用户会不会用,很大程度取决于这一层。

二、八阶段流水线:把一件事拆成可验收的八步

有了轨道,还需要列车时刻表。

我把一个需求的完整过程拆成八个阶段:初始化 → 需求澄清 → 代码生成 → 文档沉淀 → 评测 → 验收 → 发布 → 进化。

初始化是把任务登记下来,生成一个状态记录;需求澄清把需求文档和设计标注翻译成一份可执行的计划;代码生成按计划写代码;文档沉淀在代码写完后,把这个功能做了什么、有哪些约定,从代码里反向整理成文档;评测和验收是两道检查,后面会细说;发布是走正常的分支和合并流程;进化是把这次的失败和修正回写到规则和 Skill 里,让下一次少踩同一个坑。

文档沉淀放在代码生成之后、评测之前,是个有意的设计。它和评测可以并行:评测读的是代码,不读文档。而有些问题——比如文案资源漏了——与其退回去重走澄清和生成,不如直接在文档沉淀这一步补掉,快得多。

整条流水线有三个原则,都是踩出来的。

原则一:先定义“完成”长什么样,再写代码。

需求澄清阶段产出的计划里,必须有一份可测试的功能点清单。不是“做完一个待办页面”,而是拆到这种粒度:“添加待办——用户输入文本,校验非空,加入待处理列表,清空输入框。”每一条都能单独验证做没做、做对没做对。

Anthropic 有个发现我很认同:评分标准的措辞不只是用来判断好坏,它会直接塑造生成者的输出方向。你把“完成”写清楚,AI 写的时候就在朝那个方向收敛。

反例就是那句最常见的“把首页做完”。AI 自由发挥六个小时,你验收时发现方向错了,改一句话要返工半天。模糊的指令不会被执行,只会被放大。

原则二:检测和修复必须分开。

评测阶段只报告问题,永远不许顺手改代码。修复只能回到代码生成阶段重试,或者走验收阶段的路由。

原因很实际。修复必然引入新问题:评测顺手修了样式,可能改坏类型;修了类型,可能漏掉文案。更麻烦的是评测一旦动了代码,这次评测就没法复现了——你分不清报告说的是哪一版代码。

分开之后,每次评测对应一份固定的报告,每次失败走哪条路重试,都有记录可查。

原则三:验收用四道门,按顺序短路。

验收阶段有四道门:canRun(能不能跑)、dataReady(数据齐不齐)、usageRight(用得对不对)、featureWorks(功能通不通)。每道门背后是若干维度的评测分数,每一维都有自己的及格线。

关键在短路:第一道门不过,后面三道根本不跑,直接退回重试。

这不是省时间,是顺序本身有逻辑。代码都编译不过,你去查文案齐不齐、组件用得对不对,查出来的全是噪声。先把能不能跑确认了,后面的检查才有意义。如果第一道门连续失败多次,那多半不是代码写错了,而是计划本身错了,这时要停下来让人介入,而不是让它继续重试。

也说一下节奏。整条流水线里有三个反馈循环:写代码时,编译和 Lint 的报错在秒级回流,相当于写一句拦一句;功能做完,分钟级打开浏览器看效果、做截图对比;提交时,CI 在更长的周期里把类型、单测、端到端和视觉回归全跑一遍。越早的循环越便宜,能在秒级拦住的,别拖到提交时。

三、Skill 的三种暴露方式:放错位置,写得再好也会丢

这套系统里,每个阶段的能力都写成一份 Skill 文档:什么时候用、按什么步骤做、有哪些反例不许犯。

写了二十多个之后,我发现一个比内容更要命的问题:Skill 放在哪、怎么被加载,决定了它能不能活到被用的那一刻。

原因在上下文压缩。长对话里,上下文会被周期性压缩成摘要腾地方。不同载体的东西,压缩后的命运完全不同。

Skill 的名字和一句话描述会被周期性重新注入,基本不会丢;但 Skill 的全文一旦被调进上下文,压缩时会被并进摘要,关键步骤就磨没了。我测过,靠调用注入的 Skill,全文在一次压缩后只剩个位数百分比,第二次压缩后归零。

相对的,直接从文件读进来的内容要好一些:第一次压缩后几乎能全保留,但第二次压缩同样归零。只有写在项目级说明文件里的东西,每轮都会重新加载,不受压缩影响——但那种文件必须短,塞不下流程细节。

所以我用了三种暴露方式,按 Skill 的体积和使用频率分:

  • 直接嵌入:短小、高频的,控制在两百行以内。它离模型最近,触发最稳,代价是占地方。
  • 子代理读取:中等长度的,不进主对话,让一个独立的子代理在自己的上下文里读全文、跑完整个流程,主对话只收结果。
  • 预读精确注入:很长、低频的,只把标了“必读”的关键段落预先读进主上下文,全文留给子代理去跑。

还有一条硬上限:全局注册的 Skill 不能超过 12 个。

12 这个数是试出来的。全局列表里每个名字和描述都是每轮都要付的固定成本。数量一多,一是每轮的信噪比下降,模型在一长串名字里挑错的概率上升;二是总长度膨胀会让压缩来得更早、更频繁,本来想让 Skill 更容易被用上,结果它们死得更快。超过十来个就要开始讨论降级:新 Skill 默认放在项目级,只有被用户直接触发过好几次的,才有资格升到全局。

顺带说 Skill 本身怎么写。每份只做一件事,步骤化,每一步都要有可验证的产出——“写一份计划”不算步骤,“产出一份带功能点清单的计划”才算。还要有至少三条真实踩过的反例。注意,不是“不要写烂代码”这种废话,而是“需求没提错误码时,不许自己编一个”这种能照着执行的具体禁令。平均下来每份不到一百行,复杂的事靠编排器组合,不靠把单份 Skill 写长。

四、评测必须确定性:不让模型给自己打分

整套系统里我最坚持的一条:写完代码之后,判断对错不能用模型,要用代码。

Anthropic 在他们的长周期 Harness 设计里说得很直白:开箱即用的 Claude 是一个糟糕的 QA。它能看到真正的问题,然后说服自己“这些问题没什么大不了”,照样批准自己的工作。

这不是某个模型的毛病,是位置决定的。让生成者兼任评审者,它天然对自己宽容。

所以我的评测拆成五个维度,全部用确定性的方法判定:

  • 结构:类型能不能编译通过、有没有用忽略规则绕过检查、导入关系合不合架构约束、文件和函数有没有超长。用的是编译器退出码和语法树扫描。
  • 数据:文案键是否齐全、状态结构是否闭合、本地存储的键有没有按命名空间规范。做法是把代码里用到的键和资源文件里的键集做比对。
  • 逻辑:需求澄清阶段列的功能点清单,逐条核对代码里有没有对应的实现和关键分支。这就是为什么功能点必须写在写代码之前——它是评测的考卷,考后出题就没意义了。
  • 界面:样式有没有走组件作用域、颜色间距有没有用变量、有没有写死数值、响应式有没有覆盖。
  • 端到端:真的把测试跑起来,统计通过和失败的数量,看主链路有没有覆盖到。

每条检查都必须给证据:具体到哪个文件、哪一行。没有证据,就不算通过。我不接受“看起来没问题”出现在评测报告里,那正是整套系统要消灭的东西。

每个维度单独在一个独立的子代理上下文里跑。跑结构的看不到跑数据的结论,避免先入为主——前面发现的问题,不该影响后面维度的判断。

最后是报告的规矩:每份评测报告写完就不许再改。后续重新生成了代码、重新评测,写新报告,旧的留着。这样复盘的时候能回答一个很关键的问题:这次翻车的原因,上次的报告里是不是已经预警过了?如果预警过,问题在路由和修复;如果没预警,问题在评测本身。没有留档,这笔账永远算不清。

五、编排器:长流程不能靠模型“记住”

八个阶段如果全在一个主对话里跑,会发生什么?

每进一个阶段,就要把那个阶段的 Skill 全文加载进上下文。八份加起来上千行,走到第五阶段,上下文必然触发压缩。而压缩一来,前四个阶段的约束和结论就被磨成了摘要里的几句话,越关键的细节丢得越干净。

我用探针式抽查量过这件事:在流程开始时埋一组关键规则作为探针,在每个阶段结束时抽查这些规则还记得多少。主对话模式下,走到第五阶段,信息保留率只剩 40% 左右。

解法是把流程从对话里搬出来,交给一个编排器。它是一个确定性的程序,逻辑很朴素:

每个阶段派一个独立的子代理去跑。子代理有自己全新的上下文,只加载本阶段的 Skill,只拿本阶段需要的输入。它跑完,把结果写进一个状态文件就退出。

阶段之间不靠“记住”,靠状态文件传递。状态文件是整条流水线的唯一事实源,编排器读它决定下一步,每个阶段只在自己的边界上写它,不在中途乱改。它长这样(简化版):

{
  "feature": "todo-app",
  "phase": "评测",
  "history": [{ "phase": "代码生成", "status": "passed" }],
  "artifacts": { "plan": "...", "evalReport": "..." },
  "verdict": { "passed": false, "firstFailGate": "canRun", "retryCount": 1 }
}

验收不通过时,编排器按失败的是哪道门来路由:跑不起来,退回代码生成;数据不齐,退回代码生成并补资源;功能不通而代码本身没问题,可能是环境问题,转人工。重试次数也有上限,超了就停,别让它在一个死胡同里无限打转。

主对话在这个模式里只看到每个阶段的通过或不通过,上下文干净得多。同样用探针式抽查量,走到第五阶段,信息保留率能到 95% 左右。

差别不在模型,在信息放在哪。放在对话里,就要陪着对话一起被压缩;放在文件和程序变量里,就不会。

六、踩过的四个坑

坑一:评测一开始用模型判断。

最初的评测是让模型看代码、写结论。结果它给自己打了满分,嘴上说 100 分,代码实际跑起来只有三成左右能用,端到端测试全军覆没。

根因就是第四节说的自我宽容。修复是把五个维度全部重写成确定性检查,每条都落到具体的命令和文件行号上。

经验一句:Lint 的报错不只是报错,它同时是给模型的修复指令。报告写得越具体,它改得越准。

坑二:把所有 Skill 都注册到全局。

当时想的是方便调用,一口气全放上去。结果每个任务都跑不长:全局列表一膨胀,压缩来得又早又勤,两次压缩之后评测环节直接全线归零。

根因在第三节说过:全局是有固定成本的。修复是把全局严格压回上限以内,其余放回项目级,靠编排器在需要时调起。

经验一句:全局名额是硬约束,不是目录,想加新的,先想想把谁降下去。

坑三:在主对话里跑完整条流水线。

症状就是第五节的前半段:跑到第五阶段,前面阶段立的规矩基本被压缩吃掉了,后面的阶段等于在一个失忆的上下文里凭感觉干活。

修复是写编排器,把阶段搬进独立子代理。

经验一句:主上下文永远不要堆叠流程内容。任何“在一次长对话里跑完长流程”的设计,都会在中段崩掉,只是早晚问题。

坑四:编排器的状态机有个 bug,直接跳过了几个阶段。

第一次端到端跑,八个阶段只跑了四个:初始化、代码生成、评测被莫名其妙地跳过了,流程却显示一切正常。

根因是判断“下一步跑哪”的逻辑写错了:当某个阶段还没跑过、历史记录为空时,代码误判成“已经通过,去下一个”,一路跳了过去。

修复是把判断拆成明确的三步:先看有没有失败且还有重试次数,有就留在本阶段;再看是不是从没跑过,没跑过就跑本阶段;最后才看通过了没有,通过了才推进。

经验一句:这种状态机逻辑,单元测试测不出来。每个阶段单独测都是对的,错的是阶段之间的衔接。它必须端到端真跑一遍才能信。

七、四周落地路径

这套东西不需要一次到位。我自己的顺序是四周,每周只解决一类问题:

周目标关键产出
第一周钉死语言层和工程层开启类型严格模式并禁掉绕过写法;写架构 Lint 和体积 Lint;提交前钩子串起全部检查。AI 写代码时,错误在秒级被拦下,并带修复提示
第二周给关键阶段写 Skill三个核心阶段用直接嵌入放全局,几个长流程的放项目级。说一句“做新需求”,能走完澄清到生成
第三周上评测和验收五个维度逐个落地,聚合之后接四道门的短路验收。生成不再是终点,过门才是
第四周上编排器写状态文件和阶段路由,补齐发布和进化两个阶段,再给编排器留一个不依赖对话环境的冒烟模式。找一个小功能,端到端跑通八个阶段

四周之后的状态是:新增的代码量其实不多,净增一千多行的规则、Skill 和编排逻辑,换来的是下一个功能的开发时间明显缩短。慢的是一次性的系统建设,快的是之后每一次。

如果你只想先拿走一样,我建议是第一周。约束不需要等系统完整才生效:严格模式加两个 Lint,今天下午就能让 AI 在你的项目里少犯一类错。

八、局限:它测不了什么,我很清楚

第一,美学层只能测一半。

自动化能查的是:有没有用变量、作用域对不对、截图和基线差多少。但“颜色搭配合不合理”“间距有没有节奏”“字号层次清不清楚”,这些判断目前自动化只能覆盖一半左右,剩下一半还得靠截图加人工评审,或者靠一个独立的评审代理打分,而且那个分数要刻意往怀疑的方向校准,不能再犯自我宽容的毛病。

这也是为什么我说美学是前端特有的一层,同时它也是五层里最弱的一层。承认它弱,比假装它被解决了重要。

第二,编排能力受工具平台限制。

独立子代理加脚本编排这套打法,目前主要在一个工具里能完整跑起来,其他常用的 AI 编程环境大多还不支持同样的编排原语。我的妥协是把编排器写成独立的命令行程序,在支持的环境里接子代理跑真实模式,在不支持的环境里退到模拟模式,至少状态文件的结构和阶段路由能先验证起来,不把设计锁死在某个工具上。但体验差距是真实存在的,这一点没法粉饰。

第三,约束有保质期。

这套系统不是搭完就一劳永逸的。每次模型升级,都应该做一次消融:把某条约束撤掉,看效果变好还是变差。有些约束是补模型当下的短板,模型补上了,它就从护栏变成了路障;还有个更隐蔽的现象,模型会过拟合它长期使用的工具和 Harness,换掉某个环节的做法,效果反而会掉,这也只能靠实测发现,靠想是想不出来的。

还值得一提的是整个行业的背景并不乐观:有调查说,高频使用 AI 编码的人里,接近七成报告 AI 生成的代码至少有一半概率带来部署问题,一半左右的人觉得代码质量在下降。速度先到了,交付系统没跟上。这套 Harness 解决的是我自己项目里的那一份,不是什么通用解药。

写在最后

回到开头那句话:不要更聪明的马,要更好的赛道。

模型会变,而且变得很快。但回头看,这套系统里真正的核心不是哪个具体的数字或者工具,而是五个补偿性的设计:先定义完成再动手、检测和修复分开、上下文尽量被动给到、长流程拆进独立上下文、评测只信确定性的证据。

它们补偿的不是模型的智商,而是模型的几个结构性短板:不知道自己不知道、会说服自己没问题、记不住长流程、会被上下文淹没。这些短板短期内不会因为模型大一号就消失,所以这套设计也不会很快过期。过期的只会是具体的某条约束,那正是消融要定期清理的。

我把其中评测这一块抽出来,做了一个开源小工具 ai-code-gate(github.com/XingMXTeam/ai-code-gate):五维打分加四道门短路,就是这篇文章里评测和验收两部分的简化版,能直接在自己的项目里跑,想看代码的可以去翻。

上篇讲为什么需要现场,这篇讲现场怎么搭。下一篇如果写,我想写写进化阶段:一个系统怎么把每次失败变成自己的规则,而不是靠人复盘完写进文档就完事。

参考资料

  • LangChain:《The Anatomy of an Agent Harness》——Agent = Model + Harness 的六层解剖与 Terminal Bench 2.0 数据
  • Vercel:《AGENTS.md outperforms skills in our agent evals》——被动注入与主动检索的对照实验
  • OpenAI:《Harness engineering: engineering in the agent-first world》与 Codex 团队零手写代码的内部实践(经 Yage.ai 调研整理)
  • Anthropic:《Harness design for long-running application development》——长周期应用的 Harness 设计与评审者自我宽容问题
  • Omni-I2C 基准(2026)——图文转代码任务上主流模型的视觉还原与参数精度数据
  • Fred Brooks:《No Silver Bullet》(1986)——本质复杂度与偶然复杂度的区分