Flowness 运行机制:从任务派发到故障恢复 · 阅读系列导读 →

Flowness 是我们组织多个 AI Agent 持续工作的运行环境。一次任务从提出、派发到完成,会留下许多事件:谁接了工作、读了什么、何时失败、结果放在哪里。这些记录按顺序写入事件日志。后来接手的 Agent 要了解某个任务,首先需要从这段历史中找出与它有关的记录。
本文介绍的事件索引就负责这一步。输入是已经提交的事件和查询条件,输出是匹配的事件记录。它不替模型总结历史,而是在模型开始阅读之前,把需要的材料找出来。
一次查询只关心一个任务,日志却记录了所有任务
假设日志已有十万条记录,其中七条属于“修复上传接口”。最直接的实现是读完整个文件,把每一行都解析成对象,再筛出那七条。这样做容易写对,也适合小日志。但运行时间越长,一次小查询需要处理的无关内容就越多。
另一个容易忽略的成本出现在写入之后。日志只增加三条事件,如果每次都重建整份内存索引,就会反复整理那十万条没有变化的历史。问题并不在于事件记录得太详细,而在于读取和维护都没有利用“绝大多数记录没有变化”这个事实。
Flowness 把这两种重复工作分别处理:维护时只补充新增事件,读取时尽量只展开命中的记录。两者可以配合,但它们解决的是不同环节的成本。

新增事件只进入它所属的索引桶
索引可以理解为一组查找表。任务表记录“任务编号对应哪些事件”,类型表记录“完成事件有哪些”,此外还有执行者、运行编号、关联关系等不同入口。不同表服务不同查询,不要求读者先知道事件在文件的第几行。
看一个缩小的例子。日志依次写入 e1、e2、e3:e1 和 e3 属于任务 A,e2 属于任务 B。任务索引因此是 A 对应 [e1,e3],B 对应 [e2]。随后新增 e4,属于 B;新增 e5,属于 A。维护程序只把 e4 放进 B 的桶、e5 放进 A 的桶,原来的三条无需重新处理。
代码里的完整构建与增量追加复用同一套“将一条事件放入相关索引”的逻辑。这样,首次建立索引与后续维护不会各自解释一次事件所属关系。需要注意,有些入口保存的是列表,有些入口采用后写覆盖前写的单值规则,不能把所有查找表都想成同一种集合。
这个例子能确认的是维护工作量的变化:新增两条,就执行两次单条索引操作。它不是服务器提速倍数的测量,因为实际运行还受到文件读写、事件大小和查询方式影响。
查到事件编号以后,再解析对应正文
有了任务索引,仍可能在启动时把所有事件正文都变成复杂对象。惰性索引进一步把“知道记录在哪里”和“读取记录内容”分开。
它先保留查询需要的索引项,以及事件编号对应的文件字节位置。查询任务 A 时,先取得 A 的事件编号,再去相应位置解码正文,并缓存已经展开的记录。单条几十 KB 的巡检读数和提交记录,以及其他任务的内容,都不必因为一次无关查询全部变成内存对象。
回到十万条日志的例子,如果查询只命中七条,正文解码可以集中在这七条上。这里的“十万”和“七”是教学设定。冷启动仍要建立或读取元数据,某些查询也可能命中大量记录;要求读取全部历史时,最终仍然要处理全部历史。按需展开没有让文件扫描和内存成本凭空消失。
索引能够重建,增量追加也有明确前提
这套设计沿用了数据库索引和惰性加载的常见思路。我们在 Flowness 中要解决的具体问题,是让索引跟随事件账本增长,并让不同任务查询共享这份整理结果。事件日志仍是事实来源,索引是从日志重新生成的派生数据。
增量维护依赖一条很实在的约定:传进来的必须是已提交、按顺序排列、尚未进入索引的新记录。重复传入旧记录,某些列表就会重复;文件发生恢复或截断,旧索引也不能继续被当作有效历史使用。便宜的追加操作,是建立在调用方维持这些条件之上的。
当前实现还写明了单线程使用假设。未来若多个线程共享同一份可变索引,需要增加读锁或快照等一致性安排。不能因为追加路径已经存在,就推导出任意并发读写都安全。
这份索引还有一条覆盖范围的边界。日志按分段文件存放,写满封口的旧分段可以压缩后移进冷归档目录,原来的分段文件随之删除。索引只读还没归档的分段,所以归档里的旧事件不在任何一张查找表里:经这份索引按任务查,查不到它们,也不会报错,只是结果变少。这些事件并没有删掉,归档文件还在。按单条事件编号查时,系统会转到归档里去找;经这份索引按任务查,不会。命令行的按任务查询现在会另外把归档逐条扫一遍,归档里的记录也能找出来,代价是每次都要解析全部归档记录。
我们有意没把归档并进这份索引。提交事件时,有一道查重检查直接读这份索引;旧事件并进来,这道检查放行还是拒绝的结果就会变。
运行历史越长,越要区分保存与阅读
源码和已有测试提供了增量索引的验证入口;本文核对的是实现与测试所覆盖的情形,没有重新测量线上吞吐。可以明确说明的行为是:热索引不必为每次追加重建历史,按需查询不必解析所有无关正文。
这给长期运行的 Agent 系统一个具体设计方向。历史可以完整保存,某次工作需要阅读的部分则应有清楚的入口。以后无论更换模型、交接任务还是追查失败,都可以先定位事件,再决定读多深。可靠的查找能力,让保留历史不必等同于每次重读历史。
更正(2026年10月26日):此前的摘要和导语说“让不断增长的工作历史仍然可以按任务读取”,读起来像任何时候的旧事件都能按任务查到。实际这份索引只读还没归档的分段,归档后的旧事件经这份索引按任务查不到,结果会变少,也不报错。摘要和导语已改,“索引能够重建”一节补了这条边界,“可以重新生成”改成了“从日志重新生成”。另外,“大段工具输出、详细证据”在原始记录里核不到,已换成我们在最近的记录里实际量到的几类大记录。
公众号排版与配图
本篇另备有明亮、适合手机连续阅读的公众号排版稿,配图按照本文机制单独绘制。查看公众号版,或下载 Markdown、HTML、PNG 与 SVG 配图包。