最近我梳理项目的 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 是不行的——你会在两周之后被验证、返工、沟通成本拖垮。我在实际落地时的体会是框架的第一版不要追求完整先搭一个能跑通“生成、验证、失败回灌”三个环节的最小闭环然后观察指标、记录失败、逐步叠加新工具。所谓工程化不是一次性建好完美流水线而是让每一次失败都能推动下一次流程变得更靠谱。