最近我梳理项目的 AI 编码交付流程时,发现一个很有意思的分裂现象:单次让模型去改一段代码,结果往往还不错;可一旦把代码生成放进真实的多人协作仓库、多模块编译、多环境部署场景里,表现就开始随机起来。模型有时候改对了目标文件,却顺手破坏了相邻接口;有时候修复一轮又引入新错误,循环好几轮都收敛不了。问题并不都在模型本身,而是我用“对话”的方式去做“工程化”的事情。后来我逐渐把重心从写提示词转移到搭流程,基于 Agentic Harness Workflow 框架的思路,把 AI Coding 变成一条可重复、可观测、可回滚的工程链路。这篇文章想把框架的底层设计、落地步骤、编排模式,以及我在真实项目里踩过的一些坑,完整整理一遍。
1. 单次对话和工程化之间的差距:为什么要给 Agent 加套“流程壳”
1.1 传统 Prompt 模式的两头都堵
如果只是偶尔让 AI 帮自己写一个函数、补一段测试,那么普通的对话式交互完全够用。可当 AI Coding 要作为团队产能的一部分进入迭代节奏,问题立刻变了:每个人写的提示词风格不同,模型拿到上下文的方式不同,执行工具链的权限不同,最后产出的代码是否真的通过了静态检查、跑过了测试,也没有人统一确认。
我见过最典型的一个场景:某个同事让模型修复一个空指针异常,模型给出的方案是把入参判断放宽。单从那个函数看,改动没问题;但调用方依赖的恰恰是非空约束,放宽入参等于把防御逻辑挪到了更下游,直接导致另一个模块在运行时抛异常。整个问题不是“模型答错了”,而是“模型只看到了一个切片”。对话式交互的前提是用户能提供足够完整的信息,可在大型代码库里,没有谁能在一条对话里说完所有约束和关联。
另一头堵在结果不可验证。普通对话里,模型给完代码,用户自己拿着代码去编译、去测试。如果不对,再复制报错回去继续问。这套手动循环在小规模改动里还扛得住,放到几十个任务并行推进的时候,就会消耗大量人工时间。最讽刺的是,人工时间主要花在“传话”而不是“判断”上——把编译错误复制给模型、把模型的新代码复制回去,重复十几次。
1.2 Agentic Harness Workflow 做的事
所谓 Harness,直译是“线束”或“缆束”,在工程语境里更接近“外壳”和“约束装置”。它不替代模型本身,而是在模型外面套一层可编程的流程框架:谁负责拆解任务、谁负责收集上下文、谁负责调用工具、谁负责检查结果、失败之后走哪条分支、最多循环几次,这些都被显式定义出来,而不是靠自然语言临时商量。
Workflow 则是这层外壳的编排维度。一个简单的流程可以只包含“生成代码 → 静态检查 → 编译 → 单测”,复杂一点的流程会包含“初步方案生成 → 影响面分析 → 并行计划 → 分文件执行 → 全量测试 → 失败信息回灌 → 二次修复 → 人工复审”。无论复杂程度如何,关键点是执行路径是确定的,不像对话那样每次的路径都靠模型临场发挥。
我的理解是把 AI Coding 从“即兴表演”变成“流水线生产”。同样是对着代码库干活,即兴表演看重单次输出的灵感,流水线生产看重的则是整个过程的稳定性:每一步做什么、产出什么、不满足什么条件就不能进入下一步,全部可以被审核、被记录、被回放。这也正是工程化最基础的含义。
2. 框架功能图谱:任务、工具、状态、验证是怎么串联的
2.1 四个核心模块
我在落地这个框架时,并没有一步到位引入重型的平台系统,而是先按照功能边界拆成四个模块:任务解析器、执行器、工具注册表、验证器。它们各管一段,协作起来像一条小型装配线。
任务解析器负责把一句话需求转成结构化任务。比如“修复登录模块在并发条件下的竞态问题”会被解析成目标文件范围、约束条件、验收标准、可调用的工具集合。如果需求太模糊,解析器会先要求补充信息,而不是直接把模糊任务丢给模型,减少后续返工。
执行器是真正和模型交互的那一层。它负责把任务、上下文、工具调用权限组装成一次执行单元,并管理执行状态。执行器内部通常维护一个状态机:待执行、执行中、等待验证、验证失败、重试中、已完成、已终止。每一步切换都有记录,方便事后回溯。
工具注册表的作用是给模型一套受限的能力,比如读取指定目录文件、运行指定测试命令、执行语法检查、搜索符号定义等。工具不是越多越好,而是越贴近当前任务越好。把工具收敛成任务相关的小集合,能显著减少模型做出不可控行为的概率,这一点在后面专门讲坑的时候展开。
验证器是整条流水线的质检工位。它接收执行结果,运行规则化的检查项,输出 pass 或 fail,并附带结构化失败原因。验证器最大的价值不是“判断对错”,而是生成可以被下一次执行理解的信息。比如测试失败时不只是给一句“失败”,而是给出失败的测试名、堆栈摘要、涉及的文件行号,这样回灌给模型时,模型才知道从哪里入手。
2.2 两条关键数据流
框架内部有两条关键数据流。一条是前向的输入流:任务 → 上下文包 → 工具集 → 执行器 → 工作产物。前端输入流决定的是“模型这次干活时看到什么、摸到什么”。另一条是反向的反馈流:产物 → 验证器 → 失败信息 → 上下文包更新 → 执行器重新调度。反馈流决定的是“模型怎样从错误中学习并修正”。
这两条流要分开设计。如果让模型既看原始需求、又看失败信息、又看代码库全貌,很容易信息过载。我在实践里的做法是把一条流封装成“上下文档案”,每一次执行都基于当前的档案快照;验证失败后,不是往旧档案里追加一段话,而是根据失败类型重建档案,把无关文件剔除掉。这样模型每次进入执行时,面对的是一份相对干净的任务说明,而不是越来越乱的话题记录。
在实现层面,这两条流往往对应着不同的数据结构。输入流是偏向声明式的任务描述(目标、约束、验收标准),反馈流则是结构化的事件序列(验证结果、错误代码、回归范围)。把二者分开存,还有一个额外好处:可以方便地复盘某次任务为什么走了弯路,查看是哪一步的档案导致模型误判。
3. 从零搭一个最小 Harness:接口、状态机与工作流配置
3.1 核心接口定义
去掉花哨的界面,框架的骨架其实就是几个接口。我习惯用伪代码来描述这套约定,因为它不受具体语言约束,落到 Python、TypeScript 或 Go 上都行。
class Task: id: str description: str # 自然语言需求 target_files: list[str] # 允许读取/修改的文件范围 constraints: list[str] # 禁止事项 acceptance_criteria: list[str] # 验收标准 tools: list[str] # 允许调用的工具白名单 class AgentContext: task: Task workspace_snapshot: str # 代码库快照或相关文件内容 extra_evidence: list[dict] # 验证器回填的失败信息 class WorkflowStep: name: str action: str # generate, verify, patch, review next_on_pass: str next_on_fail: str max_retries: int class HarnessEngine: def execute_step(step, context) -> StepResult: ... def run_workflow(task) -> WorkflowResult: ...一个容易被忽略的点是constraints字段。它需要写得很明确,比如“不要修改公共接口签名”“不要删除现有导出符号”“不要新增第三方依赖”。这些约束并不是为了限制模型创造力的,而是为了守住代码库的兼容性底线。实际使用中我观察到,模型在缺乏约束时很倾向于顺手重构周边代码,而重构往往是回归事故的主要来源。
AgentContext.workspace_snapshot也值得一提。它不见得是完整仓库,而是一个按需构建的上下文包。后面专门讲上下文污染时会详细说,这里先记住:宁可少给上下文,也不要多给。模型处理不了那种“全部都相关”的场景,给的信息越多,决策噪声越大。
3.2 任务状态机的设计
状态机是整个 Harness 的中枢。我把每个任务的生命周期定义成八个状态:
pending -> running -> awaiting_verification ^ | | v | failed | | v v retrying -> completed / terminatedpending 表示任务排队;running 表示执行器正在和模型交互;awaiting_verification 表示产物已经产生,等待验证器检查;failed 是验证不通过时进入的等待状态;retrying 表示即将用回灌后的新上下文再次执行;completed 和 terminated 分别是成功与终止终点。
设计状态机时必须想清楚一个问题:什么样的失败值得重试,什么样的失败应该直接终止?如果失败原因是“编译错误”,说明模型生成的代码语法或依赖层面有问题,在上下文还比较完整的时候,重试是有意义的;如果失败原因是“任务描述与代码库事实冲突”,比如模型要修改的文件根本不存在,重试多少次都没用,此时应该直接终止并转人工。我把这种判断写进状态机的转移条件里,而不是靠运气。
3.3 工作流配置的最小范例
在框架里,“流程”不应该硬编码在代码里,而应该以配置的形式存在,这样才能在不上线代码的情况下调整编排策略。下面是一个极简配置示例:
workflow: name: quick_fix_flow steps: - name: generate_patch action: generate next_on_pass: run_static_checks next_on_fail: terminated - name: run_static_checks action: verify_static next_on_pass: run_tests next_on_fail: retry_with_feedback - name: run_tests action: verify_tests next_on_pass: completed next_on_fail: retry_with_feedback retry_policy: max_retries: 3 backoff_seconds: 10这个配置表达的意思非常直接:先生成补丁,补丁通过静态检查后跑测试,全部通过才算完成;任何一步失败,最多重试三次。把流程做成配置的最大好处是可审计性——团队里任何一个人看到这个文件,都能知道这次 AI Coding 的自动化边界是什么,而不是在黑盒里猜。
我在这套配置运行了一个迭代之后,又加入了一个impact_analysis步骤,放在 generate 之后。它要求执行器额外产出一份“影响文件列表”,明确说明这次改动会波及哪些调用方。这个步骤的引入,让大部分回归风险在更早的阶段就暴露出来,后面再展开讲。
4. 把代码生成编进流水线:最实用的三种编排模式
4.1 同步闸门模式:生成后立刻检查
第一种最常用,也最容易被理解:生成代码后,立刻依次跑静态语法检查、编译、单元测试,只有全部通过才允许提交。它的特点是一次执行内完成闭环,适合改动范围小、验收标准清晰的任务。
同步闸门模式实现上不复杂,就是把验证器接到生成步骤的紧后方。关键在验证器的设计要足够快,快到不能让模型等待十几分钟才有反馈。如果全量编译太重,可以先做模块级编译;如果全量测试太重,可以先跑目标测试子集。慢验证会拖垮整个流程,也会浪费执行器的并发资源。
这种模式对“状态明确”的任务特别有效,比如修复一个 lint 错误、给函数补参数校验、修改配置项默认值。模型有明确的目标,验证结果又是非黑即白,通常一次到两次就能收敛。我之前在内部实验里做过统计,同步闸门模式下简单任务的中位收敛轮次在 1.5 轮左右,也就是第一次通过率已经比较可观。
4.2 异步自愈模式:把失败信息回灌给 Agent
第二种模式更接近“智能迭代”的形态。它不是在模型执行结束后立刻判结果,而是允许一个完整循环:生成 → 验证 → 回灌失败信息 → 生成下一版。这种模式下,给模型看什么样的失败信息,直接决定了修复效果。
我通常会把验证失败整理成一个结构化摘要,而不是把原始日志直接丢给模型。原始日志动辄几百行,模型在其中筛重点的效率很低;而结构化摘要会指明“错误类型:类型不匹配;位置:order_service.py:87;期望类型:int;实际类型:None”,这样模型可以直接针对问题出具补丁。这里的关键取舍是:摘要不能过度清洗,否则会丢失必要线索。
异步自愈模式还要特别注意设置迭代次数上限。不是每个问题都能靠自愈解决,当模型连续两轮产生相同错误时,继续让它自我修复基本没有意义。我习惯在配置里把max_retries设为 3,并且要求每一轮的补丁必须是增量修改,不允许整文件重写。整文件重写会带来很高的“看似解决、实则破坏”风险。
4.3 人工审核节点:在自动化和人工之间留一道口子
经历了前面两种模式之后,我最大的体会是:再怎么自动化,也要在关键节点上保留人工审核。我的做法是把全流程分成两段:前半段 AI 全自动,产出补丁和测试结果;后半段人工审核补丁内容、影响范围、测试报告。通过后再合入主干。
有人会担心人工审核变成瓶颈,但实际经验恰恰相反。把人工审核的时间点放对后,总耗时反而下降,因为人工不必再处理“早期就能避免的低级错误”,可以把精力专注在语义判断上。比如“这个改法是否违背了业务预期”“这个接口变更是否需要同步更新调用文档”,这些事情是验证器很难自动判定的。
我常开玩笑说,AI Coding 的工程化不是要替代人工,而是要把人工从“传话筒”变成“安检员”。安检员的工作量比传话筒小得多,但产生的价值高得多。
5. 真实落地时踩过的坑:上下文污染、工具白名单、重复修复
5.1 上下文污染:别把整个仓库灌给模型
一开始我觉得上下文越全,模型判断越准,于是把整个仓库的关键文件都塞进上下文包,以为这样模型就能理解全局。实践结果非常打脸:模型确实“读过”了所有文件,但它的注意力被大量无关内容稀释,generate 出来的补丁经常跑偏。
一次典型的失败:让模型修改某个列表的排序逻辑,上下文包里包含了十几个无关模块的代码。模型在分析时开始纠结其他模块里根本没发生的“潜在问题”,最后给出的补丁动了好几个不该动的文件。我把那次补丁 diff 拿出来看,其中 80% 的改动和原始任务没有任何关系。
之后我改成了按需装配上下文的策略:先由任务解析器根据描述提取可能相关的文件清单;再执行器只加载这些文件到上下文包;如果验证器反馈“符号未找到”,再由符号检索工具动态补充。这一改动最直接的效果就是补丁污染率下降非常明显。
5.2 工具权限:把命令收敛成白名单
另一个踩得比较深的坑是工具权限放得太宽。最开始我们允许执行器在沙箱里自由运行 shell 命令,模型很多看似聪明的行为,会引发难以追踪的副作用。有一次模型为了“确认测试环境”,直接运行了安装依赖的命令,结果把依赖版本改掉了,整条流水线后续全部失败。
后来我把工具注册表收敛成一份白名单,而且每个任务绑定不同的工具子集。比如“修复类型错误”的任务,只允许读取文件、运行 tsc 检查、查看符号定义;只有“处理依赖升级”的任务,才允许接触包管理器命令。白名单之外的能力一律不可用,哪怕模型自己“想”用也不行。
现在我的原则很简单:工具权限要遵循最小化原则,给模型一个封闭的玩具箱,而不是给它一把万能钥匙。工具越少,行为空间越小,验证器需要覆盖的分支就越少,整个系统越可控。
5.3 重复修复:循环失控的扳机
异步自愈模式里最常见的失控表现,是模型用完全相同的方式重试同一个失败,而且重试了七八次。原因有两种:一种是模型的上下文不够,不足以生成新的策略;另一种是验证器的失败信息太模糊,模型根本不知道问题在哪,只能猜。
针对第一种原因,我会在上一次失败后强制更新上下文档案,把失败涉及的代码区域重新快照,并给模型额外的“假设清单”,让它先说明失败原因再生成补丁,而不是盲改。针对第二种原因,问题就出在验证器的信息设计上,行动明确规定验证报告必须有文件路径、行号、错误代码、可执行的复现命令。没有这些信息,验证就不合格。
我还在执行器里加了一个检测逻辑:如果连续两轮产生的补丁 diff 完全相同,直接终止任务并转人工。这个策略帮我省下了大量无意义的反复执行开销,也让整个框架的输出行为变得更加有边界。
5.4 局部改动与全局接口一致性
最后一个坑比较隐蔽。模型在修一个局部功能时,只看得见当前文件,很容易忽略函数签名与调用方的全局契约。最典型的表现是:单元测试全部通过,冒烟测试全部通过,可到了联调阶段,别的模块调用这个函数时立刻崩。
应对方法是在工作流里增加“影响面分析”步骤。generate 之后,执行器强制运行一次符号引用搜索,找出所有调用当前改动的外部位置,并生成一份影响清单。这份清单会回传给人工伤检节点,由人来判断是否需要连带更新调用方。
不要高估模型对全仓库引用关系的把握。它能在单文件维度表现优秀,但在跨模块的 API 变更上,自动化工具比模型自己的“记忆”可靠得多。
6. 用数据判断“工程化”是否成型:四类指标
6.1 任务完成率
工程化必须有数字说话,而不能靠感觉。我这里最重要的一点是任务完成率:进入工作流的所有任务里,有多少到达 completed 状态。这里的完成不指“模型给出了补丁”,而是“补丁通过验证、通过人工审核、成功进入代码库”。
在早期版本的框架里,我的任务完成率只有六成左右,大量任务卡在验证和人工审核环节。后来加入上下文按需装配、工具白名单、影响面分析之后,完成率逐渐上升到八成以上。这个数字并不等于模型变得更强了,而是说明流程把错误的路径挡住了。
6.2 收敛轮次
收敛轮次反映的是任务从开始到完成的平均执行循环次数。收敛轮次低,意味着第一次生成质量高,或者失败回灌效率好;收敛轮次过高,则说明上下文或验证信息存在系统性的问题。
我自己的经验阈值是:简单修复任务的中位收敛轮次应该在 1 到 2 之间,复杂重构任务可以在 3 到 4 之间。如果某个任务类别长期高于这个区间,我不会怀疑模型,而是去检查验证器输出的失败信息是不是太冗余或者太模糊。
6.3 上下文命中率
这是一个我习惯的指标,不一定会出现在其他团队的监控面板里:在一次执行中,模型实际用到上下文里的哪些信息。我会记录两次执行之间模型询问“这个函数定义在哪”的频次。如果频繁出现这类问题,说明上下文包对关键符号的覆盖不足。
上下文命中率的提升通常伴随着收敛轮次的下降。当我观察到这两个指标同时变差,会优先审计任务解析器生成文件清单的逻辑,看看是不是没有很好地把用户意图映射到文件集合上。
6.4 回归故障率
最后一个指标是回归故障率:部署到真实环境后,由 AI 补丁引发的线上故障占总故障的比例。这个数字直接反映自动化流程的信任度。即使完成率高、收敛轮次低,如果回归故障率居高不下,说明验证环节漏掉了关键风险。
压降回归故障率的主要手段是加强人工审核和影响面分析,而不是让模型自己给自己把关。模型在评估自己补丁的影响范围时,有明显的过度自信倾向;适当引入独立的静态分析工具,会比让模型重新解释一遍自己的补丁可靠得多。
7. 一点个人经验:什么场景值得上这套框架
从单次对话到 Agentic Harness Workflow 框架,并不仅仅是加了一层工具壳,而是一次心态转变:由信任模型的“灵光一现”,转向信任流程的“稳定输出”。我现在会把任务分成三类:轻度咨询类,适合直接用对话,因为成本最低;中型改动类,适合轻量级工作流,套一个同步闸门验证就够了;大型重构或多文件协调类,则需要完整的工作流,把任务解析、影响面分析、异步自愈、人工审核全部衔接上。
如果业务还停留在一套代码库、两三个人偶尔用 AI 改代码的阶段,整套框架确实是过度设计。但如果团队有几十人并行迭代,每个迭代周期都要产出大量 AI 生成的补丁,那么没有一个工程化的 Harness 是不行的——你会在两周之后被验证、返工、沟通成本拖垮。
我在实际落地时的体会是:框架的第一版不要追求完整,先搭一个能跑通“生成、验证、失败回灌”三个环节的最小闭环,然后观察指标、记录失败、逐步叠加新工具。所谓工程化不是一次性建好完美流水线,而是让每一次失败都能推动下一次流程变得更靠谱。