Flowness 真实问题 · 第 1 期 · 阅读系列导读 →
熟悉的朋友可以直接跳过这一段。
Flowness 是我们搭的多 agent 工程系统,由一个人和一群 AI agent 一起运行:人定方向、做取舍,agent 写代码、派活、审查。它从 2026 年 3 月下旬跑到现在,6 个多月;只算还留着会话记录的部分(大多是 7 月以后的),它就消耗了超过 1400 亿 Claude token,其中九成以上是缓存读取。这个系列把运行中真实遇到的问题一个个摊开写。真实环境里 agent 碰到的问题,大多非常琐碎:一次 grep 没匹配到,一道检查没有对象也记“通过”,一个用量字段被读错了含义。结果能不能信,往往就卡在这些地方。我们把它们写出来,是想和在做同样事情的人交流。
如果你的调度器会在任务失败后自动重试,你大概写过这样一段逻辑:执行者失败,清掉“已派出”的标记,重试计数加一,任务回到待派队列。下一轮调度看它有没有正在运行的实例、有没有被锁住、并发名额够不够,都没问题,就再派一个。这段逻辑我们 6 月就写进了系统里,背景一节会讲为什么。
现在设想失败的原因在任务本身。比如验收条件要求改动前每次调用至少有 10 次查找,而查找次数取决于机器上同时活跃的会话数,实测只有 7。下一轮的每道检查仍然回答“可以派”,因为它们问的是“现在能不能派”,没有一道问它和上次有没有不同。只要验收条件不变,换一个执行者,结果一样。
2026 年 9 月 6 日(本文时刻均为 UTC),Flowness(我们搭的多 agent 工程系统)把同一个任务先后交给了 9 个执行会话。前 8 个没有成功结束,第 9 个在当天 17:11 完成。这 8 次失败的原因分成 5 类:验收条件无法满足(3 次)、提交被拒、会话误用命令、协调 agent 误停、磁盘满,另有 1 次进程消失、原因未查清。其中至少 3 次,系统用的是上一次失败时的同一份任务包、同一组验收条件,再派了一个新会话。系统里本来有同一任务最多自动重派 3 次的上限,这个任务的重试计数最后记到了 8,上限一次也没有生效。
这篇写的是失败后自动重派本来是为了解决什么问题,上限为什么没有生效,以及我们之后做的几件事各挡住了什么、还开着什么。
背景:任务包、终态记录和失败后自动重派
这篇要用到的概念不多,可以先记住一条链:会话失败,标记被清,任务回到可派,派发程序再派。
任务包(task package)是一个任务的规格:要做什么、允许修改哪些文件、验收条件(acceptance criteria)是什么。验收条件是可以由程序复算的条件,例如某项测试通过,或某个读数不超过某个值。任务包每发布一个版本,系统都计算一个哈希值,记入事件账本。改任务包的是规划会话:它读中止原因,修改任务包并发布新版本。
执行会话是一个后台运行的 Claude Code 会话,领一个任务去做。它结束时应当写一条终态记录(terminal state),结果有五种:成功;需要重新规划(验收条件或规划有问题);无法完成;需要外部动作(例如等某条分支合并);经顾问裁决后中止。顾问是我们系统内部的一个机制:执行会话遇到自己无法决定的问题时,可以发出咨询,由另一个使用更强模型的会话给出裁决。它和 Claude Code 自带的 advisor 工具不是一回事。这五种结果后文会反复出现,因为 9 月 6 日当时的派发规则只对其中一种(需要重新规划)暂停派发。
派发程序每 20 分钟由定时任务运行一次。它先算出“当前可以派的任务”,再对每个任务调用单任务派发命令,启动一个执行会话。当时,系统原有的常驻编排服务处于暂停状态,这个派发程序是唯一在运行的自动派发入口。它靠派发标记(dispatch marker)防止重复派发:那是一个文件,存在就表示这个任务已经派过,不要再派。执行会话以非成功结果结束后,系统会自动删除标记,同时把这个任务的重试计数加一(计数存在这个任务目录里一个叫“重试标记”的文件中),任务回到“可以派”。
这套“失败后自动重派”是 6 月为了解决一个相反的问题加的:当时失败任务的标记永远不删,失败任务永远不会再被派发,依赖它的下游任务都静默卡住。所以在我们这里,失败后再派是被当作正确行为设计出来的;防止它无限循环的办法,主要是一个重派次数的上限(另有一条已请求重新规划就暂停派发的规则,见后文)。
另外三个概念只需要知道名字。协调 agent 是一个长期运行的 Claude Code 会话,负责盯整体进度、处理异常、必要时手动派发或停止会话。提交关口(commit gate)是所有写账本操作都要经过的检查程序;执行会话写“成功”终态时,关口要求通过若干完成检查。执行会话在复测中发现验收条件与实测不符时,会在账本里登记一条不一致记录,这类记录必须处理后才能成功结束。
做过消息队列的人会认出这个形状。一条消息每次处理都失败、每次又被放回队列,叫毒消息(poison message);通行的处理是给它设最大投递次数,超过就送进死信队列(dead-letter queue),交给人看。本篇的 8 次失败里,出在消息本身(验收条件)的是 3 次。我们两样都写了:上限 6 月就有,对象超过上限后进死信箱的入口也是 6 月建的。据代码阅读,拿任务的重试次数和上限比较的那一处,只在常驻编排服务的批量派发里,而那条路径当时停着。实际在跑的派发程序这一边,当时只对“需要重新规划”这一种终态会等新版本,其余三种非成功终态不等任何新东西就再派。它按终态的类型决定等不等,不看失败的原因:磁盘满写成“需要重新规划”,会等新版本;写成“无法完成”,当时就立刻再派。agent 场景里还多一层:队列里的“消息”不是固定的负载,是一份会被 agent 改写的规格。规划会话发了新版本,派发程序看到的是有新版本发布,而新版本里的验收条件可能换成了一个同样没有验证过的数。“有新版本”这条证据是真的,但它回答的是“有没有发布新版本”,不是“验收条件有没有变”。
经过:先看全貌,再看三段
9 个会话都在 9 月 6 日这一天。先放全貌,再按三段看;你不需要记住每个会话的细节,只需要记住它属于哪一段。
这个任务要给一条读取路径加索引,降低每次调用(一次执行这条读取路径)的查找耗时。它的第一份任务包在 9 月 5 日发布,但它所在的阶段还没满足开工条件,派发程序连续 43 次拒绝派发,没有启动任何会话。9 月 6 日 02:06,一个规划会话把这个任务从整个阶段的开工限制里单独豁免出来,并给它加了前置依赖。此后到 17:11 发生的事,先用一张小表说:
| 项 | 当天的数 |
|---|---|
| 执行会话 | 9 个先后启动 |
| 任务包新版本 | 8 个新版本:第 1 个是规划会话在第 1 个会话启动后补发的,其余 7 个来自 4 次重新规划(每次发 1 到 2 个) |
| 派发命令的发起方 | 9 次都是派发程序。依据是账本里派发记录的发起方字段、理由字段里它的自动接力说明(这个字段由调用方填写)、9 个启动时刻彼此相隔 20 分钟的整数倍(误差在一分钟内),以及协调 agent 的日志 |
| 协调 agent 手动解除拒派 | 2 次:在第 6 个和第 8 个会话之前解除拒派状态(补写终态、清理残留),目的就是让下一轮派发继续 |
第一段(02:23 到 08:41):三个会话。 第 1 个先被提交关口拒绝,第 2、3 个失败在验收条件上。
第 1 个会话 02:23 启动。会话启动 6 分钟后,同一个规划会话给任务包加了一条约束,发布了新版本,验收条件没变。执行会话随后判断应当中止并请求重新规划,想提交这条终态记录,却被提交关口以“写入冲突”连续拒绝两次:它启动时读到的账本位置,早于这次重新发布。它向顾问求助,按顾问的裁决写下终态“经顾问裁决后中止”。
但这个会话的进程没有结束,它又开了一轮新的工作,复测发现一条验收条件要求某个比值不超过 0.40,实测是 0.676。它登记了这个不一致,之后再没有写终态记录。这条没人处理的不一致记录,后来在第 5 个会话准备结束时被完成检查指了出来。
第 2 个会话 04:23 启动,用的仍是同一组验收条件。04:23 到 04:59 之间,第 1 个会话的进程也还在活动,两个会话同时在跑。
第 2 个会话复测得到 0.612,并发现这条条件引用的读数来自另一条代码路径,与本任务无关。它请求重新规划后中止。规划会话因为并发名额已满,2 小时 12 分钟后才被派出。
第 3 个会话 07:23 启动。新任务包把验收条件改成:改动前后的耗时之差不小于 1500 毫秒。这个阈值是从一份报告里的线性成本模型推算出来的,没有实测过。会话复测的结果和报告差得很远:
| 报告的模型与验收条件 | 会话实测 | |
|---|---|---|
| 每个会话能省多少 | 82.5 毫秒 | 约 3.4 毫秒(在账本里的 72 个会话上) |
| 改动前后的耗时之差 | 不小于 1500 毫秒(验收条件) | 244 毫秒 |
会话指出实测与报告的模型相差约 24 倍,再次请求重新规划。
第二段(09:03 到 12:45):第 4 个会话用新任务包,第 5、6 个沿用同一份。 一次误用、一次误停、一次磁盘满,第 4 个会话之后没有新版本。
第 4 个会话 09:03 启动,用的是新任务包。会话在工作中想重新开始一轮:它先直接释放了会话锁,再开新一轮时被拒绝,于是执行了“以无法完成结束”的命令,随后写道旧会话已关闭、现在重新开工,并立刻开了新的工作。系统把那条终态记录当真,任务回到“可以派”。
第 5 个会话 10:04 启动。第 4 个会话写下终态 32 分钟后,派发程序的一轮派发派出了它,用的是同一份任务包。它准备以“成功”结束时,被两项完成检查拒绝,其中一项指向第 1 个会话第二轮工作留下、从未处理的那条不一致。它正在处理时,协调 agent 把它当成重复启动的会话停掉了,随后替它补写了终态。
第 6 个会话 11:23 启动,仍是同一份任务包,距上一条终态 3 分钟。验收条件里的测试每次需要复制约 2GB 的工作副本,而服务器根分区已经满了,只剩 626MB。请求重新规划。磁盘那一个小时的变化是这样的:
| 时刻 | 根分区可用空间 |
|---|---|
| 第 6 个会话启动几分钟后 | 清理到约 12GB 可用 |
| 12:20,第 6 个会话读到 | 626MB |
| 清理后的一小时内(12:20 的 626MB 就在这段下降当中) | 并发运行的其他会话的测试副本又把它填到 106MB |
第三段(13:23 到 17:11):残留、再一次条件、成功结束。
第 7 个会话 13:23 启动,用的是新任务包。它在最后的完成检查阶段进程消失,没有写终态记录,原因至今没有查清。它留下的派发标记、执行锁和待处理标记(后两个也是防止同一任务被重复启动的文件)让下一次派发一直被拒,到约 15:20 之后,协调 agent 手动清理了三处残留才放行。
第 8 个会话 15:42 启动,用的是第 7 个会话的同一份任务包。验收条件有一个前提:改动前,每次调用至少要有 10 次查找。而查找次数取决于机器上同时活跃的会话数,连续三个时间窗口实测都是 7。会话运行了 22 分钟,请求重新规划。
第 9 个会话 16:42 启动。第四次重新规划把那条条件拆成两条与活跃会话数无关的条件:一条比较改动后的稳态查找耗时与改动前,一条要求索引的建立成本在 3 次调用内收回。两条都通过,会话运行 30 分钟,成功。
前面几个会话并不是什么都没做。按协调 agent 当时的工作日志,第 5 个会话之后,代码改动和实测读数大部分已经在分支上,后面的会话主要卡在验收条件和结束的手续上。
为什么当时看起来没问题
这一节是这篇文章的核心。我们把当时手里的每条证据先按它当时的读法写一遍,再说它实际的含义。可以在每条揭开之前停一下,想想你的调度器会不会也这样读。每条的“当时的读法”是我们事后的复原,不是当时有人说过的话。
证据一:大部分中止都走了规程。 多数执行会话按规程登记不一致、请求重新规划、写终态记录,规划会话也按规程发布新版本。按规程走完的失败,看起来就是正常工作的一部分。
实际发生的:有一条规则会在“已请求重新规划、新版本还没发布”时暂停派发,第 3、4、7、9 个会话都是在新版本发布之后才派出的,说明这条规则在它设计的情形里起了作用。但第 1、4、5 个会话的中止类型不在这条规则的范围内,第 7 个会话没有终态,于是第 2、5、6、8 个会话都是在没有新版本的情况下派出的(第 2 个会话的任务包多了一条约束,验收条件和上一次相同)。四种非成功终态里,9 月 6 日当时只有一种会让派发程序等新版本。
证据二:四道检查都放行。 派发前的每一道检查(有没有活跃会话、有没有派发标记、阶段开工条件、并发名额)对每一次重派都回答“可以”。
实际发生的:这些检查问的是“现在能不能派”。没有一道检查去比较任务包的内容或验收条件相比上次有没有变化。同一份任务包、同一组验收条件(第 6 个会话还有同一块装满的磁盘),四道检查给的答案一样是“可以”。
证据三:有新任务包。 4 次重新规划各发了新版本。有新版本,看起来就是有新信息。
实际发生的:有新任务包,不等于有新信息。第 3 个会话用的新任务包,把比值条件换成了“绝对差不小于 1500 毫秒”。这个数是从同一份报告的线性模型推算出来的,没有经过实测验证,第 3 个会话就是因此失败的。
证据四:重派上限存在。 同一任务最多自动重派几次、超过就熔断的规则,6 月就写好了,默认上限是 3 次。按这条规则,就算前面的检查都放行,超过 3 次也会停。代码里有一段说明,大意是:清除标记时的计数、上限和熔断记账与其他清除来源一致,不额外造一条绕过上限的路。
实际发生的:上限只在常驻编排服务的批量派发里检查,而那段时间编排服务是暂停的。派发程序走的是单任务派发命令,这个命令只读取熔断标记是否存在,不比较重试次数,所以在这条路径上,熔断标记不会被写出来;这个任务的目录里也确实没有熔断标记。重试计数一直在正常累加,最后是 8,但派发决定里没有任何地方拿它和上限比较。另有一条收割规则会读它:收割没有终态的会话时,先按失败的类型决定,类型不明的、已经再派过一次的进死信箱;这个任务这次没有进死信箱,依据是它的目录里没有熔断标记。那段代码说明讲的是记账方式一致,读到的人很容易理解成上限在这条路径上也生效。这一节的结论来自代码阅读和运行时计数文件,我们没有做对照实验。
四条证据揭开之后,还有一条当时谁都看不见的结构性原因:监控只盯“不派”。9 月 5 日,系统有四个小时一个任务都没派出,没人察觉。当天我们加了一个报警:连续两轮有可派任务却零派发,就发出告警。这个报警只在“不派”时触发。派发决定这一侧没有“同一任务重派过多”的报警。前面说的那条收割规则会发出一种“结构性失败直接死信”的通知,我们查了账本,9 月 6 日这个任务没有这类通知。常驻编排服务的批量派发里有一个重派耗尽的告警,但它和上限一样,在停着的那条路径上。据代码阅读、运行时计数文件和账本查询,我们推断 8 这个计数没有变成任何人看到的东西。
最后发现这个任务被反复派发,是因为负责人看到有任务反复重做,问这是不是在浪费 token。协调 agent 查账本后回答:9 月 2 日以来的账本里没有任何任务跑到几十次,最多的是这一个任务,8 条终态记录(第 1 到 6 个和第 8 个会话各一条中止,第 9 个一条成功),1 次成功。
还有一件事要单独说,因为它决定了“再派一个”为什么没有用。第 2、3、8 个会话失败的原因是同一类:验收条件依赖执行会话控制不了的东西,分别是另一条代码路径的读数、按未验证模型推算的绝对阈值、机器上同时活跃的会话数。只要条件不变,再派一个会话也过不去。这三次失败,换多少个会话结果都一样。
消耗了多少
我们统计了这个任务的 9 个执行会话和 4 个规划会话各自的会话记录,按请求去重;不包括它们派出的检查子任务、顾问咨询和协调 agent 自身。为了把不同类型的 token 放在一起比较,我们把缓存读取、缓存写入和输出按价格比例折算成“输入等价 token”,这是估算口径,不是账单。最后一列按 2026 年 9 月 4 日读到的官方标价折算:9 个执行会话用的都是 Sonnet 5(输入每百万 token 2 美元),4 个规划会话用的是 Opus 5(5 美元)。美元同样是标价估算,不是账单。
| 模型调用 | 输入等价 token | 按标价约合 | |
|---|---|---|---|
| 8 个没有成功结束的执行会话 | 1543 次 | 折算约 5400 万 | 100 到 110 美元 |
| 其中用同一份任务包再派的 3 个(第 5、6、8 个) | 折算约 1780 万,占 33% | 约 36 美元 | |
| 4 个规划会话 | 折算约 790 万 | 36 到 40 美元 | |
| 成功的第 9 个会话 | 折算约 190 万 | 约 4 美元 |
同包再派的那一行要单独说明:这是“如果挡住同包重派,这几个会话本可以不启动”的上限;挡住之后任务仍然要有人做,只是需要先改任务包,所以它不等于可以省下的量。
我们试过的
按时间写。这次事故有三种情形,下文叫它们缺口 1、2、3。缺口 1:没有新信息也再派,派发前不比上次。缺口 2:派发决定里没有任何地方拿重试计数和上限比较,派发决定这一侧也没有重派过多的报警。缺口 3:验收条件依赖执行者控制不了的量。每一件事都针对一个具体事故,我们想让你看到的是每件事落在哪个缺口上,以及它覆盖了哪一部分。
9 月 6 日当天,修了派发与完成检查里的几个缺陷。 一是完成检查:原来按运行编号把不一致记录和处理记录配对,而命令行每次都生成新的运行编号,已经处理过的不一致也会对不上;改为按不一致记录自己的编号配对。这一项的触发案例是另一个任务。二是派发前的“是否已有活跃会话”检查:原来只要会话进程还在就拒绝派发;改为看这次运行是否已经写了终态。三是单任务派发命令补上了“任务自身状态”检查,例如任务是否已关闭、是否正在等待重新规划;此前派发程序单发任务时会绕过这些检查。这些修复解决了具体的门禁问题,没有触及“用同一份任务包再派”。
9 月 6 日深夜(第 9 个会话成功几小时后),“需要重新规划”的任务在出新版本之前不进可派列表。 原来的规则依据“请求重新规划”事件判断,新规则改为依据终态记录本身,覆盖没有发出请求事件的情形。它不覆盖“经顾问裁决后中止”,这是有意的设计:顾问叫停不等于必须重新规划。
9 月 7 日,负责人裁定:同一任务被反复派发而每次都没有新信息,要用机制解决,不要针对每个具体缺陷逐个修补。 随后写了一份设计稿,列出五项:每种中止都要写明“满足什么条件才能再派”;派发前检查验收条件是否可满足、所需资源是否具备;按“任务加任务包哈希”计重试次数并熔断;新会话继承上一个会话的产出;同一个任务同时只允许一个活跃会话。这五项对着三个缺口,是这几件事里唯一一份把三个缺口都写进去的;对应关系是我们的归类:第一项对缺口 1,第二项对缺口 3,第三项对缺口 2。设计稿要求走正式的设计、共识、任务拆分流程实施。截至今天,它的状态仍是“提案”;我们在文档里没有找到 9 月 7 日之后的进展记录,也没有查到谁认领了它。设计稿要求的实施流程没有走;下面几项是之后针对具体事故的单独改动,其中一部分在内容上与设计稿的某一项重合(见“现在的状态”)。
9 月 11 日,“需要外部动作”的终态不再自动删除派发标记,改为进入“等待外部动作完成”的队列,有新版本或人工清理后才解除。又一种终态。
9 月 14 日,任务包发布时新增一项检查。 对代码改动类任务包,如果验收条件要求某个程序行为,而任务包允许修改的文件里没有任何生产源文件(例如登记了定时器,却没有承载它的脚本),就拒绝发布。它检查的是任务包的一种情形,不是本文第 2、3、8 个会话遇到的那三种。截至 10 月 6 日,账本里没有它实际拦过一次的记录:最后一条任务包发布拒绝记录(9 月 14 日 10:44)早于这项检查转为硬拒的合入时刻(同日 16:48),之后发生了 15 次任务包发布,没有再出现发布拒绝记录。它拦得住,只有测试证明过。同一天,“无法完成”的终态在出新版本之前也不再进可派列表。又一种终态。
9 月 14 日到 18 日,写成协调 agent 的规则。 同一任务被打回两次以上就停止派发;反复中止两次以上,直接派一个使用更强模型的会话一次做完,不再派只做分析的判断会话,也不再重新规划。这些规则由协调 agent 执行,不是由派发程序执行,我们在代码里没有找到对应的自动检查。
9 月 19 日,派发程序被设成每轮最多派 0 个,自动派发执行会话暂停。 这是负责人为控制整体开销下的命令,不是针对本问题的修复。
现在的状态
截至 2026 年 10 月 6 日。
我们统计了“非成功终态之后的重派”,并标出其中的同包重派(派发时,这个任务最近一次终态不是成功,而且之后没有发布过新版本):
| 时段 | 非成功终态之后的重派 | 其中同包 | 逐个核对的结果 |
|---|---|---|---|
| 9 月 1 日到 7 日 | 43 次 | 11 次 | 只判了 4 次,见表下 |
| 9 月 8 日到 14 日 | 32 次 | 11 次 | 逐个核对后其中 4 次确实没有新信息(其中 1 次的起因是终态被误记到了另一个任务上) |
| 9 月 15 日到 18 日 | 4 次 | 0 次 |
9 月 1 日到 7 日的 11 次,我们只逐个判了其中 4 次(含本任务的 3 次)。
另按“同一任务、同一份任务包下非成功终态次数”统计全账本:共 11 个任务达到 3 次以上,其中 6、7 月 8 个,最多的一个 8 次;9 月 3 个,最后一次出现在 9 月 10 日。这个问题在 9 月之前就存在;我们没有查到当时有人把它作为问题提出来,也没有核对它们是否像这次一样集中在一天之内。
其余几条:
- 9 月 19 日之后,派发程序不再派发执行会话,账本里最后一条执行会话启动记录在 9 月 18 日。所以上面这些修复,在暂停之后没有在生产上经受过检验,10 月的数据只能说明“没有发生派发”,不能说明“问题没有复发”。其他可能自动启动会话的路径(例如跟进脚本的救援功能)在 10 月是否运行过,我们没有逐个核对。
- 重派上限至今仍不在单任务派发路径上起作用。
- “经顾问裁决后中止”的任务,今天仍然可以被立即重派。这是 9 月 6 日有意保留的设计,是否要调整还没有决定。没有终态就消失的会话(第 7 个会话那种),残留清掉之后也一样可以被立即重派。
- “验收条件依赖执行者控制不了的量”至今没有机器检查。第 2、3、8 个会话遇到的三类条件,都只能靠规划会话逐个重写。
- 设计稿五项里,第一项(统一的“再派条件”字段)、第三项在实际派发路径上的接线、第四项(产出继承)都没有实现;第二、五项部分实现。
- 第 7 个会话为什么消失,没有查清。
如果是你的系统
五条,像饭桌上跟同行说的几句话。第 2 条有可以直接跑的命令,其余四条是设计建议;我们自己没做到的会标出来。
派发前先比较和上次有没有不同。 这是我们的设计建议,还没有在生产上验证:对每一次自动重试,记录任务规格的哈希、验收条件的哈希、上一次失败的原因,派发前比较;全都没变,就不派,转人工或转重新规划。“有新版本”也要看新在哪里:换了一个同样未经验证的阈值,不算新信息。
给重试计数找一个读者,程序或者报警。 列出系统里所有能启动任务的入口,逐个检查它们是否比较重试次数。一个在实际派发路径上没人读取的计数器,起不到保护作用。可以直接搜索:计数器被写入的地方有几处,被比较的地方有几处,后者是否覆盖了所有派发入口。下面两条命令把写入处和比较处列出来。在源码目录下运行,不要在带有多个副本(例如多个工作树)的仓库根运行;把 retry_count 换成你代码里计数器的名字:
# 计数器在哪里被写入
grep -rnE 'retry_count' --include='*.py' . | grep -vE '/tests?/' | grep -E '\+= *1|\+ *1\b|write'
# 计数器在哪里被比较
grep -rnE 'retry_count\s*(>=|>|<=|<|==)' --include='*.py' . | grep -vE '/tests?/'
第二条命令列出的每一处,再顺着调用链确认它在不在每个派发入口上。这两条命令在我们自己的源码目录下跑过。第一条列出两行:一行是这个计数的累加,一行是无关的另一个计数器。第二条列出十行:七行是注释,一行是文档字符串,两行在一个判断函数里,其中一行是 retry_count >= threshold,要看谁调用它才知道它在不在派发入口上,而调用它的地方变量名叫 rc,按名字搜不到。再按计数器函数的名字搜一次,读取处有 5 处:3 处在批量派发里(其中 1 处与上限比较),1 处是累加时先读后加一,1 处在收割没有终态的会话时读,用来决定再派还是进死信箱。只有批量派发里的那一处拿它和上限比较。所以命令只能告诉你去哪里看,判断还得靠读调用链。读者也可以是报警:只监控“该派却没派”,同一任务被反复派发时就没有任何告警,我们这次就是这样,派发决定这一侧没有这样的告警。同时统计“同一任务、同一规格的重派次数”,超过你设定的次数就报警。
为每一种中止结果写明“什么条件下值得再派”。 我们在 9 月 6 日、11 日、14 日按真实事故逐类补了三种中止结果的规则,每补一类之前都至少发生过一次事故。可以在设计中止结果时就一起写清再派条件,例如“需要外部动作”要等对应的分支合并、“需要重新规划”要等新版本发布。
验收条件只用执行者能控制的量。 写验收条件时检查:它是否引用了别的模块的读数,是否依赖机器负载、并发数这类运行环境,阈值是否来自未经实测验证的模型。满足任何一项,再多的执行会话也可能过不去。这一条我们至今没有机器检查,每次失败之后靠规划会话逐个重写。
终态记录只用于真正结束。 我们的一个执行会话把“无法完成”当作“重启会话”来用,系统按字面执行,记下了终态,之后的一轮派发就派了一个新会话。如果你的 agent 能写终态,可以考虑给“重启”“释放锁”提供单独的命令,或者在终态命令里要求写明原因类别。
还没想明白的
有两件事我们到今天没有答案,想听听你的做法。
第一件关于“新信息”由谁判。比较任务包的哈希能回答变没变,回答不了变得有没有道理:第 3 个会话的新任务包哈希变了,验收条件换成了另一个同样没有验证过的数。我们还有一种终态是有意不等新版本的:顾问叫停不等于必须重新规划,所以“经顾问裁决后中止”的任务今天仍然可以立即重派,这是对还是错我们没有定。如果你的调度器会自动重试,这次和上次有没有不同,是由程序判(哈希、失败原因的类别)、由人判,还是交给下一个执行者在开工前先判?如果是程序判,你用的键是什么?
第二件关于验收条件依赖运行环境的量。第 2、3、8 个会话遇到的三种条件各不相同:引用了另一条路径的读数、阈值来自未验证的模型、前提是机器上的活跃会话数。我们没有想到一条规则能覆盖这三种,到今天没有机器检查,每次靠规划会话逐个重写。你允许验收条件依赖并发数、负载这类运行环境吗?如果允许,环境不满足的时候,你怎么让它不被记成执行者的失败?
还有一个小一点的问题:我们的第 7 个会话在最后的完成检查阶段进程消失,没有留下终态,原因没有查清。如果你为后台 agent 会话做过进程消失也会留下一条记录的机制,例如由父进程或看门狗替它写终态,我们想知道它在你那里怎么工作。
依据说明:本文依据 Flowness 事件账本的全量查询(会话启动、终态、任务包发布、重新规划事件)、9 个执行会话和 4 个规划会话的会话记录、9 月 6 日 02:00(UTC)的代码版本和当前代码、派发程序的历史备份、git 提交历史,以及协调 agent 的工作日志。“重派上限不在单任务派发路径上起作用”来自代码阅读和运行时计数文件,没有做对照实验。token 折算是估算口径。美元按 2026 年 9 月 4 日读到的官方标价折算,是估算,不是账单。第 7 个会话的死因没有查清。日期和时刻均为 UTC,状态截至 2026 年 10 月 6 日。
每篇末尾的问题我们自己还没有答案。你有做法,或者在自己的系统里见过同样的事,欢迎写信到 hi@natureblueee.com。