最近有好几个正在做 Agent 项目的朋友,不约而同跑来问我同一个问题:能不能像以前做单点需求那样,先写一份详细的 spec,然后让模型照着生成 Agent 代码?我的回答基本都是——能做,但如果你把这当成 Agent 开发的主线,大概率会在第一个真实场景里翻车。
所谓 spec coding,简单说就是先写规格、验收条件、示例输入输出,再让模型按着这份"设计图纸"生成实现。这套思路在前两年做单点工具、脚本、页面时非常香,也确实让不少人找到了"批量生产代码"的手感。但到了 Agent 开发这里,问题开始变得不对劲:Agent 不是一段静态代码,它是一个在有反馈、有状态、有不可控环境里持续执行任务的运行体。你用 spec 去约束"它应该长什么样",却忽略了真正需要设计的其实是"它遇到环境和预期不符时该怎么办"。
所以今天这篇文章,就想把这两条路掰开揉碎聊清楚:spec coding 到底解决了什么问题,Agent 开发真正需要的又是什么,以及我看到的从"写 spec"到"做 harness"的范式转移。不管你是在搭个人 Agent、做企业级多 Agent 系统,还是刚准备从提示词工程转 Agent 工程,这篇文章应该都能给你提供一些不同的视角。
1. 先别急着站队:spec coding 曾经解决过什么问题
1.1 spec coding 是"提示词工程的一次工业化尝试"
别一听到"落后"两个字就直接把它扔进垃圾桶。spec coding 能在前两年成为主流方法论,是因为它确实踩中了一个时代痛点:大模型生成代码这件事,最大的问题不是"能不能写出来",而是"怎么验收、怎么确保稳定"。
先说它是怎么来的。早期的 ChatGPT 一代玩法是"你给我个需求,我给你生成一坨代码",这种东西只能当玩具,因为你没法确定它生成的代码到底符不符合你的预期,更没法在后续迭代里保证它不把原来的功能改坏。于是大家开始引入软件工程里最经典的那套思维:先写需求文档,再写设计文档,再列验收标准,最后才动手写代码。映射到提示词工程里,就变成了"把完整 spec 喂给模型,再让它实现"。
这套思路在当时的项目里确实帮我解决过几个实际问题。最常见的是这类需求:写一个内部工具函数、做一个批量处理脚本、把某个接口封装成 SDK,这种场景下任务的边界非常清楚,输入输出可枚举,行为空间也有限。你只要把"接收什么参数、返回什么结构、异常时怎么处理"写清楚,模型就能生成一份几乎可以直接往线上推的代码。
我印象特别深的一个案例,是让模型写一个 Markdown 转 PDF 的小工具。当时我把源文件格式、CSS 样式、分页规则、目录生成、中文字体嵌入方案全都写进了 spec,甚至连"如果输入文件超过 5000 行,必须分块处理"这种边界条件都列上了。模型生成的代码虽然中间需要我微调几处,但整体结构完全正确,测试用例一跑就过。那一刻我真的觉得,spec coding 就是未来。
1.2 它真正擅长的地方:确定性的单点功能生成
你要问我 spec coding 的舒适区在哪,我的回答很直接:凡是边界清晰、状态可控、反馈回路短的任务,它都是无敌的。因为这类任务本质上就是一个"输入到输出的映射函数",你只要把映射规则枚举出来,模型就能照着做,而且还能用测试用例回归验证。
这么说可能有点抽象,换个生活化的比喻。spec coding 就像你在装修时给施工队画了一张特别详细的施工图:哪里放插座、墙刷什么颜色、地面用什么材料、验收时拿尺子量哪几个尺寸。在这种场景下,施工队完全不需要自己发挥,只需要照着图干。你验收起来也省心,拿图纸一对比就知道有没有偷工减料。很多团队当时用 spec coding 做小工具、内部后台、自动化脚本,本质上就是这套"画图—施工—验收"的流程。
但它有个隐含前提:施工对象是确定的。如果装修过程中突然告诉你"这面墙不能打了,因为里面有承重结构"、"邻居投诉噪音你只能夜间施工",原来的施工图就瞬间作废,而且没有任何逻辑能推导出"下一步该怎么办"。这恰好就是我后面要说的——Agent 开发环境里的常态。
所以我的态度一直是:spec coding 没有做错什么,它只是被用错了地方。它适合的是"确定性系统",而 Agent 恰恰是不确定性的产物。认清它的优势边界,比简单地骂它一句"落后"更有价值。
2. 从"做出代码"到"让 Agent 活着跑起来",范式的转移
2.1 Agent 开发的核心不是代码生成,而是执行循环
如果你去读市面上的 Agent 框架源码,会发现一个共同点:几乎所有 Agent 的核心都是一个循环。这个循环大概长这样——接收用户输入,理解当前环境,决定下一步要调用哪个工具,执行工具,拿到结果,把结果放回上下文,再继续决定下一步,直到得出最终答案或达到最大步数。
这个循环学名有很多,agent loop、执行循环、推理-行动循环,听着高大上,其实本质就是一个带反馈的控制器。你可以把它想象成一个外卖骑手:他接单之后,不是拿着固定路线一直跑就完事了,他会看红绿灯、看路况、看用户在不在家、看商家出餐了没,每一步都根据真实世界的反馈调整自己的行动。真正值钱的能力不是"规划了一条完美路线",而是"在真实路况里不断校正路线"。
恰恰是这一点,和 spec coding 的思维模式发生了根本冲突。spec coding 假设:只要我在开工前把需求定义得足够完备,结果就是可预期的、可验收的。但 Agent 开发中,你面对的是真实环境:外部 API 可能挂掉、返回格式可能变、用户输入的意图可能是模糊的、工具调用的结果可能会污染上下文。你根本没法提前写清楚"当用户在凌晨三点请求一个需要人工审批的操作时,Agent 应该说什么话"。这不是你 spec 写得不够细,而是这类情况无法穷举。
所以在 Agent 开发里,核心工程对象从"生成实现代码"变成了"构建一个能在不确定环境中持续做出合理决策的运行系统"。这个系统里,有工具层、有记忆层、有权限边界、有观测日志、有重试机制、有安全护栏。这些东西不是靠一篇 spec 能"生成"出来的,它们需要在真实环境的反复试错中逐步搭建和调优。
2.2 spec coding 错过的五个关键点:工具调用、记忆、自愈、并发与边界
我们拿一个真实场景来对比。假设你要做一个简单的"日程管理 Agent",它的任务是帮用户查日程、建日程、改时间、发会议邀请。如果走 spec coding 路线,你会写一份非常详细的文档:用户说"帮我安排一个会议"时,要调用哪个函数,参数怎么映射,回复格式是什么,遇到冲突怎么回复。听着很合理对吧?但现实是,你很快会发现这么几个问题。
第一是工具调用的不确定性。你以为一个"创建日历事件"的 API 很简单,但真实世界里它有 30 个字段、支持时区自动转换、会撞上权限错误、可能返回重复 ID、还有限流。你的 spec 可以规定"如果返回 error 就提示用户",但错误可能是十几种不同的错误,每种错误的用户沟通策略都不一样。这不是靠写更长的 spec 能解决的,而是要靠 Agent 在运行时根据错误码和场景动态选择应对策略。
第二是记忆和上下文管理。一个真正能用的 Agent 必须记得用户之前在聊什么、上次处理到哪一步、哪个信息是可信的、哪个信息已经过期。可大模型上下文窗口有限,你不能把整个对话历史和所有工具返回结果都一股脑塞进去,必须设计合适的记忆压缩、检索、淘汰策略。这些内容在 spec 里连怎么描述都很难找到合适的粒度。
第三是自我纠错能力。Agent 在实际执行中一定会出现这种情况:工具调用一次失败,换个参数重试就成功了;或者第一次搜索的结果不够充分,需要再追问用户澄清。一个合格的 Agent 需要具备足够的弹性来处理这些问题,而不是遇到没写进 spec 的异常就直接崩溃。
第四是并发与稳定性。你这边的 Agent 可能同时服务几十个甚至几千个用户,每个用户都有独立会话、独立状态、独立的工具调用链。怎么保证状态不串、调用不超时、资源不耗尽、记忆不串号?这纯粹是工程架构问题,和"生成代码"没有任何关系。
第五是权限和安全边界。Agent 能调用的工具越多,越需要精细的权限管控。哪些工具是只读的、哪些可以写、哪些操作需要用户二次确认、哪些数据绝不能吐给外部模型,这些护栏设计直接决定系统能不能落地,也决定了系统出事故时的爆炸半径。
五个痛点摆在一起就明白了:spec coding 关心的是"代码写没写对",Agent 开发关心的是"系统能不能在复杂环境里稳定地把事办了"。这两者的差距,不是改进 spec 写法就能弥补的。
3. Agent 开发真正该用的"spec"长什么样
3.1 不再是一份大而全的规范,而是一组小契约
写到这里你可能要问:难道 Agent 开发真的不需要任何规范吗?当然不是,Agent 开发有规范,只是它的形态变了:从一篇大而全的规格文档,拆散成一组小而明确的契约。这组契约分散在系统的不同层,各自管好自己那一摊。
第一类是工具契约。每个 Agent 能调用的工具,都要有清晰的定义:输入参数是什么结构、输出返回是什么格式、哪些字段可能为空、哪些错误码可能抛出、哪些操作是幂等的、哪些操作有副作用。这个契约不是用来给模型"照着实现代码"的,而是用来让模型在运行时正确做出决策的。你可以把它当成"API 文档+运行时 schema 校验"的结合体。工具定义得越清晰,Agent 在选择工具、构造参数、解析结果时的出错率就越低。
第二类是评估契约。与其写一大堆"当用户说 XX 时,你应该回复 XX",不如设计一组可自动执行的评估场景:输入一组用户请求,运行 Agent,检查工具调用序列是否正确、最终答案是否满足要求、敏感操作是否触发了确认。这一组评估集的价值,和传统开发里的自动化测试一模一样,它才是 Agent 迭代的真正安全带。
第三类是边界契约。包括权限边界、操作边界、成本边界。比如哪些工具只能读取、哪些操作必须先走用户确认、单次任务最大步数是多少、单次对话 token 预算上限是多少、重试次数上限是多少。这些边界规则不是"业务需求",而是"运行护栏"。
你会发现,这三类契约没有一条是"告诉模型怎么说话、怎么回复"的完整剧本,它们更多是在给 Agent 划出一个可运行的范围,在范围之内让它自己决策。
3.2 一个最小可跑的 Agent 闭环示例
我直接给一个最简化的 Agent 循环骨架,你感受一下它和 spec coding 工作流的差异。这不是完整的可上线代码,但能表达出核心的结构。
def agent_loop(user_request, tools, memory, max_steps=10): context = memory.build_context(user_request) for step in range(max_steps): action = llm_choose_action( context, tool_schemas=[tools[t].schema for t in tools], ) if action["type"] == "final_answer": return action["output"] if action["type"] == "tool_call": result = execute_tool_with_retry( tools[action["tool"]], action["arguments"], max_retries=2, ) context.append({ "role": "tool_result", "tool": action["tool"], "result": result, }) context = memory.compress_if_needed(context) return handle_timeout(user_request)你仔细看这个循环里有哪些设计要点。memory.build_context负责决定把哪些历史信息放进上下文;tool_schemas提供了 Agent 决策时所需的工具能力描述;execute_tool_with_retry处理那些一次调用失败但可以重试的场景;memory.compress_if_needed在上下文变长时做压缩,避免 token 爆炸;handle_timeout兜底处理超出最大步数的情况。
这些环节里,模型只负责一个非常小的动作:根据当前的上下文,选择下一步是调用工具还是直接给出最终答案。真正决定系统好不好的,是整个循环框架的健壮性——工具契约写得细不细、重试逻辑合不合理、记忆管理到不到位。这些恰恰是 spec coding 里完全不存在的内容。
3.3 harness 和 agent 的分工,别再混为一谈
这几年"harness"这个词在 Agent 圈子里出现频率越来越高,但很多人还是搞不清楚它和 agent 的边界。我用大白话说:agent 是那个负责"想"的决策内核,harness 是那个负责"活"的运行外壳。
决策内核做的事情很纯粹:读上下文、选动作、输出下一步指令。它本身不关心怎么跟外部 API 通信、怎么管理 HTTP 连接池、怎么把日志写到监控系统、怎么限制单个用户的内存占用。这些全是 harness 的责任。
我在上一篇分享里也反复强调过一个观点:大部分 Agent 项目的实际工作量,不是在"调模型 prompt",而是在"调 harness"。你花在错误处理、状态管理、工具注册、权限管控、日志观测、并发隔离这些工程问题上的时间,通常会远超你花在"怎么让模型更听话"上的时间。
所以当一支团队还在用"先写 spec 再让模型生成 agent 代码"的方式工作时,我基本可以断定:他们对 Agent 的理解还停留在"更聪明的 GPT 封装"层面。真正走上正轨的团队,早就在琢磨怎么让 harness 更稳、工具契约更清晰、评估集更全面、故障恢复更平滑了。
4. 我在实际项目里看到的"落后"细节
4.1 典型翻车现场:按 spec 生成的"完美代码"放进 Agent 后立刻崩
理论说了那么多,讲几个我实际见过的案例,你会更直观感受到 spec coding 和 Agent 开发的鸿沟。
第一个案例来自一个做"会议记录 Agent"的团队。他们最初的开发方式是:让产品经理写了一份极其详细的 spec,包含了用户说什么话时 Agent 应该怎么反应、最终纪要应该分成几个板块、每个板块的标题格式是什么、语气风格是什么。然后他们让 Claude 照着这份 spec 生成核心代码,demo 阶段效果特别好,演示给领导看的时候,纪要格式漂亮、内容完整、语言得体。
结果一接入真实会议室就崩了。原因让人哭笑不得:他们用的会议转录 API 返回的数据是不稳定的,有时候一句话会被切成五段,有时候说话人识别会串行,有时候转录文本里混着大量语气词和重复片段。而他们的 Agent 是按照"完整干净的转录稿"设计的,遇到这些脏数据时,工具调用没有做任何清洗和容错,直接就把原始数据塞给模型,模型被脏数据带偏,纪要里甚至出现了"呃呃呃,那个那个,嗯嗯嗯"这样的内容。
这个团队的 spec 写得不差,但问题是他们定义的是一个"理想化的输入场景",而真实环境根本没有那么听话。一个能扛住脏数据的 Agent,需要的是数据清洗层、分段合并策略、说话人识别置信度判断、异常片段跳过规则,而这些全都属于 harness 层面的工程,不是靠写 spec 能无中生有的。
4.2 另一个翻车案例:完美遵守 spec 的 Agent,却把上下文污染了
第二个案例更有意思。一个团队做了一个企业内部知识问答 Agent,他们的 spec 相当完备,详细规定了工具调用顺序、回复引用格式、答案风格,甚至规定了"当用户问到没有覆盖的问题时,统一回复'这个我暂时不知道'"。开发者巡检时发现,Agent 的行为完全符合预期,但就是感觉回答质量越来越差,越聊越不对劲。
排查到最后才发现,问题出在 RAG 检索的结果没有做严格的上下文隔离。工具返回的哪段文本来自哪份文档、当前对话的引用证据之间是什么关系,这些信息全都混在一起。结果 Agent 在一次回答中采用了文档 A 的数据,在下一次回答中,文档 A 的内容已经被记忆层当作"已知前提"带进了新问题的检索范围里,导致它基于旧文档的错误内容回答了新问题。用行话讲,就是上下文被污染了,信任传播出了问题。
这类问题,再详细的 spec 也写不出来。因为 spec 回答的是"在给定输入下应该输出什么",而上下文污染是一个跨多轮、跨工具的状态管理问题。解决它需要的是记忆策略设计、引用溯源机制、证据链记录,以及精心设计的评测集来提前暴露这种状态级故障。
4.3 从"spec 评审会"到"agent 演练场"的验收转变
上面两个案例还反映出一个更深层的问题:验收方式的错配。传统 spec coding 的验收方式是评审——把生成的代码和 spec 条文逐条对照,看看有没有实现偏差。这在单点工具时代是合理的,代码是静态的,你评审一次基本就放心了。
但 Agent 是动态的、随环境变化的系统,你评审一次只能在"评审那一刻的环境"里确认它的表现。真正的验收方式应该是演练:专门准备一个模拟环境,构造各种真实世界里会出现的脏数据、异常响应、歧义输入、多轮状态变化,在足够多的场景下跑它,统计成功率、失败率、平均轮数、错误恢复时间。
我后来在团队里推动的做法是:每周固定跑一轮"Agent 对抗测试",由测试同学和研发同学轮流当"刁钻用户",故意说一些模糊的话、故意触发权限错误、故意连续修改需求、故意给 API 注入异常数据。跑完之后不做代码评审,只看失败记录,然后一项项修 harness。这个习惯持续了一个多月,Agent 的可用性提升非常明显。
5. 给还走在 spec coding 路上的团队几个实操建议
5.1 第一步:把大 spec 拆成可调用的工具集
如果你的团队现在还在用写大文档的方式推进 Agent 开发,我建议从第一步就开始改:不写"系统应如何回应"的大 spec,先列"Agent 手上应该有哪些工具"的工具清单。
这个转换非常关键,因为它改变了你的设计思维。大 spec 的问法是"系统应该做什么",工具清单的问法是"为了实现用户的真实目标,Agent 需要哪些能力"。后者的拆解逻辑更贴近 Agent 的运行机理。
你可以这样操作:把产品需求里的每个能力点抽象成一个工具。比如"输入会议邀请"是一个工具,"检查日程冲突"是一个工具,"发通知给参会者"是一个工具。然后为每个工具写一份类似 API 文档的契约:参数、返回值、错误列表、是否需要用户确认、是否幂等。这个工作做完后,你手里不再有"一坨 Agent 需求",而是一组边界清晰的运行能力。
5.2 第二步:建立评估集和观察面板,比写 prompt 更重要
这是我最想强调的一条经验:Agent 开发里,评估集的优先级应该高于提示词设计。你 prompt 写得好不好,不应该靠"感觉还行"来判断,而是靠跑一组固定场景后看数据来判断。
评估集的建设不用一步到位,可以先小后大。第一版只放 20~30 个场景,覆盖核心功能和典型异常。每个场景包含:用户输入、预期工具调用序列、预期最终结果、敏感操作是否正确触发确认。你每改一次 harness、每调一次提示词、每加一个新工具,都把这套场景跑一遍,对比前后结果。
有条件的话,再配一个简单的观察面板,把每次运行的完整轨迹记录下来:模型调用了哪些工具、每个工具返回了什么、哪里出现了异常、最终耗时多少 token 花了多少。没有这个观测底座,你做任何优化都是盲飞。我在实际项目里的感受是,Agent 开发 60% 的工作量都花在"看清楚它到底怎么跑的"这件事上。
5.3 第三步:把提示词从"完整剧本"改成"操作手册+边界策略"
最后一点落到提示词本身。很多从 spec coding 转过来的团队,写 Agent 提示词时仍然带着浓厚的"剧本思维":什么场景说什么话、什么请求返回什么格式,全都写死。这在简单场景里确实可控,但在复杂场景里就是给自己挖坑。
我更推荐把提示词改写成"操作手册+边界策略":告诉模型它有哪些能力、在什么情况下优先做什么、哪些操作必须请示、哪些错误要放弃重试,但不要规定每一句话怎么说。举一个真实的改写例子。
改之前的写法是:"当用户请求安排会议时,你需要调用 create_event 工具,传入 title、start_time、end_time、attendees 参数,如果工具返回 error,则提示用户'创建失败请重试'。"
改之后的写法是:"你是一个日程管理助手。你可以调用 create_event、update_event、list_events、check_conflicts 四个工具。优先使用 check_conflicts 检查时间可用性,再创建事件。创建事件时必须输出时间、地点、与会者摘要,并要求用户确认。如果工具返回 401,说明登录已过期,不要重试,直接引导用户重新授权。处理模糊请求时,先拆出关键字段向用户确认,不要臆测。"
你感受一下两者的差异。第一种把模型当成了"状态机",你替它把每条路径都铺好了,一旦出现没预料到的场景,它就卡住。第二种把模型当成了"有判断力的执行者",你只告诉它游戏的边界和优先级,具体路径由它自己根据环境决定。后者需要的模型推理能力更高,但应对真实世界的能力也强得多。
6. 我对"落后"二字的最终看法
说了这么多,其实我对"spec coding 落后"这个判断有一个更温和的版本。我认为 spec coding 并没有"死",它只是从"主角"变成了"配角"。它依然在工具契约定义、测试用例生成、评估集构建、组件边界设计这些方向上发挥作用,只是这些工作不再是 Agent 开发的主线了。
能与大家分享的实际体会是:Agent 开发的主语言正在从"用自然语言写需求文档"变成"用环境和回路做工程"。你把工具接好、把权限划好、把记忆管好、把观测做好、把评估集铺好,Agent 的能力是在这个"场"里自己长出来的。还在把 spec coding 当主线的人,会发现自己在做一个非常精致的"剧本播放器",而不是一个能在真实世界里独立解决问题的智能体。
如果你想继续深入这方面,我的建议也很简单:找一个小场景,搭一个最简的 agent loop,亲手塞进去几个不靠谱的工具,然后亲眼看着它在一个脏数据组成的模拟环境里挣扎,接着一轮轮把 harness 补牢。等你走完这一轮,你自然就会理解为什么说 spec coding 这条路,对 Agent 开发来说已经不够用了。