Harness Engineering,给 Coding Agent 套上 “缰绳”,搞定千万 Token 级长程任务

张开发
2026/4/12 16:55:24 15 分钟阅读

分享文章

Harness Engineering,给 Coding Agent 套上 “缰绳”,搞定千万 Token 级长程任务
导读Coding Agent处理目标明确、规模可控的任务很成熟但面对上千文件的批量迁移、全量代码审查等长程任务时总会陷入上下文耗尽、中断无法恢复、规模放大后行为不可控等困境。本文从一线工程落地经验出发拆解长程任务的核心痛点提出任务拆解、并行执行、File As Progress状态持久化、多层重试等核心设计原则结合真实业务场景展示完整落地方案最终将这套编排经验沉淀为meta-skill让Agent自己生产长程任务的执行框架真正实现大规模任务的可靠落地。一、什么是Coding Agent的长程任务最近Harness这个词在AI Coding圈子里被频繁提起它的英文本意是缰绳用来控制马的方向让其沿着正确的路径前行。放到AI Agent场景里这个词的含义同样好理解现在的大模型能力已经足够强能轻松搞定单个文件的修改、简单函数的编写但要让它稳定处理大规模工程任务就需要一个“缰绳”也就是一套工具和框架让Agent在安全边界内被稳定约束、引导最终实现能力的复用和规模化落地。我们都有这样的体验Coding Agent处理目标明确、规模可控的任务时效率极高。比如修改一个函数的逻辑、给一段代码添加注释、修复单个文件的语法错误这些任务Agent能快速完成甚至不需要太多人工干预。但在实际工程化建设中我们总会遇到一类远比这复杂的任务它们不是“一蹴而就”就能搞定的比如把21个前端模块的JavaScript文件全部迁移到TypeScript涉及上百个组件、上千行代码对几十个业务模块做一轮全量Code Review不仅要找出问题还要批量修复产出的几十上百条审查意见把散落在项目各个角落的中文硬编码字符串全部提取成i18n资源确保多语言适配的一致性。这类任务有三个非常鲜明的共同特征也是它们区别于普通任务的核心一是规模大往往涉及成百上千个文件覆盖多个业务模块二是运行时间长单次会话根本跑不完需要跨越多个Agent会话才能完成三是Token消耗极高动辄几千万到上亿Token的量级远超单个模型会话的Token上限。这就是我们今天要重点探讨的“长程任务”。它不是什么新鲜概念只要我们用Agent处理较大规模的工程任务就一定会遇到。但很多人在落地时都会陷入“Agent能搞定小任务就一定能搞定大任务”的误区最终导致任务中断、结果不可靠、成本翻倍等问题。这篇文章就从实际落地经验出发往深处走一步聊聊长程任务的Harness Engineering也就是如何给Agent套上“缰绳”让它可靠地完成长程任务。二、长程任务落地我们真正要关注的3个核心点用Agent完成长程任务绝不能只看“最终能不能出结果”更要关注过程的可靠性和效率。结合我们几十次长程任务落地的经验核心要盯紧三个维度效果、速度、成本。这三个维度相互关联、相互制约也是后续所有设计原则的出发点。2.1 效果确保任务能落地、结果可验证效果是基础没有可靠的效果再快的速度、再低的成本也没有意义。我们在落地长程任务时最担心的就是“看似完成实则无效”具体会遇到三个问题首先是任务能否完整完成。Agent在执行大量子任务时很容易出现中断比如Token超限、网络异常、模型服务超时这些都不是偶然情况而是长程任务中的常态。如果没有对应的恢复机制任务就会停在半路之前消耗的Token和时间全部白费。其次是完成的真实性。Agent有时候会“自欺欺人”明明还有大量文件没处理却因为上下文耗尽等原因提前宣布“任务已完成”。如果不做核查很容易误以为任务落地完成等到线上出现问题才发现遗漏了大量工作。最后是结果的可验证性。长程任务的产出往往涉及几百上千个文件的变更靠人工逐一检查根本不现实效率极低且容易出错。这就需要有程序化的手段批量校验Agent产出结果的正确性确保每个修改都符合预期。2.2 速度用并发打破串行瓶颈长程任务的规模决定了它的执行时间不会短而速度直接影响工程落地的效率。举个简单的例子如果有1000个文件需要处理每个文件平均处理时间30秒要是串行处理总共需要8个多小时甚至超过一个工作日但如果能开启10路并发理论上1小时内就能完成效率提升8倍。很多人在使用Agent时会忽略并发的重要性依然采用“逐个处理”的方式导致长程任务拖了几天甚至几周才能完成严重影响项目进度。因此如何设计合理的并发机制打破串行瓶颈是提升长程任务速度的关键。2.3 成本避免无效Token浪费长程任务的Token消耗本身就很高一旦出现无效操作成本会直接翻倍。最常见的浪费场景有两种一是Agent一次没做对整个上下文的Token全部浪费一个任务反复重试3次成本就会翻3倍二是更隐蔽的“隐性浪费”Agent在长会话中逐渐偏离预期比如忘记之前约定的编码规范、重复犯已经纠正过的错误导致前面消耗的几十万Token全部白费最终还要人工重新处理。因此控制成本的核心就是减少无效操作避免Token的浪费让每一次Agent调用都能产生实际价值。效果、速度、成本这三者构成了长程任务的核心关注点。后续我们所有的设计原则和落地技巧都是围绕这三个目标展开的既要保证任务可靠完成又要提升效率、控制成本。三、长程任务的3个核心困难90%的人都会踩坑从长程任务的三个核心特征出发我们能推导出落地过程中会遇到的三个核心困难。这些困难不是Agent能力不足导致的而是模型本身的局限性和长程任务的规模特性共同决定的也是我们做Harness Engineering需要重点解决的问题。3.1 上下文耗尽Agent会“忘事”还会提前“摆烂”模型的上下文窗口是有限的这是所有大模型的共性问题哪怕是Claude Opus这样的顶尖模型也有明确的Token上限。而长程任务需要处理大量文件随着任务推进历史对话信息不断累积上下文会逐渐被填满。现在的Agent框架普遍带有上下文压缩能力当上下文接近窗口上限时会自动对历史对话做摘要压缩。但压缩必然会丢失信息每压缩一轮前面的细节就会模糊一层。随着任务推进、压缩不断叠加哪怕是顶尖模型对早期上下文的理解质量也会持续下降。我们在落地时就遇到过这样的情况Agent在处理第10个文件时和我们约定了统一的编码规范但处理到第50个文件时因为上下文被多次压缩它已经“忘了”这个约定开始按照自己的逻辑修改导致格式不一致。更麻烦的是Agent还会出现“上下文焦虑”——它能感知到上下文快到上限了就会提前收尾、草草了事明明还有大量文件没处理却自己宣布“任务完成”让人防不胜防。3.2 中断要重来没有记忆一次中断就前功尽弃网络断开、Token用尽、模型超时这些都不是异常情况而是长程任务中的常态。但目前的Agent没有跨会话记忆每次新对话开始它面对的都是一张白纸不知道之前做过什么、做到了哪里。如果没有任何恢复机制一次中断就意味着从头再来。比如我们曾经处理一个JS转TS的迁移任务已经处理了300多个文件耗时4个多小时结果因为网络中断Agent会话被迫终止再次启动时只能重新从第一个文件开始处理之前的工作全部白费。这种情况不仅浪费时间和Token还可能让任务陷入“永远无法达到终点”的尴尬境地——每次快完成时中断反复重来成本越来越高。3.3 规模放大后行为不可控单个文件做好不代表上千个也能做好很多人会有一个误区既然Agent能做好单个文件的处理那批量处理上千个文件只要循环调用Agent就可以了。但实际情况是规模放大后Agent的行为会变得不可控很多在单个文件上不会出现的问题都会集中爆发。比如格式不一致单个文件处理时Agent能严格按照约定的格式输出但处理上千个文件时可能会因为上下文波动、疲劳效应出现格式混乱的情况再比如文件处理失败个别文件因为代码复杂、依赖关系多Agent无法处理若没有错误隔离机制一个文件的失败会导致整个任务挂掉还有构建失败Agent修改后的代码可能存在语法错误、依赖缺失单个文件的错误不会影响整体但上千个文件的错误叠加会导致整个项目构建失败无法部署。如果一个文件的失败就导致整个任务挂掉那这套流程就不可能在生产环境中使用。因此如何控制规模放大后的风险实现错误隔离是长程任务落地的关键。四、4个核心原则破解长程任务的落地困境针对上面提到的三个核心困难结合我们的落地经验总结出了四个核心原则。这四个原则相互配合分别对应效果、速度、成本三个维度能从根本上破解长程任务的落地困境让Agent的行为变得可控、可靠。4.1 任务拆解解决上下文耗尽问题任务拆解的核心目标就是解决上下文耗尽的问题。我们不把所有事情塞进一个Agent会话而是把大任务拆成合理粒度的子任务每个子任务都是一个Agent能在单次会话内独立完成的工作单元。拆完之后Agent每次只需要关注有限的几个文件上下文里只有当前子任务需要的信息不会被无关内容干扰也不会出现上下文被填满、需要反复压缩的情况。比如把“21个前端模块JS转TS”这个大任务拆成21个“单个模块JS转TS”的子任务每个子任务只处理一个模块的文件Agent能专注于当前模块的迁移不会因为上下文累积而“忘事”。这里需要注意的是拆解不是“越细越好”而是要把握合理的粒度既要避免单个子任务过大导致上下文耗尽也要避免子任务过细导致调度开销过高这个我们后面会详细讲。4.2 并行执行解决速度瓶颈问题并行执行是解决长程任务速度瓶颈的核心手段。拆完的几十上百个子任务如果还是逐个串行处理速度不会有本质提升依然会耗时漫长。因此必须支持多个Agent同时跑不同的子任务通过并发提升整体吞吐量。比如我们把1000个文件的处理任务拆成100个子任务开启10路并发那么整体耗时就能从8小时缩短到1小时左右效率大幅提升。但并行执行不是“盲目开启多线程”而是需要有合理的调度机制控制并发数量避免资源耗尽同时处理子任务之间的依赖关系这个我们后面会详细拆解。4.3 可续传解决中断重来问题可续传的核心是“状态持久化”也就是把任务的进度持久化到会话之外的地方比如磁盘文件使得任何一次中断后新的会话都能接着上次的进度继续而不是从零开始。这就相当于给长程任务加了一个“存档”功能每次完成一个子任务就把进度“存档”到文件里一旦中断下次启动时Agent只需要读取这个“存档”就能知道哪些子任务已经完成、哪些还没开始、哪些失败了需要重试然后从断点继续执行避免前功尽弃。状态持久化的载体可以是TSV、JSON甚至纯文本形式不重要重要的是“一切状态都在文件里”不依赖Agent的记忆不依赖会话上下文这样才能实现真正的可续传。4.4 有完成条件解决行为不可控问题有完成条件就是给每个子任务设定明确的、可程序化检查的成功标准通过客观手段验证产出确实符合预期避免Agent“敷衍了事”同时实现错误隔离。比如一个“JS转TS”的子任务完成条件可以设定为“迁移后的TS文件能通过tsc编译、语法无错误、逻辑与原JS文件一致”一个“Code Review”的子任务完成条件可以设定为“审查意见按error/warn/style三级分类、无遗漏问题、修复后的代码能通过单元测试”。如果子任务的产出不符合完成条件就给Agent反馈错误信息让它在当前会话内继续修复同时设定修复的轮次和停止边界比如修复2-3次仍未通过就标记为失败不再继续尝试避免无效Token浪费。这样既能保证每个子任务的产出质量也能实现错误隔离一个子任务失败不会影响其他子任务的执行。总结一下任务拆解控制单次执行的复杂度解决上下文耗尽问题并行执行压缩整体耗时解决速度瓶颈可续传消除中断带来的沉没成本解决中断重来问题完成条件保障产出的可信度解决行为不可控问题。这四个原则相互配合覆盖了长程任务落地的核心痛点。五、落地理念让长程任务更可控、更可靠有了核心原则还需要一套落地理念指导我们在实际操作中规避风险、提升效率。结合我们的落地经验核心有三个理念任务边界清晰、错误在最小范围内解决、允许局部失败。这三个理念是对四个核心原则的补充能让长程任务的落地更顺畅、更可靠。5.1 任务边界清晰子任务独立避免连锁失败任务边界清晰的核心是让每个子任务都有明确的输入、输出和约束子任务之间不共享状态、不交叉引用。这样做的好处是每个子任务可以独立理解、独立执行、独立验证即使一个子任务失败也不会像多米诺骨牌一样影响其他子任务实现错误隔离。根据子任务之间关系的不同边界的实现方式分为三种我们结合实际场景逐一说明5.1.1 无依赖直接并行执行这是最简单的情况子任务之间没有文件级别的依赖各自处理各自的文件互不干扰。比如“提取项目中所有硬编码字符串到i18n资源”的任务每个文件的硬编码提取都是独立的不依赖其他文件的处理结果这种情况下子任务可以直接分组后并发执行不需要考虑顺序。5.1.2 有依赖按拓扑排序执行有些子任务之间存在顺序约束比如文件A依赖文件B的类型导出那么B必须先处理完A才能开始。最典型的就是JS转TS迁移任务被依赖的叶子文件比如工具函数文件必须先迁移依赖它的文件比如业务组件文件才能开始迁移否则会出现类型缺失、编译失败的问题。处理这种情况的方式是在分组前先做依赖分析按拓扑序排列子任务的优先级调度时按优先级批次执行。同一优先级内的子任务仍然可以并行但跨优先级必须串行确保依赖关系不被破坏。5.1.3 有冲突物理隔离延后解决有些子任务可能会修改同一个文件或互相影响的文件比如Code Review修复时两个子Agent可能同时修改同一个模块的公共配置文件导致冲突。这种情况下强行并行会导致代码覆盖、冲突无法解决影响任务进度。我们的解决方案是“物理隔离”让每个子任务在独立的Git Worktree中操作各自修改互不干扰。冲突被推迟到所有子任务执行完毕后在合并阶段集中处理。这样做的好处是所有子任务都能正常执行不会因为冲突而中断而合并阶段的冲突是静止的Agent面对的是一个确定的、不再变化的冲突集合更容易理解上下文解决冲突的效果更好。只有当隔离方案确实行不通比如子任务之间需要实时交换中间结果才考虑Agent Teams这样的网状协作结构。但网状结构会引入额外的通信开销和不确定性容易出现协作混乱因此是最后的选项。5.2 错误在最小范围内解决不把问题留给后续阶段“错误在最小范围内解决”是一个分层的概念核心原则是不将错误带到后续阶段不将错误带出子任务。这样能避免小错误放大成大问题减少无效Token浪费同时保证任务进度的顺畅推进。具体分为三个层面5.2.1 子任务内闭环问题在当前会话解决子任务在执行过程中如果发现生成的代码编译不通过、不符合完成条件应该在当前会话内尝试修复而不是把错误留给后续步骤。比如Agent生成的TS文件有语法错误就直接在当前会话中让Agent修改直到编译通过如果重试2-3轮仍然修不好就标记为失败回退环境明确告诉上层“这里搞不定了”不能让有问题的产出悄悄混进已完成的队列。5.2.2 阶段内收敛不带着问题进入下一阶段长程任务通常会分成几个阶段比如“环境准备→批量执行→收尾验证”。如果在批量执行阶段发现子任务失败应该在当前阶段内调整并重试而不是带着问题进入收尾验证阶段。举个例子如果批量执行阶段有10个子任务失败我们应该在这个阶段内重新调度这些子任务尝试修复如果修复失败就明确标记为失败不进入收尾验证阶段。如果带着失败的子任务进入收尾验证阶段下游任务会基于有问题的产出继续工作最后问题在验收时才暴露会浪费整条链路的工作和Token。因此阶段之间的交接必须是干净的进入下一个阶段时当前阶段的状态要么是“全部完成”要么是“部分完成、失败项已明确标记”。5.2.3 步骤间有校验保障及时发现问题不攒到最后每完成一个子任务就立刻进行校验不要攒到最后一次性验证。这样能及时发现问题避免问题积累同时减少无效Token浪费。校验分为两类各有侧重第一类是程序化校验能用脚本判定的绝不交给Agent。比如TS文件是否能编译通过、项目是否能成功构建、单元测试是否全部通过这些都有明确的对错标准用脚本自动检查即可。程序化校验的好处是零Token消耗、结果完全确定、可以无限次重复执行效率极高。第二类是Evaluator校验需要主观判断的用独立会话的Agent审核。比如Code Review意见的质量、代码修改的合理性这些需要主观判断的场景就用独立的Evaluator Agent去审核。这里有一个关键技巧做事的Agent和评价的Agent必须在不同的会话中因为在同一个会话内Agent的历史推理过程会形成“自我说服”效应让它倾向于认为自己之前的产出是正确的无法做出客观评价。打开一个全新的会话Agent面对的是干净的上下文只看到产出本身不受执行过程的干扰能做出更客观的评价。我们甚至可以用不同的模型来做Evaluator比如用Claude Sonnet执行Code Review任务再用GPT去审核意见的置信度将置信结果交回给Sonnet去修复。跨模型的评估能引入不同的“视角”进一步降低偏见提升评价的准确性。5.3 允许局部失败不因个别问题阻塞整体进度长程任务涉及成百上千个文件出现个别子任务失败是正常的我们不能因为5个文件处理失败就阻塞其他995个文件的进度。任务编排框架要能容忍局部失败已完成的部分照常合并产出失败的部分单独标记后续再处理。在实际实践中“失败”分为两种情况处理方式也不同第一种是确实搞不定回退给人工。如果子任务重试多次仍然无法通过校验或者产出的结果明显错误比如代码逻辑被篡改、无法修复的编译错误这种情况下就回退工作区、标记为失败留给人工处理。这是真正意义上的失败不能强行合入否则会影响整个项目的稳定性。第二种是能搞定但不完美接受有限的妥协。比如TS strict迁移中一个文件的绝大部分any类型都被正确消除了但有一两处复杂的泛型推断Agent实在处理不了留下了// ts-ignore或者少量any类型。这种情况下只要编译能通过、业务逻辑没被改坏就可以标记为“通过但有妥协”比如状态设为DONE_WITH_WARNINGS照常合入把遗留的问题记录下来作为后续人工优化的清单。如果把这种情况也当作硬失败来处理回退整个文件、反复重跑会浪费大量Token拖慢任务进度。因此允许局部失败、接受有限妥协是平衡效率和质量的关键。六、实战技巧从落地细节提升长程任务效率在落地了大量长程任务、沉淀了几十套Skill之后我们总结出了一些实用的实战技巧。这些技巧针对任务拆解、并发调度、状态管理等核心环节能帮助我们进一步提升长程任务的效率和可靠性避免踩坑。6.1 任务粒度把握“不粗不细”的黄金原则任务拆解的第一个问题就是拆到多细才合适粒度太粗单个子任务塞进去的文件太多上下文又会膨胀回到“上下文耗尽”的老问题粒度太细每个子任务只处理一个小文件调度开销和Prompt模板本身的Token消耗占比过高效率反而下降。合理的粒度取决于三个因素模型的上下文窗口大小、单个文件的平均规模、任务本身的推理复杂度。我们在实践中总结了一个经验公式以JS转TS迁移任务为例给大家参考模型使用Claude Sonnet有效上下文窗口大约200K Token。一个子任务的Token消耗由三部分组成Prompt模板任务说明、规则约束、输出格式要求大约1K Token输入文件内容代码文件大约每行10-20 Token3000行代码约30K-60K TokenAgent的工作过程读文件、推理、写代码、跑验证、修复错误这部分消耗通常是输入的2-3倍大约60K-180K Token。加起来大约90K-240K Token刚好接近但不超过模型的上下文窗口给多轮交互和可能的修复留有余量。这里的3000行不是一个写死的常数不同任务的推理密度不一样。比如当需要Agent深入理解代码意图、评估设计合理性时推理消耗远大于输入本身3000行就偏多了可能需要拆成2000行甚至1000行一个子任务而如果是简单的文本替换、注释添加任务推理消耗小就可以适当放大粒度减少调度开销。判断粒度是否合适有一个简单的检验标准跑几组样本看子任务的Token消耗是否经常逼近上下文窗口的80%。如果经常逼近说明粒度偏粗应该缩小如果只用到了30%-40%说明可以适当放大减少调度开销。还有一个容易忽略的点同目录的文件应该尽量放在同一组。不是因为它们行数加起来刚好合适而是因为同目录的文件往往共享import、类型定义、配置文件放在一起处理时Agent能看到完整的局部上下文做出的修改更准确减少因上下文缺失导致的错误。6.2 子任务CLI化与并发调度提升效率降低Token消耗我们在早期落地长程任务时曾经踩过一个坑子任务在主Agent的对话里嵌套调用导致上下文累积、Token消耗过高还出现了子任务理解偏差的问题。后来我们调整了设计子任务不再在Agent的对话里嵌套调用而是作为独立的CLI进程执行每个子任务是一次独立的Agent会话由外部脚本启动和管理。这个设计带来了四个重要好处6.2.1 保证Prompt的确定性每个子任务的Prompt由build-prompt.js脚本根据任务参数程序化组装包含明确的任务详情、规则约束、输入文件列表、输出格式要求、验证标准。所有子任务拿到的指令结构一致不会因为主Agent在长会话中的“自由发挥”导致子任务理解偏差。举个例子没有程序化构建Prompt时我们给主Agent的指令是“使用subAgent完成Code Review任务Prompt如下请审查以下文件按error/warn/style三级分类产出审查意见。待审查文件src/components/UserCard.tsx、src/components/UserList.tsx、src/components/UserDetail.tsx”。但主Agent并不会原样转发它“理解”任务后实际传给subAgent的Prompt会变成“你需要审查以下组件代码。重点关注边界情况和错误处理。以下是文件内容代码内容直接贴入请按error/warn/style三级分类产出审查意见。”对比可以发现主Agent的转述改变了内容把文件内容直接贴进Prompt让subAgent失去了渐进式发现代码结构的机会还添加了自己推断的“重点关注边界情况”万一推断有误subAgent的审查方向就会被带偏最终导致审查效果打折扣。而程序化构建Prompt能避免这种问题确保每个subAgent拿到的指令都是统一、准确的。6.2.2 大幅降低Token消耗一方面消除了上下文累积。每个CLI子任务是一个全新的Agent会话上下文里只有当前子任务需要的信息不会有前面子任务的对话历史堆积白白消耗Token。如果在主Agent的会话中串行调度到第30个子任务时前面29个子任务的对话历史全部堆积在上下文中Token消耗会呈指数级增长。另一方面减少了Prompt构建本身的消耗。在主会话中Agent需要花大量Token去“想”怎么写子任务的指令组织措辞、回忆约束条件、决定输出格式而build-prompt.js脚本生成Prompt是程序化的不需要消耗任何Token能节省大量成本。6.2.3 并发数量可控CLI进程可以由dispatch.js脚本自由控制并发数资源充裕时开10路并发资源紧张时降到3路甚至可以根据API限流情况动态调整。而在对话内让Agent自己调度并发效果很难保证——模型往往过于谨慎不愿意一次性开启几十、上百个并发子任务很难真正达到高并发提速的效果。6.2.4 可插入前后置脚本逻辑子任务执行前脚本可以做预处理比如创建Git Worktree、准备输入文件、检查前置条件执行后脚本可以做后处理比如校验产出、更新状态、清理临时文件。这些逻辑是确定性的不需要Agent参与既能提升效率也能减少Agent的无效操作。6.2.5 并发调度的具体设计我们的并发调度由两个脚本分阶段协作dispatch.js负责首批启动poll.js负责后续监控和补位配合主Agent的简单循环实现高效调度。dispatch.js只执行一次它读取任务清单为前N个任务N并发上限完成前置准备和任务分配比如创建Git Worktree、生成Prompt、启动子Agent进程剩余任务标记为pending等待补位。以Code Review修复为例每个任务启动前脚本先通过git worktree add创建隔离工作区然后通过内联的generateQuery函数根据任务参数文件路径、review意见列表、分支名程序化组装Prompt最后spawn子Agent进程在Worktree中执行。启动完成后dispatch.js将每个任务的状态running/pending/failed、PID、启动时间写回任务清单文件然后退出。poll.js由主Agent在循环中反复调用它是单次执行的主Agent每隔一段时间调用一次通过检查退出码决定是否继续。每次调用时poll.js做三件事第一检查所有running状态的任务。通过PID判断进程是否还活着如果进程已退出就去Worktree里找fix_result.json文件存在且合法就标记为success、备份结果不存在就标记为failed、从日志末尾截取错误信息。第二补位启动pending任务。统计当前running的任务数量如果低于并发上限就从pending队列中取出任务创建Worktree、生成Prompt、启动子Agent填满并发槽位。第三输出状态摘要并通过退出码传递信号。exit 0表示还有活跃任务pending或running主Agent应该sleep后继续调用poll.jsexit 2表示所有任务已到终态主Agent可以进入下一阶段比如合并结果。主Agent的调度循环非常简单用Shell脚本就能实现whiletrue;donodescripts/poll.js --task-list task_list.jsonif[$?-eq2];thenbreak;fisleep60done整个调度过程中Agent只负责“审查代码并给出意见”这一核心工作其余的Worktree管理、Prompt构建、进程监控、状态更新、补位启动全部由脚本完成既高效又可靠。另外我们还为dispatch接口设计了统一的参数–root项目目录、–concurrency并发数、–dry-run预览模式、–retry-failed重试失败任务这样所有长程任务Skill的调度方式都是一致的降低了维护成本。这套调度设计解决了两个关键问题一是调度策略随到随补不等齐再发。不用等当前批次的所有任务都完成才启动下一批而是哪个坑位空了就立刻补上新任务。因为子任务的执行时间不均匀一个只有3个小文件的目录可能20秒就审完了一个有复杂业务逻辑的目录可能要3分钟。如果按批次等齐再发9个任务30秒完成1个任务3分钟那9个坑位会空等2分半十几批下来累计浪费的时间相当可观。随到随补策略让每个坑位始终有任务在跑整体吞吐量最大化。二是输出设计对人和对Agent分两个通道。工程师需要的是简洁、可扫视的进度概览比如当前完成数量、失败数量、剩余时间而Agent恢复时需要的是结构化的、可程序化解析的完整状态数据。因此我们将简洁的进度概览输出到终端给人看将完整的状态数据写到文件里给Agent读避免互相干扰。比如终端输出如下[Progress] 45/120 DONE | 10 IN_PROGRESS | 3 FAILED | 62 TODO [Speed] avg 35s/task | elapsed 28min | ETA ~22min [Failed] group_12 (timeout), group_27 (compile error), group_33 (timeout)结构化的完整状态则写入task_list.json文件Agent恢复时直接读取该文件就能快速定位断点继续执行。6.3 File As Progress长程任务的“存档”核心File As Progress是长程任务编排中最核心的设计也是实现“可续传”的基础。它的核心思想是所有进度状态都持久化到文件系统不依赖Agent的记忆不依赖会话上下文只依赖磁盘上的文件。每完成一步操作立即写入文件不攒批因为Agent随时可能被中断。这意味着无论Agent在哪一步被打断下次启动时只需要读文件就能知道“上次跑到哪了”。新会话开始时Agent不需要任何历史上下文它只需要做一件事读任务清单文件看哪些子任务是已完成的、哪些还没开始、哪些失败了需要重试然后从断点继续执行。文件载体可以根据任务复杂度选择简单任务用TSV文件人可读、易维护复杂任务用JSON文件支持嵌套元数据能存储更详细的状态信息甚至可以用纯文本文件只要能清晰记录进度即可。形式不重要重要的是“一切状态都在文件里”这个约束。比如我们在JS转TS迁移任务中用migration-tasks.tsv文件记录每个文件的迁移状态包括文件路径、当前状态TODO/IN_PROGRESS/DONE/FAILED、完成时间、失败原因等信息。每次完成一个文件的迁移就立即更新该文件的状态一旦中断下次启动时Agent只要读取这个TSV文件就能知道哪些文件已经迁移完成哪些需要继续处理从而实现无缝续传。6.4 任务状态设计让续传更精准有了File As Progress还需要一套状态机来描述每个子任务的生命周期确保续传时能精准定位断点减少重复工作。最基础的状态机很简单TODO → IN_PROGRESS → DONE → FAILED → SKIPPED状态设计的核心理念是仅凭当前状态就能决定下一步做什么。恢复逻辑不需要知道“之前发生了什么”不需要回放历史只需要读到“当前状态是什么”就能推导出续传策略。这要求每个状态都是自描述的它本身就携带了“接下来该怎么办”的信息。比如看到状态是TODO就知道需要启动子任务看到状态是DONE就知道需要跳过看到状态是FAILED就知道需要重试或标记为人工处理看到状态是SKIPPED就知道不需要处理。根据任务的复杂度我们可以设计更精细的状态实现更精确的续传。比如一个有“分析→执行→校验”三步的子任务粗粒度状态只有TODO/DONE/FAILED中断在“执行”阶段时只能从头重来浪费时间和Token。如果细化为TODO → ANALYZING → ANALYZED → EXECUTING → EXECUTED → VERIFYING → DONE → FAILED那恢复时就能精准判断状态停在ANALYZED说明分析已经完成直接从执行阶段开始停在EXECUTED说明执行完了但没来得及校验直接跑校验。每多一个状态就多一个可以精确恢复的断点减少重复工作。但需要注意的是状态也不是越多越好。每个状态都需要对应一个持久化的检查点比如一个中间文件或一条状态记录维护成本不低。实际设计时建议在任务执行时间较长、或者某个步骤有明确的中间产物可以复用的地方增加状态。对于10秒就能跑完的子任务TODO/DONE/FAILED三个状态就足够了即使中断了重跑的成本也很低。这里有一个细节需要特别注意IN_PROGRESS状态的残留处理。如果Agent在执行某个任务的过程中被中断这条任务的状态会停留在IN_PROGRESS但实际上没有任何Agent在处理它。恢复时必须判断这个IN_PROGRESS到底是“真的在跑”还是“跑到一半挂了”否则会出现重复执行或遗漏任务的情况。判断的依据是产出物而非状态本身具体分三步第一步检查是否有预期的产出文件存在。每个子任务在开始时就应该明确“完成后会产出什么文件”比如TS迁移任务会产出.ts文件Code Review任务会产出.review.json文件。如果产出文件不存在大概率是任务中断了。第二步如果产出文件存在检查内容是否合法。不是简单的“文件非空”就行而是要做完整性校验TS文件能不能通过编译、JSON文件能不能解析、Review意见的格式是否符合schema。如果校验通过说明Agent其实做完了只是状态没来得及更新直接将状态更新为DONE即可。第三步如果产出文件不存在或者内容不合法说明这是半成品。这时需要清理工作区避免半成品影响后续任务。我们的做法是每个子任务在独立的Git Worktree中操作所有修改都发生在这个隔离的工作区里。判断半成品的方法是在Worktree中执行git status和git diff所有未提交的变更就是半成品。清理也很简单用git worktree remove丢掉整个Worktree目录工作区就回到了执行前的干净状态。如果没有使用Worktree而是在主分支上操作那就需要在任务开始前记录当前的commit hash半成品清理时用git checkout – 恢复。完成清理后把状态重置为TODO等待下次调度。6.5 多轮重试分层处理失败减少无效消耗子Agent失败是正常的超时、输出格式错误、生成的代码无法编译这些都会发生。我们不需要因为一次失败就放弃而是将失败进行分层不同层次对应不同的重试策略既减少无效Token消耗又提高任务成功率。重试分为三个层次从内到外成本逐渐升高我们根据失败原因选择对应的重试策略6.5.1 内层恢复会话实现“续传”子Agent因为网络异常、进程崩溃、compress错误等原因异常退出任务本身没有错只是执行过程被中断。这种情况下dispatch脚本会记录这次执行的conversationId检测到异常退出后恢复同一个会话说“继续”Agent就能从断点接着跑。比如一个子任务需要处理6个文件改完4个时进程崩溃恢复同一个会话后Agent直接从第5个文件继续处理不需要重新处理前面4个文件大幅降低了Token消耗和时间成本。6.5.2 中层带反馈重试针对性修复子Agent正常完成了但产出不满足校验条件比如生成的代码编译失败、Review意见格式不符合要求。这种情况下我们会开一个新的子Agent会话把错误信息作为上下文喂进去让Agent针对性修复。比如tsc --strict报了3个类型错误我们就把完整的报错信息带给新会话让Agent只修这3处错误不重新改造整组文件减少Token消耗。同时我们会限制重试次数一般为2-3次达到上限仍然失败就回退工作区、清理环境、标记为FAILED不再继续尝试——子任务层面的重试次数过多反而会浪费大量Token。6.5.3 外层主Agent重新调度权衡成本与收益当中层重试耗尽后可能会留下一批FAILED的文件。这时需要在主Agent层面决策要不要对这些文件重新dispatch给子Agent再来一轮重试重新dispatch意味着为这些文件重新分组、生成新的Prompt、启动新的子Agent相当于把它们当作全新的子任务再跑一遍成本较高。因此我们需要权衡成本和时间如果FAILED的文件只有两三个重新dispatch一轮的代价不高值得尝试如果FAILED了几十个可能说明任务规则本身有问题比如Prompt设计不合理、完成条件太严格盲目重跑只是浪费Token不如先排查原因调整规则后再重试。需要注意的是判断走哪层重试要看产出文件的状态不依赖解析Agent的文本输出。文件是否存在、内容是否合法是稳定的、可程序化检查的依据比Agent的文本输出更可靠。七、真实场景落地两个案例看懂完整方案前面我们讲了核心原则、落地理念和实战技巧都是比较抽象的内容。下面我们结合两个真实的落地场景具体展示这些内容如何组合成完整的执行方案让大家能更直观地理解也能直接参考落地。7.1 场景一全量Code Review与批量修复场景需求对21个前端模块做一轮全量代码审查按error/warn/style三级分类产出审查意见并批量修复所有error和warn级别的问题style级别的问题记录下来后续人工优化。我们按照前面讲的原则和技巧设计了完整的执行方案具体步骤如下7.1.1 任务拆解按模块分组每个模块内按目录归类文件单文件源码行数超过MAX_LINES默认500则独立成组。分组时同目录的文件放在一起因为它们往往有共享的import和类型依赖放在一起审查Agent能看到完整的局部上下文审查质量更高。MAX_LINES的选取逻辑的是每个子任务的Token消耗由两部分组成一是源文件正文平均1行≈14 tokens按50字符/行、3.5字符/token估算二是固定开销包括Prompt模板、规则、文件元信息、review输出及Agent中间步骤约8000 tokens。以500源码行为例单个子任务的总消耗约20000 tokens占Claude Sonnet200K窗口的10%为并发子任务的对话历史和输出留出充足余量。7.1.2 并行执行用dispatch.js脚本以CLI方式启动10路subAgent并发审查每个subAgent读取自己对应的inputs/{chunkId}-input.json文件包含待审查文件列表、审查规则审查完毕后直接将issue列表写入segments/{chunkId}.json文件。主Agent通过检查segment文件是否存在来判断子任务是否完成不需要实时监控subAgent的执行过程。7.1.3 校验保障审查意见的质量属于主观维度无法用脚本程序化校验因此我们引入了独立的Evaluator Agent进行校验。整个架构分为三个角色主Agent负责编排任务、调度subAgent和Evaluator AgentsubAgent负责执行审查和修复Evaluator Agent负责对审查意见做批判性评估确保意见的准确性和完整性。Evaluator的Prompt设计有一个技巧带有挑战性语气主动挑毛病而非寻找优点。比如在Prompt中明确要求“检查审查意见是否有遗漏的error/warn级问题、分类是否准确、修复建议是否可行若有问题列出具体错误并给出修正方案”这样能让Evaluator更客观避免“自我说服”效应。7.1.4 可续传与状态管理审查进度写入review-progress.tsv文件记录每个子任务的chunkId、状态、完成时间、失败原因、Evaluator评估结果等信息。中断后恢复时主Agent读取该TSV文件定位到当前阶段跳过已完成的模块继续处理未完成的子任务对于FAILED的子任务按多轮重试策略进行重试。7.1.5 落地效果整个任务涉及21个模块、860个文件总Token消耗约8000万开启10路并发后总耗时约4小时比串行处理节省了32小时审查意见准确率达92%修复成功率达88%只有12个文件因代码复杂修复后仍有错误标记为FAILED留给人工处理整体效率和质量都远超预期。7.2 场景二JS to TS全量迁移场景需求将整个前端项目的JavaScript/JSX文件批量迁移到TypeScript/TSX要求迁移后的文件能通过tsc --strict编译逻辑与原JS文件一致不影响现有业务功能。这个场景的核心难点是文件间存在依赖关系且需要严格的程序化校验我们的执行方案如下7.2.1 任务拆解与排序按同目录归组累计行数不超过3000行单文件超过上限时独立成组。同时因为文件间存在依赖关系被依赖的叶子文件必须先迁移完成依赖它的文件才能开始因此分组后我们还需要通过脚本做依赖分析按拓扑序排列子任务的优先级调度时按优先级批次执行同一优先级内的子任务并行执行跨优先级串行执行。7.2.2 完成条件与校验这个场景可以完全用程序化校验每个子任务的完成条件设定为迁移后的TS/TSX文件无语法错误、能通过tsc --strict编译、与原JS/JSX文件的业务逻辑完全一致、不引入新的运行时错误。为了确保这四个条件同时满足我们设计了“三层校验”机制从基础语法到业务逻辑层层把关避免有问题的迁移文件流入后续环节。7.2.2.1 第一层校验语法与编译校验脚本自动化这是最基础也是最关键的一步核心目的是确保迁移后的文件符合TypeScript语法规范能正常编译。我们通过编写自动化脚本在每个子任务执行完毕后立即触发校验具体操作如下语法校验调用tsc命令对迁移后的文件进行编译关闭类型检查–noEmit --skipLibCheck仅校验语法正确性。如果出现语法错误如括号不匹配、关键字误用、导入导出格式错误等脚本会捕获完整的报错信息包括错误行号、错误描述直接反馈给子Agent让其在当前会话内针对性修复。严格模式校验语法通过后开启tsc --strict模式进行全量类型检查重点校验类型定义缺失、any类型滥用、空值判断缺失等问题。这里我们设置了“柔性约束”对于复杂的泛型推断、第三方依赖类型缺失等Agent难以处理的场景允许保留少量// ts-ignore注释每个文件不超过3处但必须在迁移日志中明确标注后续由人工优化对于基础的类型错误如变量类型不匹配、函数返回值类型错误则要求Agent必须修复否则子任务判定为失败。依赖一致性校验脚本对比迁移前后的package.json依赖确保迁移过程中没有误删、误加依赖同时检查迁移后的文件导入路径避免因文件名变更.js→.ts导致的导入失败。比如原JS文件导入“./utils.js”迁移后需自动改为“./utils.ts”若Agent遗漏修改脚本会自动捕获并提示修复。这一层校验全程由脚本完成零Token消耗且执行速度极快单个子任务的校验时间不超过5秒既能快速发现基础错误也能避免Agent因“疏忽”导致的低级问题。7.2.2.2 第二层校验逻辑一致性校验自动化Evaluator辅助JS转TS的核心要求是“逻辑不变”不能因为类型迁移导致业务功能异常。由于逻辑一致性无法完全通过脚本判定脚本只能检查语法和编译无法理解业务逻辑我们采用“自动化对比Evaluator Agent审核”的组合方式自动化对比脚本通过AST抽象语法树分析对比迁移前后文件的函数结构、条件判断、循环逻辑、变量定义等核心节点检查是否存在逻辑变更如误删代码、修改条件判断、新增/删除函数等。如果发现逻辑不一致脚本会标记出具体的差异位置反馈给子Agent要求其还原逻辑并重新迁移。Evaluator Agent审核对于AST对比无法判定的场景如复杂的业务逻辑嵌套、回调函数逻辑、状态管理相关代码由独立会话的Evaluator Agent进行审核。Evaluator会同时读取原JS文件和迁移后的TS文件逐行对比逻辑重点检查变量作用域是否变更、函数参数是否修改、异步逻辑是否正常、异常处理是否完整等。如果发现逻辑偏差会给出具体的修复建议由子Agent二次修复。这里有一个关键优化Evaluator Agent的Prompt会明确要求“只关注逻辑一致性不纠结于类型细节”避免其过度关注类型定义的优化而忽略了核心的逻辑问题。同时我们会将原JS文件的执行结果如关键函数的返回值、测试用例输出作为参考让Evaluator能更精准地判断逻辑是否一致。7.2.2.3 第三层校验运行时校验单元测试冒烟测试即使通过了编译和逻辑对比也可能存在运行时错误如类型断言错误、空值引用、依赖兼容问题因此我们在子任务完成后增加了运行时校验环节单元测试执行该模块对应的单元测试用例如Jest测试检查迁移后的文件是否能正常通过所有测试用例。如果测试失败脚本会捕获失败用例的详细信息包括错误堆栈、失败原因反馈给子Agent让其定位问题并修复。比如迁移后的TS文件因类型定义错误导致单元测试中函数调用报错Agent需根据测试反馈修正类型定义。冒烟测试对于没有单元测试的模块我们编写简单的冒烟测试脚本模拟实际业务场景调用模块的核心函数检查是否能正常运行、返回预期结果。比如对于工具函数模块冒烟测试会调用核心工具函数传入不同的参数验证返回值是否与原JS文件一致对于组件模块会渲染组件检查是否能正常渲染、无报错。只有通过这三层校验子任务才算真正完成。如果任意一层校验失败子Agent会根据反馈信息进行修复最多重试2次若重试后仍未通过则标记为FAILED回退工作区留给人工处理。7.2.3 并行执行与依赖调度由于文件间存在依赖关系如工具函数文件被业务组件文件依赖我们无法直接对所有子任务进行并行调度而是采用“拓扑排序批次并行”的方式依赖分析通过脚本解析所有JS/JSX文件的导入关系生成依赖图谱然后按拓扑序对所有子任务进行排序将无依赖的叶子节点如基础工具函数、公共常量文件归为第一优先级依赖叶子节点的文件归为第二优先级以此类推。批次调度按优先级分批执行子任务同一优先级内的子任务无依赖关系可开启10路并发执行上一批次的子任务全部完成后再执行下一批次。比如先执行所有工具函数模块的迁移再执行依赖这些工具函数的业务组件模块最后执行依赖业务组件的页面模块。依赖变更处理迁移过程中若某个子任务的迁移导致依赖它的子任务出现类型错误如工具函数的返回值类型变更我们会在该子任务完成后自动触发依赖它的下一批次子任务的重新校验确保依赖变更不会导致后续迁移失败。7.2.4 可续传与状态管理结合File As Progress理念我们用migration-progress.json文件记录每个子任务的详细状态包括子任务ID、所属优先级、文件列表、迁移状态TODO/IN_PROGRESS/ANALYZED/EXECUTED/VERIFIED/DONE/FAILED、完成时间、失败原因、重试次数、保留的// ts-ignore标注位置等信息。中断恢复时主Agent读取该JSON文件执行以下操作处理IN_PROGRESS状态检查该子任务的工作区通过git diff查看是否有未提交的半成品若有则清理工作区将状态重置为TODO若已生成迁移文件且能通过基础语法校验则将状态更新为EXECUTED直接进入校验阶段避免重复迁移。处理FAILED状态根据失败原因判断是否需要重试若为网络中断、进程崩溃等执行层面的错误自动重试1次若为Agent无法修复的逻辑错误、类型错误则标记为人工处理不参与后续调度。恢复调度按拓扑序恢复批次执行跳过已完成DONE的子任务优先执行上一批次未完成的子任务确保依赖关系不被破坏。

更多文章