☰
AI Coding工程化:Agentic Harness Workflow框架的设计与实践
2026/10/20 10:00:25 网站建设 项目流程

最近半年我一直在折腾一件事:把 AI 写代码从“聊天式生成”变成“流水线式执行”。核心落到一套我自己搭的Agentic Harness Workflow 框架上。简单说,就是在 AI Coding 这层加上一个“安全带 + 方向盘 + 行车记录仪”的壳,让每一个代码生成任务都遵循固定的流程、带着清晰的上下文、遵守明确规则,并且每一步都能被回放排查。

以前我用 AI 改代码是这样的:丢一段代码进去,描述需求,AI 输出修改建议,有概率跑通,也有概率给你一顿乱改。一次成功挺好,第二次换个需求又回到原点。后来我发现问题不在模型本身,而在流程设计——模型是无状态的工人,你需要给它一个工作台、一本规范手册和一套验收标准。这就是 Harness 的定位:把零散的 Prompt 变成可管理的工程资产。

这篇文章我想把这个框架的核心思路、关键设计、实际操作和一些坑整理出来,尤其适合那些正在把 AI Coding 落地到日常开发、但被“不稳定”折磨得够呛的开发者。不管你是刚接触还是已经试过几个工具,照着这套思路搭建,都能明显提升 AI 代码产出的可控性和可复现性。

1. AI Coding 的工程化困境与思路取舍

1.1 为什么需要 Harness?从“跑通一次”到“稳定复现”

先说个我自己的真实感受:单次使用 AI 写代码的体验,和把它当队友用,完全是两回事。

单次使用是“碰运气”,上下文、模型状态、Prompt 措辞稍有变化,结果就跟着变。我最初接一个模拟项目 X 的 API 对接需求,AI 生成的代码第一次跑通了,第二次把参数顺序写错,第三次干脆生成了一版完全不同风格的接口封装。问题很典型:模型不会自己记住“我们已经定了要用这套签名”,也不会主动遵守“不要动已有的工具函数”。

所以 Harness 的核心价值在于三个词:约束、延续、验证。约束是指给 Agent 定义一套工作边界;延续是让整个任务链共享同一个决策上下文;验证是让每一步的结果都接受检查,不通过就不进入下一个环节。

这个思路其实非常像传统软件开发里的“脚手架”。你不指望工人靠记忆干活,而是给他图纸、规范、检查单。AI Coding 能不能工程化,取决于你有没有这套配套机制,而不是模型本身够不够聪明。

我早期犯过的错是试图用“更长的 Prompt”解决所有问题。结果 Prompt 从 500 字涨到 3000 字,效果反而更差。因为上下文里的噪声多了,模型不知道该重点听谁的。后面拆成“任务描述 + 规则约束 + 上下文资料”三块,各管各的,稳定性才上来。

1.2 框架选型的核心取舍:通用编排 vs 垂直线束

接触这个方向时,网上一堆工具和框架,绝大多数可以分成两类:通用编排框架和垂直线束框架。

通用编排框架的特点是抽象层次高,什么都能干,任务调度、状态持久化、多 Agent 协作、工具注册全都有。但它的问题也很明显:学习成本高、配置项多、一旦出错排查链路特别长。我和一个小团队试过两周,大部分时间花在理解框架的“哲学”上,而不是解决实际的代码生成问题。

垂直线束框架则是一套固定的工作流模板,比如“拆分任务 → 生成代码 → 跑测试 → 收集反馈 → 重新生成”。优点是开箱即用、逻辑清晰,适合 80% 的日常开发任务;缺点是灵活性差,遇到不在模板内的场景就难办。

我最终采用的是“以垂直线束为主,以通用编排能力为辅”的方案。也就是把主流程固定成几个阶段,每个阶段用代码写死;遇到特殊需求时,允许手动插入一个自定义任务节点。这样既保证了大部分任务的稳定迭代,又留有扩展空间。

如果你也要自己搭,建议先画一张流程图,把“哪些环节是每次都一样的、哪些环节可能变化”标出来。每次都一样的部分用代码固化,可能变化的部分设计成配置项。千万不要一上来就引入重量级框架,你的问题可能只需要一百行状态机代码就能解决。

2. 核心细节解析:Harness 的构成与关键设计

2.1 三件套:Agent 实例、任务上下文、规则约束

我把 Harness 的最小单元定义为“三件套”:Agent 实例、任务上下文、规则约束。三者缺一不可。

Agent 实例就是执行任务的“工人”。它包含模型配置、温度参数、工具权限、最大输出长度等。不同任务需要不同的工人配置:写单测的任务,温度可以调低,输出要精炼;做架构设计的任务,温度可以略高,允许发散思考。

任务上下文是 Agent 干活时能看到的资料。包括仓库结构、相关文件内容、任务说明、编码规范摘要、历史决策记录。上下文不是越多越好,而是越精准越好。我后面会专门讲怎么控制上下文注入。

规则约束是一组 Agent 必须遵守的边界。包括禁止修改的文件列表、必须使用的命名风格、输出格式要求、不允许删除已有功能等。

这三者的组合方式直接决定了任务成败。实践中我会给每个任务单独维护一份 YAML 配置,大致长这样:

task_id: task_001 agent: model: code-agent-v2 temperature: 0.2 max_tokens: 4096 tools: ["read_file", "write_file", "run_command"] context: repo_map: docs/repo_map.md relevant_files: - src/core/parser.py - src/core/lexer.py task_desc: "修复解析器对注释行的误判问题" decisions_log: docs/decisions/001-comment-handling.md rules: forbidden_paths: - tests/snapshots/ required_outputs: - "必须补充至少一个失败用例" style: "遵循项目现有的 snake_case 命名规范"

一开始我直接把这三块塞进一个 Prompt 里,后来发现 Agent 很容易忽略夹在中间的规则,尤其是当任务描述很长的时候。调整后我把规则放在上下文最显眼的位置,紧跟任务描述之后,问题频率大幅下降。经验是:规则约束一定要像工具栏一样常驻,而不是藏在一堆背景资料里。

2.2 工作流编排:串行、并行与条件分支

三件套解决的是单个任务如何执行的问题,但真实开发是一个多阶段过程,任务之间存在依赖关系。所以 Harness 必须有编排能力。

我实现的编排器本质上是一个状态机,每个任务有五种状态:pending、running、success、failed、retry。执行器从队列中取出状态为 pending 的任务,检查它的前置依赖是否全部 success,然后开始执行。执行结束后根据结果更新状态并触发后续任务。

编排方式有三种。串行用于强依赖场景,比如“先确认需求再写实现”;并行用于文件级相互隔离的修改,比如“A 任务改前端组件,B 任务改后端接口定义”;条件分支用于需要根据执行结果动态决定下一步的场景,比如“测试通过就进入审查,测试不通过就回到生成阶段重跑”。

并行最容易出问题。踩过一次挺深的坑:两个 Agent 同时修改同一个文件,一个改的是上半部分,一个改的是下半部分,结果 Git 合并时冲突一堆。从那以后我加了一条硬性规则——任何并行任务,修改文件集合必须严格不相交。配置里用file_lock字段声明每个任务需要独占的文件,编排器在启动任务前做一次校验。

![workflow state diagram]

(注:流程图这里就不贴了,状态流转其实很简单:pending → running → success/failed,failed 且重试次数未耗尽时回到 pending。)

条件分支的实现上,我用了“后置钩子”机制。每个任务执行完,Harness 会读取它的输出目录下有没有一个verdict.json,里面写着pass或fail,以及对应的下一步指令。这样分支逻辑不再写在编排器内部,而是让每个 Agent 自己给出建议,编排器只做决策执行。这种设计的好处是角色清晰:Agent 负责判断,编排器负责流转。

2.3 可观测性与回放机制

Harness 之所以叫 Harness,很大程度上在于它不只是指挥干活,还要监控全过程。我把可观测性设计成了三层。

第一层是操作日志。每次 Agent 调用模型的请求和响应、工具调用记录、文件写入内容、命令执行输出,全部按时间戳追加到一个 trace 文件里。

第二层是变更快照。在每个任务开始前,对涉及的文件做一次全量快照;任务结束后再做一次,并生成统一的 diff 页面。这么设计是因为我遇到过“文件改错了想回退,但记忆里的版本已经不对了”的情况。有快照之后,回退变得没那么提心吊胆,直接恢复即可。

第三层是决策轨迹。我要求 Agent 在每个任务快结束时写一份简短“决策说明”,解释为什么这么改、有哪些备选方案、选了哪个、为什么。这份文档很影响后续任务的执行,因为下一个任务可以拿它当上下文里的“历史决策记录”,避免重复踩坑。

回放机制的实现也谈不上复杂。就是给每个 trace 文件起一个带运行编号的目录名,比如run_20250215_143322/。想回放时,重新创建同样的 Agent 实例,注入同样的上下文包,把规则约束原样附上,然后重放该任务的指令序列。这里有个关键点:模型输出可能有随机性,回放不能保证结果完全一致,但能让排查范围从“全部流程”缩小到“具体某一步的决策差异”。

3. 实操过程:从零搭建一个可复用的写代码工作流

3.1 定义目标与边界:先写 SOP 再写代码

搭建 Harness 的第一步,不是写配置,而是写 SOP。如果连你自己都不清楚一项代码任务的流程是什么,那框架只会放大混乱。

我的做法是:把一次完整的 AI Coding 任务拆成七个固定环节:需求澄清 → 影响面分析 → 设计 → 实现 → 自测 → 审查 → 收敛。每个环节定义四个属性:输入、输出、负责人、验收标准。

举个例子,在某跨平台系统里需要新增一个“导出当前报表为 CSV”的功能。我是这样拆的:

环节输入输出验收标准
需求澄清用户原始描述结构化需求清单确认导出字段、文件名规则、编码格式
影响面分析需求清单、仓库地图受影响的文件列表所有涉及的模块被列出,无遗漏
设计影响面文件接口签名和修改计划接口签名符合项目现有风格,改动点清晰
实现设计文档代码改动编译通过,新增导出逻辑
自测改动代码测试结果新增单测覆盖导出函数,现有测试不回归
审查改动、测试结果审查意见无明显的安全问题和资源泄漏
收敛审查意见最终代码合入冲突解决,变更日志更新

这里有个不得不说的重要提醒:验收标准不能写“完成代码”这种模糊表述。每一条验收标准都要能被脚本或命令验证。否则你无法确定 Agent 是否真的完成任务。

SOP 写好后把它存成一个文档并放到仓库根目录的docs/sop.md,每个 Agent 的任务上下文里都会带上提炼版。这样它们对流程的认知是一致的。

3.2 构建 Harness 的上下文注入

有了 SOP 之后,具体任务执行时要做上下文注入。这一步是把仓库里的信息按需组装给 Agent,而不是把整个仓库倒进去。

我见过很多人在这里翻车:直接把整个项目目录交给 Agent,以为模型能自动提取重点。实际上,大模型的注意力是有限的,相关代码被淹没在不相关的文件中时,输出质量会明显下降。所以上下文注入必须带有选择性。

我目前在用的流程是三步。第一步,生成仓库地图。用tree命令或脚本扫一遍目录结构,过滤掉node_modules、__pycache__之类的无关目录,生成一份精简地图。第二步,关键词检索。把任务描述里的核心名词抽出来,在代码库里搜索,找到相关文件。第三步,人工确认。这个流程在自动化之前我建议至少手工跑几次,慢慢把规则固化下来,后面才能放心交给 Harness。

上下文包我推荐用带有元信息的结构,每个文件都标注用途,Agent 更容易理解为什么给它看这个文件。举例来说,上下文字段可以分成三类:

  • 必读:任务直接相关的文件,一般 3~5 个。
  • 参考:设计文档、历史决策记录,用于风格对齐。
  • 背景:仓库地图、SOP 摘要,用于理解全局。

实践中我发现一个比例,必读文件占总上下文的 70% 左右,参考占 20%,背景占 10%,效果比较理想。当然这只是经验值,不同项目可以调整,但“任务相关文件占比最大”这个原则基本通用。

3.3 编写规则约束与验收命令

规则约束是 Harness 的核心资产之一。它直接决定了 Agent 是在“框架内发挥”还是“放飞自我”。

我的规则库分两层。全局规则对所有任务生效,包括:禁止修改未列入影响面的文件、不允许删除公共工具函数、提交信息必须遵循 Conventional Commits 格式、所有新增函数必须有类型注解。任务级规则由每个任务自己声明,典型内容包括:这次修改必须保持既有 API 兼容、必须处理某个边缘输入、不需要更新文档。

规则要可执行,才不是空话。比如“不能破坏现有功能”,这句本身没法被验证,但“必须运行pytest tests/regression/且全部通过”,就是一条可以检查的规则。

验收命令我集中写在一个verify.sh脚本里。这个脚本接收任务产物目录和任务配置,依次执行编译、单元测试、静态检查,并输出通过/失败结果。用脚本的好处是统一入口,Agent 只有一条命令需要记,Harness 也不需要单独为每个任务写验证逻辑。

一个最小版本的长这样:

#!/usr/bin/env bash set -euo pipefail TASK_DIR="$1" CONFIG="$2" # 1. 编译检查 make build 2>&1 | tee "$TASK_DIR/build.log" # 2. 运行测试 pytest tests/ -x -q 2>&1 | tee "$TASK_DIR/test.log" # 3. 静态分析 ruff check "$TASK_DIR/src" 2>&1 | tee "$TASK_DIR/lint.log" # 4. 输出结果 echo "verify done"

我踩过的坑是:最初把“警告级别”也当作失败条件,结果任务频繁卡在“某行代码风格不佳”这种小事上。后来我改成两类:必须通过的硬性检查,比如编译和测试;只提示不阻断的软性检查,比如 lint 警告。这样既守住了底线,又不会因为一点小警告打断流程。

规则还有一个优先级设计问题。当规则之间冲突时,比如“允许修改文件列表”和“禁止修改公共接口”同时命中一个文件,明确谁优先。我目前的策略是文件级规则优先于全局规则,禁止类操作优先于允许类操作。这个优先级在任务配置里显式声明,避免 Agent 自己猜测。

3.4 跑通、回放与调优

配好 SOP、上下文、规则之后,进入实际执行阶段。我强烈建议第一次不要拿大任务试水,而是挑一个“修复类型错误”级别的小任务。跑通的意义不在于任务本身有多重要,而在于验证整个链路是否闭环。

第一次跑通最小闭环时,我用的任务是:给某工具函数补上缺失的空值检查。整个流程大概花了三分钟。Harness 自动完成需求澄清、影响面分析(就一个文件)、实现、跑测试、输出 diff。当时看下来改动质量不错,但过程中暴露了两个问题:一是规则约束里没有声明“不要修改函数签名”,结果 Agent 擅自改了参数名;二是验证命令的软性检查输出太多,干扰了后续判断。这两个问题反馈回规则库,下一轮任务就明显稳定了。

任务失败时的回放排查,是我的标准操作。先看 trace 文件里 Agent 第一步的思考过程,判断它对任务的理解是否正确;再看它执行了哪些工具调用,有没有超出边界;最后对比生成前的快照和生成后的 diff,找出问题所在。多数失败的根源不外乎三类:上下文缺信息、规则漏约束、模型温度过高导致结果发散。

调优的顺序也有讲究。我先调上下文注入,再调规则约束,最后才考虑换模型参数。因为上下文和规则是确定性因素,改了必然影响所有任务;模型参数是非确定性因素,调了半天也许只是运气问题。除非上下文和规则已经做到位,才值得动模型参数。

调优需要对比数据。我习惯每次改动前后各跑 5 次同样的任务,统计通过率、平均修改文件数、需要人工介入的次数。通过率从 40% 提到 80% 的过程,大多数时候靠的是上下文和规则的修正,而不是换一个更强的模型。

4. 常见问题与排查技巧实录

4.1 模型“跑偏”了怎么办?

所谓“跑偏”,指的是 Agent 的输出明明不符合任务定义,却一本正经地交付了。比如让它改 A 模块的接口参数,它顺手把 B 模块的日志格式也改了;让它修一个 bug,它顺带重构成了另一个函数。

这类问题在早期出现频率很高。后来我总结了三个排查方向。

第一,检查任务定义是否在上下文最前面。Agent 是顺序阅读上下文的,如果任务描述被埋在长文档中间,很容易被遗忘。把“目标”放首位,是一个成本极低但效果明显的改进。

第二,检查规则约束里的“禁止项”。如果只写了“应该做什么”,模型可能会默认“其他没说的都能做”。我后来在每条任务规则后面补了一句“未经允许,不得修改影响面之外的任何文件”,跑偏概率显著下降。

第三,检查上下文里有没有相互矛盾的信息。比如仓库地图显示某个文件是旧版,而任务背景里又引用了新版行为,模型在矛盾信息中往往会选择“全都要”,结果产物变得面目全非。解决方法是,发现问题后立刻修正上下文来源,让参考文档之间保持一致。

“跑偏”的治理是一个持续过程。我的做法是建立一份“跑偏案例库”,每次出现典型案例就补一条对应的规则。跑偏案例越多,规则库越厚,后续任务就越稳。框架的价值不在于第一次就完美,而在于每次失败都能沉淀成资产。

4.2 上下文泄漏与污染问题

上下文泄漏是一个比较隐蔽的问题。症状是:任务 A 的决策影响了任务 B 的输出,但任务 B 本来不应该知道任务 A 的细节。

有个印象比较深的例子:系统里有两个独立模块,我并行跑两个任务分别优化它们的错误处理逻辑。结果其中一个任务在全然不知情的情况下,采用了另一个任务的异常类型命名风格,还试图统一两者的错误码,导致合并时出现一堆本不需要的改动。

原因追溯下来是全局上下文库被无差别地注入到了每个任务里。全局信息本意是让 Agent 熟悉项目规范,但它也把本应隔离的任务间信息暴露了出去。解决方法是坚持“最小必要上下文”原则:任务能看到的资料,仅限于完成该任务所必需的内容。全局上下文只保留仓库地图和通用规范摘要,具体模块的决策记录只注入给相关任务。

另一个污染来源是历史输出的回灌。有些框架会把前一个任务的输出追加到下一个任务的上下文里,意图是保持一致性,但往往造成越积越多的冗余。我的做法是,下一个任务只接收上一个任务的结构化总结,而不是原始输出。结构化总结里只写关键决策和变更摘要,噪声远小于全量内容。

4.3 并行任务合并冲突

并行执行是 Harness 的加分项,但也是翻车重灾区。前面提过文件锁,这里展开说几个细节。

首先,任务划分阶段就要做文件冲撞检测。把所有任务需要修改的文件集中到一个清单,两两比对,凡是存在交集的任务,要么合并成一个串行组,要么明确拆分文件边界。这一步最好在配置加载时自动执行,让人工无法在不知情的情况下并行改同一个文件。

其次,在 Git 层面做双保险。我给每个任务开一个临时分支,任务完成后再合入主分支。分支的好处是单个任务出问题不会污染主干。合入时如果出现冲突,说明前面的文件锁规则没有被严格执行,这时候要回到流程层修正,而不是在冲突现场手动解决。

还有一个小技巧:并行任务尽量按模块粒度切分,而不是按函数粒度切分。按函数切分,两个任务很容易碰触同一个文件;按模块切分,交集概率低得多。哪怕某个模块的改动量很大,让单个 Agent 负责整个模块,串行完成内部多个函数修改,也比“两个 Agent 分头改一个模块”稳定得多。

如果并行任务不得不覆盖同一个文件的相邻区域,一个可行的办法是先把文件拆成两个独立文件(比如公共部分抽出去),再并行修改,最后在收敛阶段合并。但这是少数情况,日常开发里我建议能避就避。

4.4 实战收益与避坑心得

跑了将近两个月的 Harness 工作流后,我把收益和坑分别理了一份。

收益方面,最明显的是返工率下降。在没有 Harness 的时候,AI 生成的代码大概两三成需要大幅修改之后才能合入;有了框架约束和验证环节之后,这个比例降到一成以内。其次是节奏变稳了,以前是“有空才让 AI 写代码”,现在只要定义好任务,Harness 能连续跑几个任务,人只需要在关键节点做审查。

团队里有新人接手时,框架的作用也很明显。新人不需要理解每一个 Prompt 的玄学,只要按 SOP 定义任务、看 Harness 的 trace 报告、处理验证失败即可。隐性经验被显性流程取代了,这正是我理解“AI Coding 工程化”的含义。

坑也不少。最大的坑是过度工程化。我第一版 Harness 想得很宏大,要做插件系统、多租户隔离、动态任务图,结果开发了半个月,核心流程还没跑通。后来推倒重来,先做一条能跑通的最小闭环,其他功能等真实需求出现了再加。

另一个坑是盲目追求“完全自动化”。有些环节,比如需求澄清和影响面分析,目前还是需要人来判断的。硬要让 Agent 全自动完成,结果往往是越权修改范围。我的策略是“半自动”:在关键节点设置人工确认闸口,Agent 给出建议,人来拍板。

最后分享一个还算实用的经验:每一次成功运行后,把这次运行的任务配置、上下文快照、规则集、最终产物存成一个“黄金样本”。下次遇到类似需求,直接以黄金样本为起点做少量修改,而不是从零开始定义任务。这个方法比任何参数调优都更直接地提高成功率,本质上就是不断积累项目自己的“最佳实践数据集”。

我个人实际操作中最深的一点体会是:Harness 不是用来“管住”模型的,而是用来让整个团队对模型的行为形成稳定预期的。模型在框架内发挥,框架负责兜底。你不需要一个每次都能打开局面的天才 Agent,你只需要一个稳定交付、边界清晰、能追溯的 AI 同事。把一次成功的运行固化下来,不断优化下一次的成功率,这件事比追新模型、试新工具都更值得投入。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询