1. 为什么我们需要一个 Agentic Harness Workflow
1.1 从“对话式写代码”到“工程化流水线”的认知转变
过去一年多,我身边不少开发者对 AI Coding 的态度经历了三个阶段:最开始是“哇,它居然能补全一整段函数”,然后是“它写的代码我得逐行 review,比自己写还累”,最后变成“算了,还是自己来吧”。这个曲线背后其实藏着一个很朴素的事实——把 AI 当成一个更聪明的自动补全工具,收益是有天花板的。
真正让效率产生质变的,是把 AI 从“对话框里的助手”重新定位成“流水线上的一个工位”。这个工位有明确的输入、明确的输出、明确的验收标准,还有明确的失败重试机制。Agentic Harness Workflow 这个框架,本质上就是给 AI Coding 搭一套“工装夹具”(harness 这个词在工程语境里就是夹具、测试台架的意思),让它在一个受控的、可观测的、可复现的环境里干活,而不是每次都在一个空白对话框里即兴发挥。
我最初接触这套思路是在做一个内部工具链重构的时候。当时的需求很典型:把一个老模块的几十个接口逐个迁移到新架构上,每个接口的逻辑都不复杂,但数量多、模式重复、验收标准清晰。如果纯手工做,大概要两周;如果纯靠对话式 AI,每次都要重新贴上下文、重新解释规范、重新检查它有没有跑偏,实际省下来的时间可能只有三成。后来我把整个流程拆成了“任务定义 → 上下文装配 → 执行 → 自动校验 → 失败回炉”几个环节,用脚本把它们串起来,AI 只在“执行”这个环节里干活,其余环节全部由工程化的代码控制。结果那批接口迁移的实际耗时压到了三天,而且返工率极低。
这就是 Agentic Harness Workflow 的核心价值:它不追求让 AI 一次做对,而是让 AI 在一个能自动发现错误、自动纠正错误的闭环里持续工作。
1.2 这套框架到底解决什么问题
我把实际踩过的痛点列一下,你看看有没有共鸣:
- 上下文漂移:同一个任务,你分三次跟 AI 说,它三次给出的实现风格可能都不一样,变量命名、错误处理方式、目录结构全在飘。
- 验收靠人眼:AI 写完一段代码,你得自己跑一遍、自己看 diff、自己判断对不对,这个环节完全没法自动化。
- 失败无记录:某次 AI 写错了,你纠正了它,但下次遇到同类问题它还会犯,因为纠正的过程没有被沉淀成规则。
- 任务粒度失控:一个“帮我重构这个模块”的大任务丢给 AI,它要么做一半卡住,要么自作主张改了一堆不该改的地方。
- 无法并行:多个独立的小任务本来可以同时推进,但因为都挤在同一个对话框里,只能串行等待。
Agentic Harness Workflow 的思路是把这些问题逐个拆解:用结构化的任务描述解决上下文漂移,用自动化校验脚本解决验收问题,用规则库和失败案例库解决经验沉淀问题,用任务分解器控制粒度,用多实例调度实现并行。
1.3 适合谁来用这套东西
说实话,这套框架不是给“我就想让 AI 帮我写个正则表达式”这种场景准备的。它适合的是:
- 手上有批量、重复、模式化的编码任务,比如接口迁移、测试用例补全、日志埋点注入、配置项批量修改。
- 团队里已经有一定工程化基础,能写脚本、能跑 CI、有代码规范约束。
- 愿意花一两天时间搭基础设施,换取后续几周甚至几个月的效率提升。
如果你符合上面任意两条,那这套东西的投入产出比会非常可观。下面我按实际搭建的顺序,把每个环节拆开讲。
2. 框架整体设计与核心模块拆解
2.1 整体架构:五个环节串成一条流水线
我把整个 Harness 分成五个核心模块,它们之间的关系是流水线式的,但每个模块内部都可以独立替换和升级:
| 模块 | 职责 | 输入 | 输出 |
|---|---|---|---|
| 任务定义器 | 把大需求拆成原子任务 | 需求描述、规范文档 | 结构化任务清单 |
| 上下文装配器 | 为每个任务准备精准上下文 | 任务清单、代码库索引 | 上下文包 |
| 执行器 | 调用 AI 完成单个任务 | 上下文包 | 代码变更 |
| 校验器 | 自动验证变更是否符合预期 | 代码变更、校验规则 | 通过/失败报告 |
| 回炉器 | 失败任务自动重试或升级 | 失败报告 | 修正后的任务或人工介入请求 |
这个设计的精髓在于:AI 只在执行器里出现,其余四个模块全是确定性代码。这意味着整个流程的稳定性不依赖于 AI 的“心情”,而依赖于你写的工程逻辑。AI 发挥不稳定的时候,校验器会拦住它;AI 发挥超常的时候,校验器也会确认它的输出确实符合规范。
2.2 为什么要把任务拆得这么细
我一开始也想过偷懒,直接让 AI 处理“把 user 模块的所有接口迁移到新架构”这种大任务。实测下来问题很多:它会漏掉一些接口,会对某些接口做出超出预期的改动,会在中途“忘记”前面定好的规范。后来我把任务粒度降到“单个接口的迁移”,每个任务只涉及一个文件、一个函数、一组明确的输入输出,成功率立刻上了一个台阶。
这里有个经验值可以参考:单个任务的预期代码变更量控制在 50 到 200 行之间。低于 50 行,任务拆得太碎,调度开销占比过高;高于 200 行,AI 出错的概率显著上升,而且出错后定位问题的时间成本也会增加。
任务定义器的输出格式我建议用结构化的 YAML 或 JSON,而不是自然语言。比如:
task_id: migrate_user_get_profile type: interface_migration source_file: legacy/user_api.py target_file: new/user_api.py function_name: get_user_profile input_schema: user_id: string output_schema: name: string email: string created_at: timestamp constraints: - 保持原有错误码不变 - 使用新的日志组件 - 不允许引入新的外部依赖这种结构化描述的好处是,后续的上下文装配器和校验器都可以直接读取这些字段,不需要再去解析自然语言。
2.3 上下文装配:给 AI 的信息要“刚刚好”
上下文给少了,AI 会瞎猜;给多了,它会迷失在无关信息里。我的做法是分三层装配上下文:
- 第一层:任务本身的信息,就是上面那个结构化描述。
- 第二层:相关代码片段,包括源文件的相关部分、目标文件的现有结构、被依赖的公共模块的接口定义。
- 第三层:规范和示例,包括团队的代码规范摘要、一个已经完成的同类任务的正确示例。
第三层特别关键。我发现给 AI 一个“已经做对的样板”,比给它十条文字规范都管用。样板要选那种结构清晰、没有特殊情况的典型案例,让 AI 有样学样。
上下文装配器还需要做一个重要的事情:控制总 token 量。我的经验是单个任务的上下文控制在模型上下文窗口的 30% 到 50% 之间比较合适,留出足够的空间给 AI 生成输出。如果上下文超了,就要做优先级裁剪,优先保留任务描述和样板示例,代码片段可以只保留函数签名和关键逻辑。
2.4 校验器:整个框架的“守门人”
校验器是我花时间最多的模块,因为它直接决定了整个流程能不能自动跑起来。校验分几个层次:
- 语法层:代码能不能通过编译或解析,这个用现成的 linter 就行。
- 结构层:函数签名、参数类型、返回值结构是否符合任务描述里的 schema。
- 行为层:跑单元测试,看功能是否正确。
- 规范层:命名风格、注释格式、日志调用方式是否符合团队规范。
前两层是必须的,成本低、收益高。行为层需要你有现成的测试用例,如果原来没有,可以借这个机会让 AI 顺便把测试补上——但要注意,AI 写的测试不能直接用来验收 AI 写的代码,否则就是自己批改自己的作业。我的做法是行为层的测试用例由人工编写或审核,确保测试本身是可信的。
规范层可以用正则表达式或 AST 分析来做,比如检查所有日志调用是否都用了新的 logger 对象,检查所有函数是否都有类型注解。
2.5 回炉器:让失败变成资产
回炉器的逻辑是这样的:校验失败的任务,先自动重试一次,重试时把失败原因作为额外上下文喂给 AI。如果第二次还失败,就把任务标记为“需要人工介入”,同时把失败案例存入案例库。
这个案例库后面会变成上下文装配器的一个数据源。当新任务和某个历史失败案例相似时,装配器会主动把“这个坑别踩”的提示加进上下文里。这就实现了经验的自动沉淀——每一次失败都不会白费,它会变成后续任务的护栏。
3. 核心环节的实操细节与参数选择
3.1 任务定义器的实现要点
任务定义器我建议做成半自动的:先用脚本从代码库里提取出所有待迁移的接口清单,生成一个初始的任务列表,然后人工过一遍,补充每个任务的约束条件和验收标准。全自动生成任务列表容易漏掉隐含约束,比如某个接口有特殊的错误处理逻辑,或者某个接口被外部系统依赖不能改签名。
提取接口清单的脚本逻辑大概是:解析源文件,找到所有目标函数的定义,提取函数名、参数列表、返回类型、docstring。这部分用 AST 解析比正则表达式靠谱得多,Python 用ast模块,JavaScript 用@babel/parser,Java 用 JavaParser。
人工补充约束的时候,我习惯用一个检查清单来确保不遗漏:
- 这个接口有没有特殊的错误码?
- 有没有外部系统依赖它的签名?
- 有没有性能敏感的逻辑需要保留?
- 有没有需要同步更新的文档或配置?
3.2 上下文装配的 token 预算计算
假设你用的模型上下文窗口是 128K token,单个任务的预算可以这样分配:
| 内容类型 | 预算占比 | 说明 |
|---|---|---|
| 任务描述 | 5% | 结构化 YAML,通常很短 |
| 代码片段 | 40% | 源文件相关部分 + 目标文件结构 |
| 规范与示例 | 30% | 代码规范摘要 + 一个完整样板 |
| 失败案例提示 | 10% | 相似历史失败的避坑提示 |
| 预留输出空间 | 15% | 给 AI 生成代码留的余量 |
这个分配不是死的,如果某个任务特别复杂,代码片段占比可以上调,但预留输出空间不能低于 10%,否则 AI 生成的代码容易被截断。
3.3 执行器的调用策略
执行器调用 AI 的时候,有几个参数需要仔细调:
- 温度(temperature):代码生成任务建议设低一点,0.1 到 0.3 之间。太高了 AI 会“创意发挥”,写出一堆你没要求的抽象层;太低了又可能过于死板,遇到需要变通的地方卡住。
- 最大输出长度:根据任务预期代码量设置,一般留出预期量的 1.5 倍余量。
- 停止序列:如果 AI 的输出有固定格式(比如先输出代码再输出解释),可以设置停止序列让它在代码结束后就停,避免生成一堆无关的解释文字。
我实测下来,把“只输出代码,不要解释”写进系统提示里,配合低温度,输出的稳定性最好。如果确实需要 AI 解释它的思路,可以单独开一个“解释任务”,让它对已经生成的代码做说明,而不是在执行任务里混着做。
3.4 校验规则的编写技巧
校验规则我建议用“规则即代码”的方式管理,每条规则是一个独立的函数,输入是代码变更,输出是布尔值和失败原因。这样规则可以单独测试、单独启用禁用、单独统计触发率。
几条我常用的规则示例:
- 检查所有新增函数是否有类型注解(用 AST 遍历函数定义节点)。
- 检查所有日志调用是否使用了统一的 logger 对象(正则匹配
logging.或console.log等)。 - 检查是否有硬编码的配置值(匹配常见的 IP、端口、URL 模式)。
- 检查函数复杂度是否超标(用 cyclomatic complexity 工具)。
规则的误报率要控制住。如果一条规则经常误报,要么修正规则,要么把它降级为“警告”而不是“失败”。误报太多的规则会让整个校验环节失去可信度,最后大家都不看校验报告了。
3.5 回炉重试的提示词设计
重试时的提示词和首次执行不一样,需要把失败原因明确地告诉 AI。我的模板大概是:
你上次生成的代码没有通过校验,失败原因如下: [具体的失败信息] 请针对上述问题修正代码。注意: 1. 只修改与失败原因相关的部分,不要改动其他逻辑。 2. 修正后重新输出完整代码。 3. 如果你认为校验规则本身有问题,请在代码后用注释说明。最后一条很重要。有时候确实是校验规则太严了,AI 的写法是合理的。让 AI 有机会“申诉”,可以帮你发现规则里的问题。当然,申诉是否采纳还是由人工决定。
4. 完整实操流程:从零搭一个最小可用版本
4.1 环境准备与目录结构
先说一下最小可用版本需要什么。你不需要一上来就搞得很复杂,一个 Python 脚本加几个配置文件就能跑起来。目录结构建议这样:
harness/ tasks/ # 任务定义文件 context/ # 上下文装配的中间产物 outputs/ # AI 生成的代码 reports/ # 校验报告 rules/ # 校验规则 cases/ # 失败案例库 config.yaml # 全局配置 run.py # 主流程脚本config.yaml里放模型调用的配置、token 预算、重试次数等全局参数。任务定义文件放在tasks/下,一个任务一个文件,用任务 ID 命名。
4.2 第一步:写一个任务定义文件
拿一个具体的例子来说。假设你要把一个旧接口迁移到新架构,任务定义文件tasks/migrate_user_get_profile.yaml内容如下:
task_id: migrate_user_get_profile type: interface_migration source_file: legacy/user_api.py target_file: new/user_api.py function_name: get_user_profile input_schema: user_id: string output_schema: name: string email: string created_at: timestamp constraints: - 保持原有错误码不变 - 使用新的日志组件 logger - 不允许引入新的外部依赖 - 所有函数必须有类型注解 acceptance: - 函数签名与 output_schema 一致 - 通过单元测试 test_get_user_profile - 无硬编码配置值这个文件就是任务定义器的输出,也是后续所有环节的输入。
4.3 第二步:装配上下文
上下文装配脚本读取任务定义,然后从代码库里提取相关片段。提取逻辑是:先找到source_file里function_name对应的函数体,再找到target_file里同类函数的现有实现作为结构参考,最后从规范库里取一个同类任务的样板。
装配完成后,生成一个context/migrate_user_get_profile.md文件,内容大致是:
## 任务描述 [任务 YAML 的内容] ## 源函数实现 [从 legacy/user_api.py 提取的函数体] ## 目标文件现有结构 [从 new/user_api.py 提取的同类函数] ## 代码规范摘要 [规范库里的相关条目] ## 参考样板 [一个已完成的同类任务的正确实现]这个文件就是喂给 AI 的完整上下文。
4.4 第三步:调用 AI 执行
执行脚本读取上下文文件,拼上系统提示,调用模型 API。系统提示我一般这样写:
你是一个代码迁移助手。你的任务是根据提供的上下文,完成指定的代码迁移。 要求: 1. 只输出迁移后的完整函数代码,不要输出解释。 2. 严格遵守任务描述里的约束条件。 3. 参考样板的结构和风格。 4. 如果上下文信息不足以完成任务,输出 "INSUFFICIENT_CONTEXT" 并说明缺少什么。最后一条是防止 AI 在信息不足时瞎编。实测下来,明确告诉它可以“拒绝任务”,比让它硬着头皮编要安全得多。
4.5 第四步:跑校验
校验脚本按顺序跑四层校验,任何一层失败就记录失败原因并终止后续校验。校验报告写到reports/migrate_user_get_profile.json,格式如下:
{ "task_id": "migrate_user_get_profile", "status": "failed", "checks": [ {"layer": "syntax", "passed": true}, {"layer": "structure", "passed": true}, {"layer": "behavior", "passed": false, "reason": "test_get_user_profile 断言失败:created_at 格式不正确"}, {"layer": "convention", "passed": true} ] }4.6 第五步:回炉或通过
如果校验通过,把生成的代码写入target_file,任务标记完成。如果失败,进入回炉流程:把失败原因拼进上下文,重新调用 AI,最多重试两次。两次都失败就标记为“需要人工介入”,并把失败案例存入cases/。
整个流程跑下来,一个任务的端到端耗时大概在 30 秒到 2 分钟之间,取决于任务复杂度和模型响应速度。相比人工操作,单个任务的耗时可能差不多,但你可以同时跑几十个任务,而且不需要人盯着。
5. 常见问题与排查技巧实录
5.1 AI 输出格式不稳定怎么办
这是最常见的问题。明明说了“只输出代码”,它偏要在前面加一段“好的,我来帮你迁移这个函数”。解决办法有几个:
- 在系统提示里用更强的措辞,比如“禁止输出任何解释性文字,第一个字符必须是代码”。
- 在输出解析环节做容错,用正则提取代码块,忽略代码块之外的内容。
- 如果模型支持,用结构化输出功能,直接约束输出格式。
我一般三个方法一起用,实测下来输出格式的稳定性可以到 95% 以上。
5.2 校验规则误报太多怎么办
先统计每条规则的触发率和人工复核后的真实失败率。如果某条规则触发 100 次但只有 5 次是真失败,那这条规则要么修正要么降级。修正的方向通常是放宽匹配条件,比如原来要求“所有函数必须有 docstring”,可以改成“所有公开函数必须有 docstring”,把内部辅助函数排除掉。
5.3 任务拆分的粒度怎么把握
前面说了 50 到 200 行的经验值,但更本质的判断标准是:这个任务能不能用一句话说清楚它的验收标准。如果验收标准需要写三段话,说明任务太大了,继续拆。如果验收标准是“代码能跑就行”,说明任务太模糊了,需要补充约束。
5.4 上下文超了怎么办
优先裁剪代码片段,保留函数签名和关键逻辑,去掉大段的注释和无关的辅助函数。如果还是超,就把规范摘要压缩成要点列表,把样板示例换成更短的版本。实在不行就拆任务,把一个任务拆成两个更小的任务,每个任务需要的上下文自然就少了。
5.5 多个任务之间有依赖怎么办
比如任务 B 需要任务 A 的输出作为输入。这种情况我建议串行执行,A 完成并通过校验后,把 A 的输出加入 B 的上下文,再执行 B。不要试图让 AI 同时处理有依赖关系的任务,它理不清的。
5.6 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 |
|---|---|---|
| AI 输出为空 | 上下文超长被截断 | 检查 token 数,裁剪上下文 |
| 输出格式混乱 | 系统提示不够强 | 加强格式约束,加输出解析容错 |
| 校验大量误报 | 规则太严 | 统计触发率,放宽或降级规则 |
| 任务反复失败 | 任务粒度太大 | 拆分任务,降低单任务复杂度 |
| 生成代码风格不一致 | 样板示例不统一 | 统一样板风格,增加规范约束 |
| 重试后仍然失败 | 失败原因描述不清 | 细化失败信息,明确指出期望行为 |
5.7 几个我踩过的坑
第一个坑是过度依赖 AI 的自我解释。我一开始会让 AI 在生成代码后附上一段“我这样写的理由”,想借此判断它有没有理解任务。后来发现这段解释经常和实际代码对不上,AI 会“编造”一个听起来合理的理由,但代码本身并不符合。所以现在我只看代码和校验结果,不看 AI 的解释。
第二个坑是校验规则写得太细。我曾经写过一条规则检查“函数内变量命名必须符合驼峰式”,结果 AI 生成的代码里有一堆循环变量叫i、j,全部触发失败。后来我把这类规则改成只检查公开接口的命名,内部实现不管。
第三个坑是没有给 AI 留“拒绝”的出口。早期版本里,如果上下文信息不足,AI 会硬编一个实现出来,看起来像模像样但完全是错的。后来我在系统提示里加了“信息不足时输出 INSUFFICIENT_CONTEXT”,这种情况就少多了。让 AI 学会说“我不知道”,比让它假装知道要安全得多。
6. 框架的扩展方向与个人体会
6.1 从单机脚本到团队级服务
最小可用版本跑通之后,可以考虑把它服务化。比如把任务定义、上下文装配、执行、校验做成独立的微服务,用一个调度器来编排。这样多个开发者可以共享同一套规则库和案例库,经验沉淀的效率会更高。
另一个扩展方向是接入 CI。把 Harness 作为一个 CI 步骤,每次有新的迁移任务提交时自动触发。校验通过的代码自动合并,失败的代码留在分支上等人工处理。这样整个流程就完全融入现有的工程体系了。
6.2 案例库的持续运营
案例库是这套框架里最有长期价值的部分。每次人工介入解决的失败案例,都值得花几分钟整理成结构化的条目:失败现象、根本原因、正确做法、适用的任务类型。积累到几十条之后,上下文装配器就能主动预防大部分常见错误。
我自己的案例库现在有大概 80 多条记录,覆盖了接口迁移、测试补全、配置修改等几类任务。新任务进来时,装配器会根据任务类型和关键词匹配相关案例,把避坑提示加进上下文。实测下来,有案例提示的任务首次通过率比没有提示的高出大概 20 个百分点。
6.3 一点个人体会
这套东西搭起来之后,我最大的感受是:AI Coding 的瓶颈从来不在 AI 本身,而在我们怎么组织工作流程。同一个模型,放在对话框里用,和放在 Harness 里用,产出效率能差好几倍。差别不在于模型变聪明了,而在于我们给它创造了更好的工作条件——清晰的任务、精准的上下文、自动的校验、沉淀的经验。
另一个体会是,不要追求全自动。人工介入不是失败,而是流程的一部分。关键是让人工介入发生在最需要的地方,而不是每个环节都要人盯着。我的流程里,人工只做三件事:定义任务时补充约束、审核行为层测试用例、处理回炉两次仍失败的任务。其余环节全部自动。这样人的精力集中在最有价值的判断上,而不是消耗在重复劳动里。
最后分享一个小技巧:每次调整框架之后,拿一批历史任务做回归测试。看看新版本的通过率、平均重试次数、人工介入率有没有变化。没有度量就没有优化,这套框架本身也需要持续迭代。我一般每两周做一次回归,看看哪些规则需要调整、哪些提示词需要优化。这个过程本身也是在积累对 AI 行为的理解,挺有意思的。