ToWow / Flowness 工程进展:从需求到交付

渐进式披露之后:Flowness 如何让 Agent 知道自己读到了什么

Agent 只读一部分材料时,要知道这部分来自哪一刻的历史、漏了多少、需要时怎么回到原文。

Agent 只读一部分材料时,要知道这部分来自哪一刻的历史、漏了多少、需要时怎么回到原文。

设想一个 Agent 准备接手上传接口的修复。它需要知道:前一个人做到了哪里,哪些约束必须保留,还有没有没解决的问题。Flowness 是我们自己开发的运行环境,用来组织多个 AI Agent 持续工作;这些任务、约束和结果,会留在不断增长的事件日志和材料中。

Flowness 用增量事件索引查记录:先按任务编号定位,只解析命中的正文;新事件到来时,只把它补进相关索引。这省掉了重复整理历史、重复解析无关内容的成本。但接手者真正要作出判断,还要跨过几道门槛。

查到的记录可能很多。分两页读,第二页可能已经处于另一段历史。系统只返回两条问题,Agent 可能误以为只有两条。为了缩短上下文而提供的投影,也可能被当成已经读过的原文。

本文要讲的,是 Flowness 查询和材料交付代码里的一套读取路径:它带着快照、覆盖说明和原文引用,为的是让“读得少”不再悄悄变成“把没读到的东西当作不存在”。查询、工作集(为一项任务汇集、交给 Agent 的那组材料)和派发是不同层,实现和启用边界下面分开说。

索引只负责定位,命中以后仍可能读得太多

给事件附上任务编号、类型和执行者,再查这些字段,是一个有效的起点。查上传修复这个任务,可能只命中几条;查所有心跳或状态更新,却可能得到几十万条。即使查找很快,把结果全部解码、校验、构造成对象,仍会消耗大量时间和内存。

Flowness 的底层事件索引因此还有正文展开预算。它检查一次操作准备物化多少条记录。“物化”指把存储中的记录展开成程序能够直接使用的对象。检查结果集大小,比只检查程序有没有调用某个“全量读取”函数更接近实际成本:一个名字看起来很窄的查询,也可能返回日志的大半。

默认预算是一次物化 5 万条。超过预算时,默认模式通常照常读完,同时警告并记录调用位置;只有机器内存已经吃紧、发起读取的又是命令行进程时,才会拦下报错。严格模式则一律抛出异常。所以这道预算目前用来发现昂贵读取、在内存吃紧时拦一道,还不是所有生产读取的硬上限。

面向 Agent 的工作集读取还有另一种预算:限制最终保留的候选数量和字节数。两种预算也不能互换。一个宽类型中可能只有最后一条满足任务条件,系统需要先筛选,才能知道最后保留多少。

现有测试把这个区别做得很具体:四十条同类型事件,进一步按任务字段筛选,只留下一个目标;保留上限设为一条,查询仍应成功。但测试同时确认,四十条候选都被物化了。返回一条,不代表只检查了一条。覆盖说明里因此记着一条原因“流式扫描成本未测量”,不把只返回一条写成扫描成本也小。

只返回 1 条,前面物化了 40 条
测试构造的例子:这一代只有 40 条记录,全是 NodeTouched;按任务字段只留下 1 条,前面 40 条却都被物化了。覆盖说明把检查数留空(null),并标明流式扫描成本未测量。

分页之前,先确定这次读的是哪一段历史

假设上传修复任务已有四条匹配记录,每页返回两条。Agent 读完第一页时,另一个执行者又追加了第五条。如果第二页重新查询最新数据,两个页面就可能来自不同的历史状态。尤其采用倒序或位置偏移分页时,新增记录还可能改变页面边界。

Flowness 的查询续读把这次查询与快照绑定。后续页面通过游标继续读取原来的结果范围,而不是默认刷新成此刻最新的历史。这里的游标是一张续读凭据:它让系统知道接着读哪一次查询,并携带相应的有效期和作用域约束。

我们运行了一个相关测试:第一次查询匹配四条,先返回两条;随后向日志追加一条,再通过新的查询服务实例使用旧游标。第二页仍属于原快照,读到原来的后两条。重新发起查询才会匹配五条。这个结果说明续读不依赖旧服务对象一直活着,也说明“保持一致”与“追踪最新”需要两个不同动作。

工作集组装也有相近要求。任务材料可能来自多个查询;如果每次选取都换成最新索引,最终上下文就会拼进不同时刻的片段。组装时,系统固定住同一代读取视图(代码里叫 IndexedWorksetView),这次组装里的所有选取都从这一代取。这一代在组装期间被钉住,不会被清理;钉住只保护派生缓存不被删,不会因此获得修改任务或决定业务结果的权限。

我们接受固定快照的代价:它不会自动纳入后来发生的修复。Agent 若要回答“现在是否已经解决”,就必须发起新查询,不能把早先快照中的状态当成实时状态。

一页读完,不等于问题已经查完

回到四条记录、每页两条的例子。第一页返回两条,显然还有下一页。但第二页已经没有下一页时,它自身也仍然只包含完整结果的一半。

这就是查询协议同时保留分页信息和覆盖信息的原因。分页回答“还有没有下一页”;覆盖信息回答“眼前这份返回,相对于声明的查询范围,包含了多少、遗漏了多少”。它还区分完整、部分和未知,区分穷举、受限枚举和排序选取。

在刚才通过的测试里,第二页的 has_more 为假,但状态仍为 partial,遗漏数仍是 2。只有把两页合起来,才拿到了这次固定查询的四条记录。最后一页不会因为自己是最后一页,就冒充一份完整结果。

续读停在原快照,每页只含 4 条中的 2 条
测试构造的例子:快照里匹配 4 条,每页 2 条。两页各返回其中 2 条,另外 2 条记为 omitted,所以两页都标 partial;第 2 页的 omitted 2 就是第 1 页那两条。追加的 evt-new 不进旧快照,重新查询才匹配 5 条。

同样,查不到不等于不存在。如果只查了这个任务的直接关联关系,“没有发现问题”只能解释成这个范围内没有匹配项。工作集的材料表示进一步区分“声明范围内没有”“没有开展搜索”“来源不可用”。对接手者来说,这些状态会导向不同动作:前者可以继续工作,后两者可能需要补查。

这种区别直接影响判断。若系统把“反证没有搜过”压成空列表,Agent 就可能把尚未开展的检查误认为没有反证。缩短内容时,否定判断的适用范围不能一起被删掉。

缩短正文,同时保留展开到原文的路径

即使查询准确,一份材料也未必需要一次全部交给模型。任务接手者先需要关键事实、约束和未完成项;详细审计信息可以晚些读取。这与 Skill 的渐进式披露有共同思路:先提供足够决定下一步的入口,再按需加载更详细材料。Anthropic 对 Agent Skills 的介绍将技能元数据作为第一层,相关指令及资源在需要时继续加载。[1]

Flowness 在工作集投影中面对的进一步问题是:缩短后,怎样知道哪些原文已经交付,哪些只是被表示出来?

投影实现保留材料来源、覆盖情况、必读项、缺失项和待展开项。未直接交付的条目有展开入口;完整审计元数据也有单独读取位置。执行者可以先用较短的工作视图开展工作,在需要核对某条约束或事实时,再沿引用读取对应材料。

这不是把一份短摘要宣布为“已经读完”。原文引用和内容校验负责追溯材料;材料是否支持当前判断,仍需要阅读与推理。工作集自己的输出也明确把语义质量标为未评估:机械交付与来源声明,不等于任务所需知识已经充分。

预算冲突时同样如此。必读原文不能被静默截断后标为完成。既有工作集区分原文完整要求和允许投影表示的交付要求;某些必读项可能导致超预算交付,并明确记录超额。我们在这里选择保留任务约束,而不是为了让字节数好看就制造一个“完整”的短包。

投影里,待展开项带命令,三种空分开记
工作集投影的字段示意,条目 i0 至 i3 为示例:选中条目的原文放在投影里,其余列为待展开项,并给出按条目别名展开的命令;派发时必读项超出预算也会放进投影;声明范围内没有、没有开展搜索、来源不可用分开记录。

先给轻量入口很常见,区别在它承诺什么

Flowness 里还有一个相近的设计:信号处理用的竞争封皮(Cover)。它把对象类型、阻塞情况、紧急档位、代价和严重程度等信息组织成可供规则消费的字段,用来支撑分流、处置等决策,不在上面这条读取链路里。

这一思路有成熟的相邻方法。CloudEvents 将上下文属性与事件数据区分开,规范中的 subject 就可用于中间件不理解正文时的订阅过滤。[2]

几种方法的区别,在于它们承担什么承诺。Skill 元数据帮助选择要加载的能力;事件属性帮助路由;竞争封皮约束信号处理所用的字段。本文的查询与工作集则要告诉接手者:依据哪一段历史,在什么范围里取了什么,未交付部分如何继续读取。

在渐进式披露的共同思路下,持续工作的 Agent 还需要明确的读取语义。少显示一点内容只是表面结果,关键是少显示以后哪些判断仍然成立。

已接入的代码路径,与 10 月 18 日核对时的启用范围

查询服务有索引读取分支,工作集读取器与查询服务共享项目派生索引;当时的调度器中也存在工作集准备入口,准备完成后会把返回的提示词接入派发。准备超时或出现任何异常时,都会回退到原来的提示词。

但本次读取 Flowness 工程的项目配置,得到的结果是:Agent runtime 已启用,工作集自动派发未启用。因此,这篇文章能确认的是实现、调用入口和限定测试行为,不能宣称当前所有 Agent 都通过这套工作集接收材料。其他派发分支和显式覆盖也需要单独核实,不能凭一个配置值推断每次派发出去的 Agent 拿到了什么。10 月 23 日起,这条调度器派发路径已经停用;到 11 月 3 日核对时,正在替换它的新调度器代码里没有工作集入口。

配置给出的工作集预算是 32 KiB,准备超时是 15 秒。固定读取视图代码另设最多 1 万条保留候选、64 MiB 保留字节和 300 秒生命周期上限。它们分别控制交付、准备时间和视图资源,并不是一组总内存上限,也不是测得的最优参数。本轮没有重新测量生产吞吐、模型 token 消耗或任务成功率。

实际运行的四个测试用例,覆盖了追加后的旧游标续读、宽类型进一步筛选,以及候选数或字节数超限时报错。这些证据足以检验具体行为,尚不足以回答整套方案是否比直接读取便宜;完整性说明、原文保留与校验也会带来开销。

对接手上传修复的 Agent 而言,理想结果不是拿到最短的材料,而是能说清:“我读的是这个时点的历史;这两条只是本页结果;另有材料没有展开;这一项还没搜过;需要核对时能回到原文。”索引让它更快找到记录,这些读取机制则帮助它避免把一小部分记录误当成整个问题。

参考资料与核对范围

[1] Anthropic 工程博客文章“Equipping agents for the real world with Agent Skills”。用于比较按需加载的共同思路,不代表本实现直接源自该文章。

[2] CloudEvents Specification(GitHub 上的 cloudevents/spec 仓库)。用于比较上下文属性与数据分离、按 subject 过滤。

Flowness 部分依据 2026-09-15 的源码读取、项目配置读取及四项定向测试,2026-10-18 按现行代码复核,四项测试复跑通过。文中上传修复情境是教学案例;四十条筛成一条和分页行为来自测试,不是生产收益数字。

下一步

如果你的团队正在把 agent 放进真实业务,想了解 Flowness,或想聊企业 AI 落地怎么做,请在“联系我们”页写下行业和想解决的问题,我们按这些内容回复。 联系我们