批量与并行交给代码编排
模型负责理解与判断,循环、过滤、重试、批量和并行由程序完成;同构批量任务的模型往返次数应接近 1 而不是 N。
模型负责理解、判断、规划和生成编排逻辑;循环、过滤、重试、缓存、分页和批量执行交给程序。这不是分工偏好,是算术:逐个发起 LLM 往返时,延迟、token 消耗和跑偏概率都随 N 线性放大,N 次往返应当压缩成 1 次。
把不确定性留给模型,把确定性交给程序
典型场景是搜索后读取前 N 个文件、对 N 条记录逐条执行同构工具调用。让模型发起第一步、等结果、再决定第二步,等于把同一段上下文付 N 次钱,还给了它 N 次跑偏的机会。
判据很硬:同构批量任务的模型往返次数应接近 1,而不是 N。模型产出的应该是编排逻辑——选一个编排模板,或者生成一段编排代码——真正跑循环的是你的程序:search、read、filter、batch、retry 全在代码里完成,最后一次性把结果报给模型。
什么时候可以并行
五条同时成立才并行:输入已确定、没有前后数据依赖、没有共享可变写入、输出落在独立命名空间、失败可以独立处理。任何一条不满足就串行,或者先消除依赖。
所以第一步是构建调用依赖图,把没有依赖关系的 Connector、工具、子运行和测试任务批量并行。依赖图不是可选的优化项,它是你判断「这两件事能不能同时跑」的唯一依据,也是下一步设并发上限的输入。
并发上限按资源分别设
并行不等于无限并发。模型、外部 API、浏览器、数据库、CI 各有各的容量,必须分别设上限。这类线上事故多数不是并行逻辑写错了,而是并发把下游打挂了。
还有一条容易违反:不能为了并行化重复获取相同上下文。五个并行分支各自读一遍同一个文件,省下的时间又还回去了,还多付五倍 token。共享上下文要提前取好,作为输入分发下去。
编排执行要进 Trace
编排跑在代码里,但它不能是黑箱。执行过程要进 Trace,支持回放和审计——一次批量任务改了两百个文件,事后你必须能回答「第 137 个文件是谁、依据什么改的」。
淘天塔罗平台对这个边界的处理值得照抄:文件改动、命令、Mock、截图、Diff、测试结果作为可复查事实写进执行账本;方案理由和剩余风险作为判断走 Comment 和 Handoff,并且明确 Handoff 只能当线索、不能当事实。编排层产出的是证据,模型产出的是判断,两者不能混在一个字段里。taotian-loop
模型生成编排代码时,按沙箱执行
优先提供编排模板或 SDK,让模型选模板、填参数。如果允许模型自由生成编排代码,就按沙箱执行并施加权限约束:文件系统范围、网络出口、可调用工具的白名单。模型写出来的循环和它写出来的命令一样,都可能删掉它不该删的东西。
Anthropic 在长跑 agent 上有个相关教训:用 while true 循环让 Claude 永续工作,有一次 Claude 执行了 pkill -9 bash,把自己杀了,循环直接结束。这类副作用是长跑系统的固有风险,容器隔离和权限边界只降低概率,不消除它——真正兜底的是测试和 CI 守住质量边界。anthropic-c-compiler
调度者不要读正文
把一次开发拆成十个阶段(澄清 → 设计 → 拆解 → 测试计划 → 评审 → 实现 → 审查 → 端到端 → 验收 → 归档)、每阶段一个子代理之后,最反直觉的一条纪律是:负责调度的那个角色,不读任何产物的正文。
它只做三件事:从子代理回传的结构化协议里取「结论」(通过 / 打回 / 要人确认),从状态文件里取「进度」,然后把两边的话互相转达。spec、方案、任务清单、测试报告、业务代码——一个字都不进它的上下文。有人问它「刚才那条结论为什么这么判」,它不自己去翻代码回答,而是把问题丢回给当初给出结论的那个子代理去解释。baidu-harness-workflow
为什么值得这么严格?因为调度者的上下文是整条流水线里唯一必须活到最后的东西。一个需求走完十个阶段、来回打回好几轮,如果每一轮的产物正文都堆进调度者,它早就被稀释得又慢又飘——这正是上下文腐化的机制。让调度者保持轻,它才能在整个生命周期里稳定做判断。
这条纪律还有两个附带收益:子代理只认输入里的具名字段、不认具体项目布局,所以换项目只需换一份知识库,阶段定义一行都不用改;知识变更只改一处,不用去改十个代理文件。
代价是你要定义一套结构化的双向协议,并接受「调度者不回答细节问题」这种不够顺手的交互。
回退必须受限,状态写入要卡时机
阶段流水线会往回退:用户推翻上游、下游发现上游缺口、门禁打回、测试不通过。四种触发原因不同,但必须走同一套机制,否则「回退」很快会退化成乱跳。baidu-harness-workflow
两条约束把回退钉住:
一、回退目标必须在前、且真的完成过。目标阶段在流程表里要位于当前阶段之前,并且在这个需求的历史里确实完成过。这条挡住了「从实现阶段直接跳回需求澄清」这类越级回退——中间被跳过的阶段没做过,跳回去等于凭空重来。
二、状态转移要在同一次写入里完成。当前阶段落终态、目标阶段从「已完成」改回「进行中」、指针指向目标阶段,三件事一次写完,并把回退原因作为字段塞进目标阶段的输入,让它知道这次回来要补什么。
这里有个容易被忽略的时机问题:等待用户确认的期间,当前阶段应该仍然是「进行中」。终态只在真正转移的那一次写入里才落。理由是冷启动恢复——进程挂了再拉起来时,如果状态文件里已经写着「已完成」,但用户其实还没确认,恢复逻辑就会跳过这一步。守住「任何时刻至多一个阶段在进行中,且它必然等于当前阶段」,恢复时状态才永远是对的。
程序跑完了,不等于任务做完了
编排下沉到代码之后,会多出一个容易误判的状态:流程执行成功。
主流的托管编排实现里,一次运行整体返回「完成」,只说明这段程序跑到了终点。它内部可能出现三种情况:某个分支的任务失败了、某个分支根本没被创建起来、程序捕获了失败之后继续往下跑。这三种都以「完成」收尾。anthropic-workflows
后果很具体:一项批量审查已经生成了汇总报告,但其中一部分材料根本没被读取;报告是完整的、格式是对的、状态是成功的,而内容缺了一块。只看最终状态的验收会把它当成全部做完。
正确的验收不看状态字段,看三件事:
- 覆盖范围——该处理的 N 个对象,实际处理了几个。
- 失败清单——哪些失败了、失败原因是什么、有没有被程序吞掉。
- 关键结果——最终产物里的关键结论,是否都有对应的成功来源。
配套两条:程序自身的错误(代码抛异常、超时、触达数量上限)通常走另一个终态,要和「任务结果不完整」分开处理;涉及外部写入的步骤要防重复提交,因为重试可能创建新的执行线程,同一步被执行两次。
还有一个常见误解:预算耗尽不是即时刹车。达到上限通常只是停止发起新请求,已经发出去的那些仍会跑完,并行时实际消耗可能超过上限。把它当硬开关来设计,会低估最坏情况。
让 Agent 主动到什么程度
编排解决的是「程序怎么跑」,还有一个相邻问题:Agent 该不该自己发现问题、自己决定开始干。做长期任务时这绕不开,因为用户大部分时间不在场;但「主动」不是一个开关,用两根轴才能说清楚。
横轴是问题由谁定义:L1–L3 里问题都是用户定的——时间规则是用户设的,触发条件也是用户设的,Agent 只在合适时机执行;到 L4,用户只给目标,「现在缺什么、该补什么」由 Agent 自己算出来,前提是差距必须可计算(目标要么能量化,要么有可检查的状态);L5 是 Agent 自己推断目标,L4 的目标说不清时实际上就滑到了 L5 qoder-proactivity。
纵轴是做到哪一步:只是告知、给出建议,还是直接替你执行。两根轴独立——一个 L5 的 Agent 可以只停在告知,一个 L3 的 Agent 也可以直接对外执行。讨论「能不能交给它主动做」时,两个位置都要说清。
L3 到 L4 是质变,因为责任归属变了。有一组很诚实的数据:某位自建 Personal Agent 的每日 loop 从 7 月初起产出 276 条待办,138 条被标为完成,其中 126 条是某次会话里集中手动核销的——loop 自己执行并关闭的,0 条 qoder-proactivity。同一套系统里,替人回复群消息这种对外、不可逆的动作反而敢交出去,因为规则全是人定的、边界是人画的、出事责任在人。而待办是 Agent 自己算出来的问题,它算错了、替人做错了,后果谁担没有答案之前,人只会让它停在「建议」。
所以两条判断比「聪不聪明」有用得多:
- 授权跟着子任务走,不跟着目标走。 给了「把这个里程碑推到上线」的目标,不等于授权它给别人发消息,也不等于授权它改线上配置——每个子任务风险不同,授权只能一个个给。
- 能不能交出去,看失败代价。 拆成三问:能不能撤回、影响到谁、结果能不能验证。问题由谁定义决定出错时责任归属,失败代价决定容错空间;两个都低的位置才适合直接执行。
想让一件事往上走,杠杆只有一个:把目标和验收规则说清楚。 说清目标,Agent 不用猜,L5 变成 L4;说清验收规则,「做没做对」变得可检查,一部分 L4 又退回「按规则执行」,问题定义权和责任重新回到人手里。主动性的上限,是人与 Agent 之间能显式对齐的那部分目标。
代价与自检
代价是编排层本身要建设和维护:依赖图、并发控制、Trace 回放、沙箱。任务异构、每一步都需要模型重新判断的场景,硬套编排反而更慢。
三个问题可以自查:你的批量任务里,模型往返次数接近 1 还是接近 N?并行的分支之间有没有共享可变写入?一次批量执行失败后,你能回放出来它是怎么失败的吗?
