我第一次意识到“氛围编码”这条路线迟早要出事,是在一次版本合并现场。同事让AI加一个“简单的导出功能”,AI很听话,半小时内交出三百行代码。合并进主干时我们才发现,它顺手改了订单状态的枚举值、把另一个模块的公共函数复制了一份,还绕过了类型检查拼了一段SQL。代码能跑,但没人说得清它脑子里那套“需求”是从哪来的。从那天起,我开始认真研究SDD(规范驱动开发)和Harness这类“驾驭工程AI”的工具,目标只有一个:让AI写代码的产出,从“氛围好”变成“可控”。
这篇文章写给已经在用AI写代码、但被失控感折磨的人。你会看到SDD怎么把需求语言转成AI能执行的规范,Harness怎么在Agent外面套上一根缰绳,以及我在Linux和Windows上搭建这套工作流时踩过的五个坑。它不是理论综述,是一份能用起来的实操记录。
1. 氛围编码的失控时刻:它为什么火,又为什么让人后怕
1.1 氛围编码的兴起:从“写代码”到“描述代码”
氛围编码(vibe coding)这个词最早走红,描述的就是一种非常放松的写代码方式:你不太关心每一行怎么实现,只把意图讲给AI听,让它生成,然后你看一眼结果、点个运行,觉得行就提交。它让不熟悉语言的普通人也能“造出”工具,也让熟手把大量时间从敲键盘里腾出来,专注在更高层的设计上。这个吸引力是真实的,我不会贬低它。
可问题也藏在这份“放松”里。传统编程里,代码是人类思想的执行记录,你写出来的每一行背后都有一个明确的“为什么”。而氛围编码把“为什么”埋进了对话上下文,模型一旦把需求理解得偏一点,上下文里那个错误的“为什么”就会顺着代码长出来。刚开始只是一两处不对,等你连续让它改了几天,整个代码库会变成一片“氛围”,只有氛围,没有依据。
1.2 失控的具体形态:五个高频事故
根据我的实践和身边团队的反馈,失控的形态其实高度一致,可以归纳成下面五类:
- 改A坏B:AI为了满足你当前的第一条指令,偷偷修改了相邻函数的逻辑,你review时不会发现,因为它的输出看起来“相关”。
- 重复造轮子:代码库里明明有现成的公共方法,AI不知道,重新实现了一份逻辑相近的实现,后续维护的人要对付两套“半像不像”的东西。
- 范围蔓延:你只让它“加个参数”,它顺手做了格式化、重命名、补充了一个异常处理,一次提交里混进了七八种意图。
- 测试与实现互相献媚:AI写的测试总是恰好覆盖它自己写的实现,哪怕实现本身是错的。测试绿了,安全感是假的。
- 昨天能跑今天崩:上下文窗口有限,第二天继续对话时模型记不清昨天的约定,常常推翻自己的实现。
这五个事故的共同根源是:AI在“自由发挥”,而它的自由发挥没有一条可被校验的基线。如果有一份规范事先写清了“输入是什么、输出是什么、边界在哪、不许动什么”,大多数事故在发生前就会被拦下来。
1.3 哪些项目不适合“氛围优先”
我现在的判断标准很简单:一次性脚本、原型验证、个人玩具项目,氛围编码效率极高,放开用;可维护的产品代码、多人协作的模块、涉及数据一致性和资金逻辑的系统,必须以规范驱动为主。很多人说起AI原生软件工程,以为就是“堆更多AI”,我理解的正相反——是先定规矩再让AI干活。AI越强,规矩越要提前定死,否则力量越大,破坏半径越大。
2. SDD规范驱动的运转逻辑:把需求语言变成AI可执行的任务
2.1 SDD和TDD、提示词工程有什么不同
SDD全称是Specification-Driven Development,规范驱动开发。它的核心主张是:在写代码之前,先把“做什么、做到什么程度、边界和禁忌是什么”固化成一份可评审、可版本化的规范,然后让实现去贴合规范,而不是让模型从对话里自行揣测。
经常有人把SDD和TDD(测试驱动开发)放在一起比较。TDD是用测试来描述行为,红绿循环驱动实现;SDD在更靠前的位置,描述的不仅是行为,还包括约束条件和排除项。更好的理解是:TDD回答“怎么知道它做对了”,SDD回答“到底让它做什么、不做什么”。两者可以叠加使用——先用SDD把任务边界画出来,再用TDD把验收条件写下来。
提示词工程和SDD的区别更关键。提示词是一次性的,今天这个prompt有效,明天换个模型版本可能就失效;SDD里沉淀出来的规范文件是工程资产,有文件名、有版本、有评审记录,可以随时回放。你不需要每次跟AI重复解释业务背景,它只要读spec文件就够了。
2.2 一份合格规范长什么样
我在项目里使用的规范结构,通常包含以下七块:
- 背景与动机:为什么要做这个变更,解决什么问题。
- 输入定义:函数参数、接口字段、外部事件,类型、范围、可选性。
- 输出定义:返回值、响应结构、副作用,明确哪些会产生副作用。
- 边界与异常:空值、超时、非法输入怎么处理,错误码和日志口径。
- 明确禁止项:不许动哪些模块、不许引入哪些依赖、不许绕过哪层校验。
- 验收条件:可运行的检查命令、测试用例、性能指标。
- 范围声明:这次任务只包含什么,不包含什么,防止范围蔓延。
我拿一个真实例子来说明。下面是一份我写过的批量导出规范的骨架:
# SPEC-20250601 订单批量导出CSV ## 输入 - 时间范围 start_time / end_time (RFC3339) - 状态过滤 status (可选,默认all) - 分页游标 cursor (可选) ## 输出 - text/csv 文件流,列顺序:order_id,status,amount,created_at - 文件编码 UTF-8 with BOM ## 边界 - 单次导出上限10万行,超过返回 400 EXPORT_LIMIT_EXCEEDED - 超时时间60秒 ## 禁止项 - 不得修改现有订单查询逻辑 - 不得新增第三方CSV依赖 ## 验收 - pytest tests/test_order_export.py - 1万行数据导出耗时<5秒写完规范后,花五分钟自我检查:如果把这个文件交给一个完全不了解项目的新人,他能照着写出你心里想要的东西吗?如果答案模糊,AI也会模糊。这个结构其实不复杂,但最大的价值在于“禁止项”和“范围声明”。氛围编码翻车,绝大多数不是因为AI能力不够,而是因为它不知道哪些事不能做。你一旦在规范里明确写了“不得修改order_status枚举定义”“不得新增第三方依赖”,它就多了一道硬约束。
2.3 规范颗粒度与“谁写规范”的现实问题
规范的最大争议是粒度:写细了像文档地狱,写粗了等于没写。我的经验是分三层处理:
第一层,项目级规则(rules)。面向所有任务,比如代码风格、测试要求、提交信息格式,一次配置,长期生效。第二层,特性级规范(spec)。一个功能一个文件,描述范围、输入输出、禁止项和验收条件。第三层,任务级指令,嵌入在具体执行时的对话里,只写“本次会话的临时约束”。
“谁写规范”这个问题也很现实。我见过不少团队直接让AI去写规范,我不反对,但前提是你得把规范当代码审查:AI起草的spec必须经过人来拍板,尤其禁止项和边界条件,人不说清楚,AI猜不准。更有效的分工是工程负责人写骨架,AI负责补细节和检查遗漏,人最后签收。
3. Harness到底是什么:它和Agent的边界不在名字,而在控制权
3.1 用一场驾驶来理解Agent与Harness
很多人第一次看到harness这个词会懵,因为它在英文里有“马具、挽具”的意思,后来引申为“约束和控制工具”。在AI编程的工具语境下,Harness不是另一个“会写代码的Agent”,而是套在Agent外面的那套控制装置。
打个比方:Agent是发动机和车轮,Harness是方向盘、刹车、仪表盘、行车记录仪。发动机马力再大,没有控制装置,车只能冲出去;装了控制装置,你才能决定它什么时候加速、什么时候停下、走的路径是否符合规则。所以社区里流行“无Harness不工程”的说法,不是夸张,而是因为裸露的Agent默认选择是“无限自由”。
热词里经常有人问“Harness和Agent区别”,我回答过很多次:Agent负责“做”,Harness负责“边界”。“做”和“边界”是两个维度。你可以在一个Harness下面接多个Agent,也可以是同一个Agent在有无Harness时表现出完全不同的可控性。判断一个工具是不是Harness,要看它是否具备规则注入、技能封装、执行审计和回退控制,而不是看它叫不叫Agent。
3.2 Harness的核心能力拆解
在我使用过的这类工具里,几个核心能力是共通的:
- 规则注入(Rules):把项目级规范加载进AI的上下文,让它每次都看见、也不得不遵循。这对应SDD里的第一层规则。
- 技能管理(Skills):把高频动作封装成可调用的技能。例如“按规范生成模块”“写单元测试并运行”“做一次不改变行为的重构”。技能是介于提示词和程序之间的东西,AI调用它时更像在执行一个明确定义过的流程。
- 插件系统(Plugins):扩展Harness与外部工具链的连接,比如读取Git信息、触发lint、调用静态检查、提交代码。
- 执行审计(Trace):记录AI每一步做了什么、改过哪些文件、执行过哪些命令。这是“可控”的重要来源——出了问题可以回溯。
- 断点与回退(Checkpoint):在关键节点暂停,等人批准后再继续;或者回退到某个之前的稳定状态。它对应SDD里的“范围声明”,防止AI一路狂奔。
这些能力对应的不是新发明,而是把人类软件工程里“评审、权限、审计、批准”这套机制,翻译成了机器可执行的控制逻辑。这才是从“氛围编码”走向“AI原生软件工程”的关键一步。
3.3 接入不同模型:为什么会频繁看到“DeepSeek Harness”“Claude Code + Harness”这种说法
社区里讨论Harness时,经常会看到“DeepSeek Harness”“Claude Code + Harness”这样的组合词。理解它很简单:Harness负责控制和边界,底层模型负责生成能力,两者是解耦的。你可以给Harness配置不同的模型端点,比如DeepSeek的大模型、Claude系列模型等,也可以接本地部署的模型服务。
换句话说,Harness不去抢模型该干的活,它把模型的能力封装进一套可控的流程里。这也解释了为什么很多团队的落地路径是这样的:先本地把模型服务跑起来,再在Harness里配置好模型端点,剩下的就是写规则、写规范、定义技能。模型选型的变化不会推翻整套流程,规则和规范仍然沿用,这给团队省下了很多重复磨合的成本。
4. 从零搭出第一套可控工作流:安装、模型配置与插件选型
4.1 安装与运行环境准备
不同Harness实现的具体安装步骤会有差异,但整体思路是相同的,我按通用流程来写。以社区里常见的命令行形态为例,先保证运行环境有Node.js 18+或者Python 3.10+(看你选的那个Harness基于什么运行时)。然后从官方仓库拉取对应版本,执行依赖安装。
不管是Linux服务器还是Windows桌面环境,我建议把Harness的工作目录独立出来,不要和项目代码混在一起。我见过有人图省事直接放在项目根目录,结果配置文件被AI误当成项目文件读进去,白白污染了上下文。独立目录里放Harness自身的配置文件、技能定义和临时缓存,项目目录里只放规则、规范和源码。
安装完成之后,第一件事不是急着跑任务,而是初始化一个空项目,确认命令行能正常唤起界面、能读到配置文件。如果这一步界面都起不来,后面所有插件问题排查难度会翻倍。很多“奇怪问题”其实是版本不匹配造成的,先跑通最小闭环,再逐步加插件,这个顺序不能省。
4.2 模型接入:公共API与局域网本地模型
模型接入的核心是配置“模型提供方”和“端点地址”。如果你使用公共API服务,通常只需要填入API Key和模型名称;如果你是局域网内使用,比如要处理敏感代码或者完全没有外网的环境,配置就会多几步:在本地或内网搭一个模型服务,把base_url指向内网地址,保证Harness所在机器能访问到它,再填一个模型名称标识。
这里有一个容易忽略的细节:很多公共API的地址和本地兼容服务使用的路径不一样,后者的模型名也可能是自定义的。配置前先看一眼你部署的模型服务对外暴露的名称列表,不要照抄别人的配置。我曾经在离线环境里照搬了公共API的模型名,结果Harness一直报“模型不存在”,查了半天才发现是名字对不上。
我之前用过的Harness版本里,配置文件常用TOML或JSON格式,包含类似这样的关键块(以伪配置为例):
[model] provider = "local" # 可选:public / local / custom base_url = "http://192.168.1.10:8000/v1" model_name = "deepseek-local" api_key = "no-key-required" # 本地服务可能不需要校验如果是在公网服务,则把provider改成public,填上对应的api_key和model_name。配置文件改完之后,建议用一条极小的命令测试连通性,比如让AI输出“ok”,不要一上来就生成整个模块。这个习惯能帮你把“模型配置问题”和“代码生成问题”隔离开。
4.3 插件与技能的初始组合
第一次搭建,我的建议是只装四类插件:代码格式化与lint、单元测试运行、Git操作、规范模板生成。这四类对应日常开发里最频繁、也最容易失控的环节。格式化与lint保证产出符合项目风格,单元测试运行提供快速反馈,Git操作让提交和撤销受控,规范模板生成帮你把SDD落地的成本降下来。
技能方面,我建议从三个技能起步:第一个是“按规范生成模块”,让AI读取指定spec文件后生成对应目录和代码骨架;第二个是“生成并运行测试”,让它为本次改动补测试并实际执行;第三个是“自查变更清单”,让它列出所有改过的文件、新增的导入、被删除的代码,方便人工评审。
这三个技能基本覆盖了“从规范到实现再到验证”的主链路。至于那些花哨的自动编码插件,等主链路稳定了再慢慢加。工程化的第一原则永远是“先让流程可控,再谈效率”。
4.4 第一次任务:让Harness跑通“规范→代码→检查”
配置好之后,跑一个最小任务验证整条链路。我通常的做法是:在项目的specs目录下写一份很小的规范,比如“新增一个工具函数formatOrderId,输入字符串,输出格式化后的字符串,非法输入返回空字符串”,然后在Harness里调用“按规范生成模块”技能,让它基于这份spec去实现。
任务完成后,人工检查三步:第一,生成的代码是否严格对照spec里的输入输出定义;第二,是否出现了spec没有要求的内容,比如额外的依赖、无关的重构;第三,运行一次lint和测试,确认工具链正常接入了。
这条链路第一次跑通大概需要半小时到一小时。跑通之后,你的工作方式就正式从“对话-生成-提交”切换成了“规范-任务-验证”,后面所有复杂功能都复用这个骨架。这一步是整个工程化的分水岭。
5. 连踩五个坑之后,我把问题排查表留在这了
5.1 插件入口激活失败:web boot: 1 entry did not activate
这类问题我最早遇到时很崩溃:Harness本体能正常启动,但加载某个插件时报出“web boot: 1 entry did not activate”之类的错误,界面某个面板就是不出来。排查下来,绝大多数情况是插件版本和Harness主版本不匹配,插件的入口声明写法变了,宿主找不到对应的激活入口。
我的处理步骤是:先看插件的manifest文件(通常是plugin.json或类似定义),确认入口文件名和导出的函数名;再对照Harness版本更新日志,看激活机制有没有变化;最后把插件换成与主版本同一代的稳定版。不要为了用某个新插件去升级主程序,除非你确认兼容矩阵,否则很容易把好不容易跑通的基座又弄坏。
5.2 Skill读取文件被拒:setnamedsecurityinfow failed背后的Windows权限逻辑
在Windows上使用Harness时,我遇到过一个很典型的权限问题:技能在执行时读取某个项目文件,结果直接报“setnamedsecurityinfow failed”这种Win32安全接口错误。第一反应往往是“是不是杀毒软件拦截了”,其实不完全是。
这个错误通常意味着进程尝试设置或获取文件的安全属性时,没有足够的Windows ACL权限。常见的触发点是:项目目录放在系统保护目录下、目录ACL被之前的管理员账户改过、或者Harness进程以普通用户身份运行但目录继承权限被切断。解决路径我按优先级排列:
- 把项目目录移到普通用户完全控制的路径下,比如用户目录或专用工作目录;
- 检查目录的安全属性,确认当前用户拥有“修改”和“写入”权限;
- 避免用“管理员身份运行”去强行绕过,因为这会引入更多权限混乱。
这个问题在Linux上往往不明显,因为文件权限模型更直白。Windows下踩过一次之后,我现在每个新项目初始化时都会先确认目录的ACL,再让Harness去读写。
5.3 代码回退的粒度陷阱
Harness一般会提供某种形式的回退机制,但刚开始我天真地以为“回退”等于Git里的reset。实际使用中,回退的单位取决于Harness记录变更的粒度。如果你让它一口气完成了七八个文件的改动,然后回退到任务开始前,它可能把整个任务的中间过程全部撤销,而不是只撤销其中某个不太对劲的片段。
这个坑的解法不在工具里,而在工作方式上:把任务拆小,一个spec对应一次较小的变更,每完成一步就确认一次。每当你想回退时,回退的是“一个粒度的决策”,而不是“一整段失控过程”。我会在spec的范围声明里明确写“本任务最多涉及N个文件”,超过就自动报警,人为介入拆分。
5.4 局域网模型端点配置失效
离线环境里,模型端点配置失效是一个好消息和坏消息并存的问题。好消息是证明你的控制层已经接上了;坏消息是排查链路比较绕。常见症状是:Harness能启动,模型请求也发出去了,但返回超时或者一直重试。
我排查时会按三层走:第一层,用curl直接打一下base_url的补全路径,看模型服务本身是否响应;第二层,检查Harness所在机器到模型服务器的网络路由,有些环境只允许特定端口通信;第三层,检查模型服务日志,看请求是否真的到达了。三层走完基本能定位是网络问题、协议路径问题还是认证配置问题。
还有一个小经验:很多本地模型服务要求的URL路径带着版本号前缀,比如/v1/chat/completions,配置时直接写在base_url里或分开配置都行,只要最终拼接出来能和curl测通的结果一致。
5.5 重装与卸载后的残留问题
Harness这类工具卸载起来比想象中麻烦,因为除了主程序目录,它还会在用户配置目录、缓存目录里写入内容。我一度被人问“为什么卸载重装后还报错”,查到最后都是残留的全局配置在起作用:新装的Harness实例启动时读到了旧版本留下的配置文件,加载了不兼容的插件路径,于是一切又回到老问题。
所以,需要彻底重装时,记得清理三个地方:主程序安装目录、用户级配置目录、临时缓存目录。清理前先备份那些你自己写的规则和技能文件,这些是你的工程资产,不要被顺手删掉。装好之后,第一次启动不要急着导旧配置,先空配置启动,确认干净,再逐个导入定制内容。
6. 回到工程现场:SDD文件结构 + Harness控制的完整配合
6.1 一个可复制的仓库布局
到这一步,我把整套工作流沉淀成了下面这个仓库布局,新项目直接按这个结构搭:
- specs/:存放特性级规范,一个功能一个文件,Markdown格式。
- rules/:存放项目级规则,全局长期生效。
- skills/:存放自定义技能定义,Harness从这里加载可调用技能。
- src/:业务源码。
- tests/:测试代码。
- .harness/:Harness自身的配置、插件清单、审计轨迹。
这个布局的价值在于:人和AI看到的是同一个结构。AI读specs下的文件就能知道任务边界,读rules就能知道项目纪律,读skills就能知道可复用的能力;人评审时只需看spec有没有写清、测试有没有跟上、审计轨迹里有没有越界动作。整个项目的“事实”不再散落在聊天记录里,而是全部沉淀在文件系统里。
6.2 完整实战:给订单模块增加批量导出CSV接口
我拿一个实际场景演示完整流程。需求是给现有订单模块增加一个批量导出CSV的接口。按照SDD框架,我先在specs目录下创建批量导出规范文件,内容就按2.2里那个结构写:输入参数(时间范围、订单状态过滤、分页游标)、输出定义(CSV文件流、列顺序、UTF-8 with BOM)、边界条件(最大导出行数、超时时间、并发限制)、禁止项(不得修改现有查询逻辑、不得引入新的CSV库、复用现有导出工具函数)、验收条件(一万行订单导出测试,耗时小于5秒)。
规范写完,人在Git上单独提交了这个spec文件。这一步很重要:规范本身就要进入版本管理,后续审查的是“实现是否符合这个版本”。然后在Harness里调用“按规范生成模块”技能,让AI读取并实现。实现完成后,Harness自动跑一遍单元测试和lint,并生成一份变更清单。
人工评审阶段,我按规范逐条对照变更清单。如果发现AI引入了新依赖,或者改了范围声明里禁止动的查询逻辑,我可以选择让Harness回退、或者单独下达“不改动查询逻辑,只在其上层做过滤”的补丁任务。全部通过后,合入主干,关闭任务。整个流程里,人的角色从“逐行审代码”变成了“审规范和审变更清单”——这才是AI原生软件工程里人的位置。
6.3 规范评审与验收清单
规范评审不能只看功能是否符合,我给自己定了一张固定检查单:
- 输入、输出定义是否无歧义,空值和异常是否覆盖;
- 禁止项是否覆盖本模块“最容易出事的点”;
- 范围声明是否能防止一次任务演变成大重构;
- 验收条件是否可以在CI里跑起来,而不是需要人肉眼判断;
- 规范是否独立可读,不依赖本次对话上下文。
这张检查单让我避免了大多数“AI跑偏”。你不需要每次都逐项写长篇规范,但每次都要把禁止项和范围声明这两块写清楚——它们就是缰绳。
6.4 一点体会:哪些场景请继续“氛围编码”
写到最后说点个人感受。我并不认为氛围编码应该被消灭,它依然是原型期最高效的工具。但我会在两类场景里主动关掉Harness的严格模式,把控制放松:一是探索期的技术验证,二是写一次性脚本。原因很简单,这些场景的产出不需要长期维护,失败成本低,“氛围”反而是最好的燃料。
而只要代码要进主干、要给别人维护、要跟资金和数据沾边,我就把SDD和Harness这套组合拉满。氛围编码解决的是“从0到1跑起来”,SDD加Harness解决的是“从1到N还不崩”。两者不是对立关系,是不同阶段的工具。我现在最顺手的状态就是:用氛围编码做原型、生成规范草稿、梳理思路,然后切换回规范驱动模式,让AI在缰绳之内把可维护的东西真正落地。