2026年4月10日 周五晚上19:30,来了解“从个人单点提效,到构建企业AI生产力”(限30人)
免费POC, 零成本试错
AI知识库

53AI知识库

学习大模型的前沿技术与行业应用场景


我要投稿

Harness Engineering: 让 Coding Agent 可靠完成长程任务

发布日期:2026-04-08 18:11:26 浏览次数: 1523
作者:百度Geek说

微信搜一搜,关注“百度Geek说”

推荐语

如何让AI Agent可靠完成上千文件的大规模迁移任务?本文揭秘任务拆解、并行执行、状态持久化等关键技术。

核心内容:
1. 长程任务的三大特征:规模大、耗时长、Token消耗高
2. 解决长程任务的四大核心设计:任务拆解、并行执行、状态持久化、多层重试
3. 将解决方案沉淀为meta-skill,让Agent自主构建执行框架

杨芳贤
53AI创始人/腾讯云(TVP)最具价值专家
图片

点击蓝字,关注我们

图片

作者 | 无糖可乐

导读 
introduction
Coding Agent 处理目标明确、规模可控的任务很成熟,但面对上千文件的批量迁移任务,会遇到上下文耗尽、中断无法恢复、规模放大后行为不可控等问题。本文从实际落地经验出发,提出任务拆解、并行执行、File As Progress 状态持久化、多层重试等核心设计,并结合真实场景展示完整方案。最终将这套编排经验沉淀为 meta-skill,让 Agent 自己生产长程任务的执行框架。

全文 11157 字,预计阅读时间 16 分钟
GEEK TALK

01

长程任务的特征


最近 Harness 这个词在 AI Coding 圈子里被频繁提起,Harness 的英文本意是缰绳,能让马往对的方向跑。放到AI Agent场景,就是模型能力很强,但需要一个工具使其能够在安全边界内被稳定地约束、引导和复用。

Coding Agent 已经能很好地处理目标明确、规模可控的任务了。但在做工程化建设的时候,有一类任务远比这复杂。比如:把 21 个前端模块的 JS 文件全部迁移到 TypeScript。对几十个模块做一轮全量 Code Review,再批量修复产出的几十上百条意见。把散落在代码各处的中文硬编码全部提取成 i18n 资源。

这类任务有三个共同特征:规模大,涉及成百上千个文件;运行时间长,一次跑不完,可能需要跨越多个会话;消耗 Token 极高,动辄几千万到上亿 Token的量级。

这就是我们说的"长程任务"。这不是什么新概念,我们在使用Agent完成较大规模的任务是很常见的。这篇文章往深处走一步,聊聊长程任务的 Harness Engineering。

GEEK TALK

02

关注的点


使用Agent完成任务,我们要关注下面的点:

效果。

任务能否完成:Agent在执行大量任务的时候可能在执行过程中中断,原因可能是Token超限、网络异常、服务中断等原因,任务停在半路。

完成的真实性:Agent 有时候并没有处理完所有文件,但它会告诉你"任务已完成",核查后才能发现。

中断后的连续性:断了之后能不能接上继续执行,接上之后的任务的执行质量会不会变差?

结果的可验证性:如何判断Agent执行的结果是正确的,当产出涉及几百上千个文件的变更,靠人工很难看过来,需要有程序化的手段去批量校验正确性。

速度。 

1000 个文件逐个串行处理,即使每个文件只要 30 秒,也需要 8 个多小时。如果能 10 路并发,可能在 1 小时内搞定。

成本。 

Agent 一次没做对,整个上下文的 Token 就浪费了。一个任务反复重试 3 次,成本直接翻 3 倍。更隐蔽的浪费是:Agent 在一个长会话里在长会话中逐渐偏离预期,前面消耗的几十万 Token 全部白费。

效果、速度、成本,这三者构成了长程任务的核心关注点。接下来所有的设计,都是围绕这三个目标展开的。

GEEK TALK

03

困难在哪里


从长程任务的特征出发,能推导出有三个核心困难点:

上下文耗尽。  模型的上下文窗口是有限的。当处理的文件越来越多,历史信息不断累积,上下文逐渐被填满。现在的 Agent 框架普遍带有上下文压缩能力,当上下文接近窗口上限时,自动对历史对话做摘要压缩。但压缩一定会丢失信息,每压缩一轮,前面的细节就模糊一层。随着任务推进、压缩不断叠加,即便是 Opus 这样的顶尖模型,对早期上下文的理解质量也会持续下降。你会看到 Agent 在第 50 个文件时"忘了"第 10 个文件建立的约定,或者重复犯前面已经纠正过的错误。更麻烦的是,模型在长上下文中还会出现"上下文焦虑":它感知到上下文快到上限了,就开始提前收尾、草草了事。Agent 明明还有文件没处理,却自己宣布"任务完成"。

中断要重来。 网络断开、Token 用尽、模型超时,这些不是异常,是常态。而 Agent 没有跨会话记忆。每次新对话开始,它面对的是一张白纸。如果没有任何恢复机制,中断就意味着从头再来,这不仅浪费已完成的工作,也拖慢了最终完成的速度,甚至可能进入“永远无法达到终点”的尴尬境地。

规模大了行为不可控。 单个文件做得好,不代表一千个文件都做得好。规模放大后,个别文件处理失败、输出格式不一致、生成的代码破坏构建,这些都会发生。如果一个文件的失败导致整个任务挂掉,那这个流程就不可能在生产环境中使用。

GEEK TALK

04

核心原则


要解决这些困难,需要下面四个原则。

任务拆解。 对应上下文耗尽的问题。不把所有事情塞进一个会话,而是把大任务拆成合理粒度的子任务,每个子任务是一个 Agent 能在单次会话内独立完成的工作单元。拆完之后,Agent 每次只需要关注有限的几个文件,上下文里只有当前任务需要的信息,不会被无关内容干扰。

并行执行。 对应速度的问题。拆完的几十上百个子任务,如果还是逐个执行,速度没有本质提升。必须支持多个 Agent 同时跑不同的子任务。

可续传。 对应中断要重来的问题。任务的进度必须持久化到会话之外的地方,使得任何一次中断后,新的会话都能接着上次的进度继续,而不是从零开始。

有完成条件。 对应行为不可控的问题。每个子任务必须有明确的、可程序化检查的成功标准。通过客观手段验证产出确实符合预期,如果不符合预期,给Agent失败的原因在当前的session继续修复,设定修复的轮次以及明确的停止边界,比如修复完成或者触发边界(比如Retry触发上限),标记FAILED。

任务拆解控制单次执行的复杂度,并行执行压缩整体耗时,可续传消除中断带来的沉没成本,完成条件保障产出的可信度。这些原则分别作用于效果、速度、成本三个维度。

GEEK TALK

05

理念


任务边界清晰。 每个子任务有明确的输入(处理哪些文件)、输出(产出什么、写到哪里)、约束(哪些操作绝对禁止)。子任务之间不共享状态、不交叉引用。这样做的好处是每个子任务可以独立理解、独立执行、独立验证。如果子任务之间有隐式依赖,一个任务的失败就会像多米诺骨牌一样影响其他任务。

根据任务间关系的不同,边界的实现方式分三种:

  • 无依赖,直接并行。 最简单的情况。子任务之间没有文件级别的依赖,各自处理各自的文件,互不干扰。比如做i18n 对每个文件的中文硬编码独立提取,不影响其他文件。这种情况下子任务之间不共享状态、不交叉引用,分组后直接并发执行。

  • 有依赖,拓扑排序。 子任务之间存在顺序约束,文件 A 依赖文件 B 的类型导出,B 必须先处理完,A 才能开始。JS to TS 迁移就是这种情况:被依赖的叶子文件先迁移,依赖它的文件后迁移。处理方式是在分组前先做依赖分析、按拓扑序排优先级,dispatch 时按优先级批次执行。同一优先级内的子任务仍然可以并行,但跨优先级必须串行。

  • 有冲突,物理隔离。 多个子任务可能修改同一个文件或互相影响的文件,比如 Code Review 修复时,两个 subAgent 可能同时改到同一个模块的公共配置文件。这种情况下让每个子任务在独立的 Git Worktree 中操作,各自修改互不干扰。冲突被推迟到合并阶段处理。当发生冲突时,直接使用脚本几乎不可能直接解决,代码层面的冲突往往需要理解上下文才能决定保留哪边,最终需要引入 Agent 来解决冲突。但延后处理的好处在于:所有子任务都已经执行完毕,工作区处于静止状态,Agent 面对的是一个确定的、不再变化的冲突集合,而不是在多个 Agent 同时修改的竞态中实时协调。在静止状态上解冲突,效果会好很多。只有当隔离方案确实行不通(比如子任务之间需要实时交换中间结果),才考虑 Agent Teams 这样的网状协作结构,但网状结构引入的通信开销和不确定性是显著的,因此是最后的选项。

△ 不同任务边界的实现


错误在最小范围内解决。 "最小范围"是一个分层的概念。核心原则是:不将错误带到后续阶段,不将错误带出子任务。

  • 子任务内闭环。 子任务在执行过程中发现生成的代码编译不通过,应该在当前会话内尝试修复,而不是把编译错误留给后续步骤去处理。如果重试几轮仍然修不好,标记 FAILED、revert 环境,明确告诉上层"这里搞不定了"。不能让一个有问题的产出悄悄混进已完成的队列。

  • 阶段内收敛。 长程任务通常会分成几个阶段(比如"环境准备→批量执行→收尾验证")。如果子Agent的执行阶段发现了结果失败,应该在当前阶段内调整并重试,而不是带着问题进入收尾验证阶段再去补救。这样做的原因是:一旦子任务产出了一个有问题的文件,没被及时发现,下游任务基于这个文件继续工作,最后问题在验收时才暴露,导致浪费了整条链路的工作。所以阶段之间的交接必须是干净的:进入下一个阶段时,当前阶段的状态要么是"全部完成",要么是"部分完成、失败项已明确标记"。

步骤间有校验保障。 每完成一个子任务就立刻校验,不要攒到最后一次性验证。校验分两类:

  • 程序化校验:能用脚本判定的,绝不交给 Agent。对于能用程序化验证的场景(比如 TypeScript 编译通过、成功完成构建、单元测试全部通过),这些都有明确的对错标准,用脚本自动检查,程序化校验的好处是零 Token 消耗、结果完全确定、可以无限次重复执行。

  • Evaluator 校验:需要主观判断的,用独立会话的 Agent 审核。对于需要主观判断的场景(比如 Code Review 意见的质量),用独立的 Evaluator Agent 去审核。其中要注意的点:做事的 Agent 和评价的 Agent 必须在不同的会话进行,这是因为在同一个会话内,Agent 的历史推理过程会形成一种"自我说服"效应,影响后续的判断,让 Agent 倾向于认为自己之前的产出是正确的。打开一个全新的会话,Agent 面对的是干净的上下文,只看到产出本身,不受执行过程的干扰,得到客观的评价。甚至可以用不同的模型来做 Evaluator,比如用 Sonnet 模型进行 Code Review 的任务,再用 GPT 去做意见置信度判断(Grader),将置信结果交回给 Sonnet 去修复。跨模型的评估能引入不同的"视角",进一步降低偏见。

允许局部失败。 1000 个文件中有 5 个处理失败,不应该阻塞其他 995 个。任务编排框架要能容忍局部失败,已完成的部分照常合并产出。“失败”在实际实践中分为两种情况:

  • 确实搞不定,回退给人工。 子任务重试多次仍然无法通过校验,或者产出的结果明显错误。这种情况下 revert 工作区、标记 FAILED,留给人工处理。这是真正意义上的失败,不能强行合入。

  • 能搞定但不完美,接受有限的妥协。 比如 TS strict 迁移中,一个文件的绝大部分 any 都被正确消除了,但有一两处复杂的泛型推断 Agent 实在处理不了,留下了 // @ts-ignore 或者少量 any。编译能通过,业务逻辑没被改坏,只是没有达到 100% strict 的理想状态。这种情况可以标记为"通过但有妥协"(比如状态设为 DONE_WITH_WARNINGS),照常合入,把遗留的 any 记录下来作为后续人工优化的清单。如果把这种情况也当作硬失败来处理,revert 整个文件、标记 FAILED,导致大量文件在"99% 搞定"的状态下被反复重跑,浪费 Token。

GEEK TALK

06

技巧


在建设了大量的Long Term Task Skill实践后,总结出下面的技巧:

6.1 任务粒度

拆解的第一个问题是:拆到多细?

粒度太粗,单个子任务塞进去的文件太多,上下文又开始膨胀,回到了老问题。粒度太细,每个子任务只处理一个小文件,调度开销和 Prompt 模板本身的 Token 消耗占比过高,效率反而下降。

合理的粒度取决于三个因素:模型的上下文窗口大小、单个文件的平均规模、任务本身的推理复杂度。

我们在实践中用了一个经验公式来估算。以 JS to TS 迁移任务为例:

模型使用 Claude Sonnet,有效上下文窗口大约 200K Token。一个子任务的 Token 消耗由几部分组成:Prompt 模板(任务说明、规则约束、输出格式要求)大约 1K Token;输入文件内容,代码文件大约每行 10-20 Token,3000 行代码约 30K-60K Token;Agent 的工作过程(读文件、推理、写代码、跑验证、修复错误),这部分消耗通常是输入的 2-3 倍,因为 Agent 不是一次性处理完的,它会多轮读写文件、执行命令、检查结果,每一轮都会累积上下文,大约 60K-180K Token。加起来大约 90K-240K Token。所以 3000 行是一个让单次子任务能在上下文窗口内完成的经验上限,给多轮交互和可能的修复留有余量。但 3000 行不是一个写死的常数。不同任务的推理密度不一样:比如当需要 Agent 深入理解代码意图、评估设计合理性,推理消耗远大于输入本身,3000 行可能就偏多了。

判断粒度是否合适,有一个简单的检验标准:跑几组样本,看子任务的 Token 消耗是否经常逼近上下文窗口的 80%。如果经常逼近,说明粒度偏粗,应该缩小;如果只用到了 30%-40%,说明可以适当放大,减少调度开销。

还有一个容易忽略的点:同目录的文件应该尽量放在同一组。不是因为它们行数加起来刚好合适,而是因为同目录的文件往往共享 import、类型定义、配置文件。放在一起处理时 Agent 能看到完整的局部上下文,做出的修改更准确。

6.2 子任务的 CLI 化与并发调度

子任务不在 Agent 的对话里嵌套调用,而是作为独立的 CLI 进程执行。每个子任务是一次独立的 Agent 会话,由外部脚本启动和管理。这个设计选择带来了几个重要的好处:

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 变成了这样:

你需要审查以下组件代码。重点关注边界情况和错误处理。以下是文件内容:// === src/components/UserCard.tsx ===import React from 'react';... (200行代码被直接贴入)// === src/components/UserList.tsx ===... (150行代码被直接贴入)// === src/components/UserDetail.tsx ===... (300行代码被直接贴入)请按 error/warn/style 三级分类产出审查意见。

对比发现经过主Agent的转述内容变了,把文件内容全部贴进了 Prompt 而不是让 subagent 自己去读文件,subagent 失去了渐进式发现代码结构的机会,审查变成了"看一坨代码然后给意见"而非"逐个文件深入理解再评价";还塞了属于主 Agent 自己推断的上下文,万一推断有误,subAgent 的审查方向就被带偏了。最终导致审查效果打折扣。

Token 消耗大幅降低。消除上下文累积,每个 CLI 子任务是一个全新的 Agent 会话,上下文里只有当前子任务需要的信息。如果在主 Agent 的会话中串行调度,到第 30 个子任务时,前面 29 个子任务的对话历史全部堆积在上下文中,白白消耗 Token,还会干扰 Agent 的注意力。 同时也可以减少Prompt 构建本身的消耗,在主会话中,Agent 需要花 Token 去"想"怎么写子任务的指令,组织措辞、回忆约束条件、决定输出格式。build-prompt.js 脚本生成 Prompt 是程序化的省去这部分的Token。

并发数量可控。 CLI 进程可以由 dispatch.js 脚本自由控制并发数。资源充裕时开 10 路并发,资源紧张时降到 3 路,甚至可以根据 API 限流情况动态调整。在对话内让 Agent 自己调度并发,效果很难保证,模型往往过于谨慎,不愿意一次性开启几十、上百个并发子任务,很难真正达到高并发提速的效果。

可以通过脚本在前后插入逻辑。 子任务执行前,脚本可以做预处理(创建 Git Worktree、准备输入文件、检查前置条件);执行后,脚本可以做后处理(校验产出、更新状态、清理临时文件)。这些逻辑是确定性的,不需要 Agent 参与。

下面讲讲并发调度的具体设计。

dispatch 由两个脚本分阶段协作:dispatch.js 负责首批启动,poll.js 负责后续监控和补位。

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.js;exit 2 表示所有任务已到终态,主 Agent 可以进入下一阶段(比如合并结果)。

主 Agent 的调度循环非常简单:

while truedo    node scripts/poll.js --task-list task_list.json    if [ $? -eq 2 ]; then breakfi    sleep 60done

整个调度过程中,Agent 只负责"审查代码并给出意见"这一步,其余的 Worktree 管理、Prompt 构建、进程监控、状态更新、补位启动全部由脚本完成。

△ dispatch调度架构图


为 dispatch 接口的参数进行统一设计:--root(项目目录)、--concurrency(并发数)、--dry-run(预览模式)、--retry-failed(重试失败任务),这样所有长程任务 Skill 的调度方式都是一致的。

这套设计解决了几个问题。

调度策略:随到随补,不等齐再发。 不用等当前批次的所有任务都完成才启动下一批,而是哪个坑位空了就立刻补上新任务。因为子任务的执行时间不均匀——一个只有 3 个小文件的目录可能 20 秒就审完了,一个有复杂业务逻辑的目录可能要 3 分钟。如果按批次等齐再发,假设一批 10 个任务中 9 个在 30 秒内完成、1 个要 3 分钟,那 9 个坑位会空等 2 分半,十几批下来累计浪费的时间相当可观。随到随补策略让每个坑位始终有任务在跑,整体吞吐量最大化。

输出设计:对人和对 Agent 分两个通道。 设想一个具体的场景:你启动了 120 个 Code Review 子任务,10 路并发,预计 40 分钟跑完。你在终端前盯着输出,想知道"现在跑到哪了、有没有异常、还要多久"。半小时后进程意外中断了,一个新的 Agent 会话启动,它需要知道"哪些任务完成了、哪些失败了、从哪里继续"。

这两个受众的需求完全不同。作为工程师,需要的是一眼能扫到的进度概览,数字、比例、异常摘要。不需要看到 120 行的完整任务清单,那反而是噪音。Agent 恢复时需要的是结构化的、可程序化解析的完整状态数据,每个任务的 ID、状态、产出路径、失败原因。

所以终端输出给人看,简洁、可扫视:

[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)

结构化的完整状态写到文件里给 Agent 读,这是 File As Progress 的状态文件。dispatch 脚本不需要在终端输出里重复这些信息,Agent 恢复时直接读文件。

6.3 File As Progress

这是长程任务编排中最核心的设计。

所有进度状态持久化到文件系统。不依赖 Agent 的记忆,不依赖会话上下文,只依赖磁盘上的文件。每完成一步操作,立即写入文件。不攒批,因为 Agent 随时可能被中断。

这意味着无论 Agent 在哪一步被打断,下次启动时只需要读文件就能知道"上次跑到哪了"。新会话开始时,Agent 不需要任何历史上下文,它只需要:读任务清单文件,看哪些是已完成的、哪些还没开始、哪些失败了需要重试,然后从断点继续。

文件载体可以是 TSV(简单任务,人可读)、JSON(复杂任务,支持嵌套元数据),甚至纯文本。形式不重要,重要的是"一切状态都在文件里"这个约束。

6.4 任务状态设计

有了 File As Progress,还需要一套状态机来描述每个子任务的生命周期:

TODO → IN_PROGRESS → DONE                   → FAILED                   → SKIPPED

状态设计的核心理念是:仅凭当前状态就能决定下一步做什么。恢复逻辑不需要知道"之前发生了什么",不需要回放历史,只需要读到"当前状态是什么",就能推导出续传策略。这要求每个状态都是自描述的,它本身就携带了"接下来该怎么办"的信息。

基于这个理念,可以根据任务的复杂度设计更精细的状态,实现更精确的续传。比如一个有"分析→执行→校验"三步的子任务,粗粒度状态只有 TODO/DONE/FAILED,中断在"执行"阶段时只能从头重来。如果细化为:

TODO → ANALYZING → ANALYZED → EXECUTING → EXECUTED → VERIFYING → DONE                                                               → FAILED

那恢复时可以精确判断:状态停在 ANALYZED,说明分析已经完成,直接从执行阶段开始;停在 EXECUTED,说明执行完了但没来得及校验,直接跑校验。每多一个状态,就多一个可以精确恢复的断点,减少重复工作。细粒度状态必须搭配对应的持久化产物,本质还是File As Progress的思路,每个中间状态都需要对应一个落盘的文件,例如ANALYZE将结果写入到文件,EXECUTING才能立即恢复回来。

但状态也不是越多越好。每个状态都需要对应一个持久化的检查点(比如一个中间文件或一条状态记录),维护成本不低。实际设计时,建议在任务执行时间较长、或者某个步骤有明确的中间产物可以复用的地方增加状态。对于 10 秒就能跑完的子任务,TODO/DONE/FAILED 三个状态就足够了,即使中断了,重跑的成本也很低。

最简单的恢复逻辑是:找到所有 TODO 和 FAILED 的任务,继续执行,已经 DONE 的跳过。

但有一个细节需要特别注意: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 <hash> -- <files> 恢复。

完成清理后,把状态重置为 TODO,等待下次调度。

6.5 多轮重试

subagent 失败是正常的。超时、输出格式错误、生成的代码无法编译,这些都会发生。将失败进行分层,不同层次对应不同的重试策略。

内层:恢复会话。 子 Agent 因为网络异常、进程崩溃、compress 错误等原因异常退出,任务本身没有错。dispatch 脚本记录了这次执行的 conversationId,检测到异常退出后,恢复同一个会话说"继续",Agent 从断点接着跑。这相当于续传,降低了成本。比如 6 个文件改了 4 个时进程崩溃,恢复同一个会话后 Agent 直接从第 5 个继续。

中层:带反馈重试。 子 Agent 正常完成了,但产出不满足校验条件。开一个新的子 Agent 会话,把错误信息作为上下文喂进去,针对性修复。比如 tsc --strict 报了 3 个类型错误,把完整报错信息带给新会话,Agent 只修这 3 处,不重新改造整组。限制 2-3 次。达到上限仍然失败,revert 工作区、清理环境、标记 FAILED。到此为止,子任务层面不再做更多尝试。

外层:主 Agent 重新调度。 中层耗尽后留下了一批 FAILED 的文件。要不要对这些文件重新 dispatch 给子 Agent 再来一轮?这需要在主 Agent 层面去决策。重新 dispatch 意味着为这些文件重新分组、生成新的 Prompt、启动新的子 Agent,相当于把它们当作全新的子任务再跑一遍。这里需要权衡成本和时间:如果 FAILED 的文件只有两三个,重新 dispatch 一轮的代价不高,值得尝试;如果 FAILED 了几十个,可能说明任务规则本身有问题,盲目重跑只是浪费 Token,不如先排查原因再决定。

判断走哪层,看产出文件的状态,不依赖解析 Agent 的文本输出。文件是否存在、内容是否合法,是稳定的、可程序化检查的依据。

△ 多轮重试分层


GEEK TALK

07

示例


上面的内容是从抽象的层面去介绍存在的困难点以及如何去解决。下面用两个真实落地的场景来具体展示它们如何组合成完整的执行方案。

7.1 全量 Code Review

场景: 21 个前端模块做一轮全量代码审查,产出审查意见并批量修复。

任务拆解: 按模块分组,每个模块内按目录归类文件,单文件源码行数超过 MAX_LINES(默认 500) 则独立成组。分组时同目录的文件放在一起,因为它们往往有共享的 import 和类型依赖,放在一起审查质量更高。

其中MAX_LINES 的选取逻辑:每个 chunk 的 token 构成为:源文件正文(主要消耗):agent 执行时从磁盘读入源文件,平均 1 行 ≈ 14 tokens(按 50 字符/行、3.5 字符/token 估算)

固定开销:prompt 模板、规则、文件元信息、review 输出及 agent 中间步骤,约 8,000 tokens

以 500 源码行为例,单 chunk 总消耗约 20,000 tokens,占 Claude Sonnet(200K)窗口的 10%,为并发子任务的对话历史和输出留出充足余量。

并行执行dispatch 脚本以 CLI 方式启动多路 subAgent 并发审查,每个 subagent 读取自己对应的inputs/{chunkId}-input.json,审查完毕后直接将 issue 列表写入segments/{chunkId}.json。主 Agent 通过检查 segment 文件是否存在来判断任务是否完成。

校验保障: 审查意见的质量属于主观维度,需要独立的 Evaluator Agent 校验。架构变成三个角色:主 Agent 编排、subAgent 执行审查和修复、Evaluator Agent 对意见做批判性评估。Evaluator 的 Prompt 要带有挑战性语气,主动挑毛病而非寻找优点。

续传: 审查进度写入 TSV 文件。中断后恢复时,读取 TSV 文件定位到当前 Phase,跳过已完成的模块,继续处理未完成的。

7.2 JS to TS 迁移

场景: 将整个项目的 JavaScript/JSX 文件批量迁移到 TypeScript/TSX。

任务拆解: 按同目录归组,累计行数不超过 3000 行,单文件超过上限时独立成组。但这个任务有一个额外的约束:文件间存在依赖关系。被依赖的叶子文件必须先迁移完成,依赖它的文件才能开始。所以分组时还需要按依赖拓扑序排优先级。

完成条件: 这个场景可以完全用程序化验证。每个文件迁移完后,用 Babel AST 对比确保逻辑结构不变,用 tsc 做类型检查确保编译通过。两项都通过才标记为 DONE。

错误隔离: 某个文件迁移失败,不影响其他文件。失败的文件保留原始的 JS 版本,状态标记为 FAILED。整个项目在部分文件未迁移的情况下依然可以正常构建(因为 TypeScript 项目可以混用 JS 和 TS)。

File As Progress: 用 migration-tasks.tsv 记录每个文件的迁移状态。每次启动按顺序检查:.agent.env 存在且含 token → migration-tasks.tsv 已生成 → 无 IN_PROGRESS 残留 → 进度是否全部完成,从第一个不满足的条件对应的 Phase 继续执行。每个 Phase 对应独立的 reference 文件,Agent 只读当前 Phase 所需的指令。

GEEK TALK

08

从经验到框架:Skill for Skill


在实现了上述一系列长程任务后,我们发现每个任务的骨架结构是高度一致的:

<skill-name>/├── SKILL.md                    # Phase 定义 + 会话恢复检测 + 完成标准├── scripts/│   ├── discover.js             # 扫描目标,生成任务清单(幂等)│   ├── dispatch.js             # 读清单,分组,并发调度 subagent│   ├── build-prompt.js         # 程序化构建子任务 Prompt│   ├── poll.js                 # 轮询子任务状态 + 补位启动│   ├── merge.js                # 收集子任务结果,合并为最终产物│   └── status.js               # 查询整体进度├── references/│   ├── phase0_setup.md         # 环境配置指令│   ├── phase1_analyze.md       # 分析规划指令│   ├── phase2_dispatch.md      # 批量执行指令│   └── phase3_finalize.md      # 收尾验证指令└── evals/    └── evals.json              # 评估用例

这就引出了一个更进一步的思路:能不能把长程任务的编排经验本身也做成一个 Skill?

我们做了一个叫 long-term-task-orchestration 的 meta-skill(元技能)。它不直接执行任何业务任务,而是教 Agent 如何创建新的长程任务 Skill。当你告诉 Agent"我需要一个批量做 X 的 Skill",它会读取这个元技能的 reference 文件,按照模板生成完整的 SKILL.md、scripts 目录、references 目录,自动包含 Phase 设计、状态管理、并发调度、恢复逻辑。

这是用 Agent 来强化 Agent 的工作能力。不是让 Agent 做一次任务,而是让 Agent 生产出能反复做这类任务的工具,节省每一次长程任务都需要工程师亲自编排的成本。

可以从仓库地址(https://github.com/hixuanxuan/long-running-agent-tasks下载体验,在Cluade Code环境下安装,安装命令如下,会将long-term-task-orchestration 和 skill-eval(用于评测)一并安装。

npx skills add hixuanxuan/long-running-agent-tasks -y

long-term-task-orchestration 配合 skill-creator 一起使用。你只需要用自然语言描述任务目标,Agent 会自动完成从骨架生成到脚本填充的全过程。示例prompt:

/long-term-task-orchestration 创建skill实现React Compiler迁移并下线全部memo。

Agent 会自动生成完整的 Phase 设计、脚本目录、状态管理和恢复逻辑,并自动启动skill-eval评测,将 body 的评测结果可视化展示给用户并询问是否需要继续,确认后启动循环修复的过程,确保Skill的 workflow 是可以稳定执行的。工程师不需要关心背后的并发调度、断点续传、重试分层这些编排细节。当出现任务类型是对大量同类目标执行相同的操作,并逐个验证结果,可以使用这个这个meta-skill帮助你快速完成Skill的创建,并使用创建的Skill在项目中高效稳定的完成任务。

GEEK TALK

09

结尾


Harness 中的每一个环节,都隐含了一个"当前模型做不到"的假设。随着模型能力提升,这些假设会逐渐过期。

做Harness Engineering 是在模型能力和工程可靠性之间找到合适的边界。模型每一次进化,这个边界都会移动:曾经需要脚本控制的环节,可能下一代模型就能自主处理了。但"确定哪些环节该交给模型、哪些该留在框架里"这个判断本身,不会因为模型变强而消失。每当新模型出现,重新审视这个边界,去掉一个环节,观察对结果的影响。

Harness Engineering 是团队基础设施建设的一部分,解决 Agent 完成大规模任务时的不确定性,并提供可量化的结果评估能力。


 END

  推荐阅读

IMClaw:通过微信/飞书操控ClaudeCode/Codex/GeminiCLI/Pi Agent蜂群


我用 Go 重写了一个 OpenClaw 框架:这就是 GoClaw


从心理按摩到实操上手的OpenClaw全指南


百度MEG数据中台ClickHouse在数据湖仓中的探索和应用


百度一「虾」🦞“龙虾”全家桶开张!手机、电脑、家里都能用~


图片
一键三连,好运连连,bug不见👇

53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询