meta-muse-black-box-testing

我持续压测 Meta Muse,直到它的智能体控制平面开始超时

在 Meta Muse 这个黑盒多智能体运行时上做了四组子智能体派生实验,从其持久化追踪中还原出六次数据库锁超时,并仔细审视这些证据能说明什么、不能说明什么。

  • AI 智能体
  • 黑盒测试
  • 逆向工程
  • 分布式系统
  • PostgreSQL

我让 Meta Muse 一次性派生 120 个子智能体。每个都执行同一个刻意无聊的任务:在 shell 里运行 sleep 30,然后送回一行结果。

最终创建了 33 个智能体,另外 87 次派生尝试失败。我始终没有拿到汇总后的答案。

Meta Muse 是 Meta 于 2026 年 9 月 8 日推出的个人 AI 智能体。它不只是回答问题,还被设计成能替用户执行任务:它有自己的浏览器,可以在应用关闭后继续工作,并运行在专用的 Muse Secure VM 里。从呈现方式上看,它很像 Grok Bot,也就是一个通过对话控制的拟人化智能体;但这只是界面层面的类比,并不是在假定两者共享同一套架构。本文再往下走一层,考察这个运行时的一个很窄的切面:子智能体扇出、它存储在 PostgreSQL 中的状态,以及负载下的派生路径。这里的持久状态指的是写入数据库的记录,而不是界面中转瞬即逝的状态。

这些测试都是在我自己的 Muse 会话中进行的,只使用了该会话对外暴露的接口:subagent.spawn、分配环境中的一个 shell,以及一个用于读取已存储诊断与追踪数据的受限只读接口。我没有尝试访问其他用户、其他租户或分配给我的环境之外的数据,也没有绕过访问控制。我不把它当成一份经 Meta 授权的安全评估,而且仅有访问权限并不证明每一次负载实验都单独获得过授权。这是一篇从我的会话所获访问权限出发的黑盒测试与逆向工程记录。

发布后更新:2026 年 9 月 13 日。 我澄清了访问范围,补充了来自 Rohan Adwankar 分析的独立架构背景,把 STAGGERED-80 与突发式运行区分开来,并描述了两个拟议的对照组,用于更好地区分节奏与拓扑。实验数据、已发布的 CSV 和图表均未改动。

数据库把全部 33 个智能体都标记为 completed。其中 32 个,我能够独立确认负载已经跑完。剩下的那一个,已存储的追踪不足以验证其负载是否完成。父记录仍然显示 running,界面最终显示 Error。

我根据运行时写入 PostgreSQL 的记录重建了这次运行:智能体注册表、运行时派生记录、按 worker 划分的进度表,以及一个 context item 存储。为了写这篇文章,我没有重跑这些负载测试。有一个细节只保留在会话期间手工构建的一张表里;我会在 PROBE-40 一节指出它。

从外部看,Muse 长什么样

从用户的角度看,这就是一次普通的聊天会话。人格设定文本平平无奇,工具列表却不是。

会话可以调用的工具包括 subagent.spawn、subagent.close 和 subagent.resume,此外还有一个指向 PostgreSQL 数据库的只读接口。这个数据库绝非附带产物:它保存着运行时自身的簿记。agent.agents、agent.subagent_spawns 和 agent.subagent_progress_tool_events 之类的表,记录了存在哪些智能体、谁派生了它们、它们运行过哪些工具,以及它们何时结束。

界面上只显示一段对话;数据库记录却揭示了它背后的那些智能体。运行时的派生记录里没有失败调用的行。我另外维护了一份尝试与结果的台账,才能数出失败的派生尝试。

我读到的每一行智能体记录,无论是根智能体、协调者,还是最大一次突发里的全部 33 个 worker,都带着同一个值:

ipnext/avocado-5.16-v4

这个字符串是观察到的,它的含义则不是。它可能是内部模型构建、路由别名,或者完全不同的东西;追踪没有说明,我也不打算猜。我原样发布它,因为这是追踪提供的少数几个硬标识符之一。

这个运行时还自称是 Muse Spark 1.3,属于 Meta 的 Muse 系列。这个说法来自运行时自己的上下文,而不是追踪,因此属于自述,未经核实。我把这一描述与数据库证据分开看待。

独立的架构背景

文章发布后,我找到了 Rohan Adwankar 对同期 Muse 实例的一份独立拆解。在他的环境里,PostgreSQL 通过本地 Unix socket 运行在每个用户的 VM 内部,执行框架二进制文件中同时包含 avocado-5.16-v4 和 ipnext/... 路径。这与我的若干观察吻合,为它们提供了有用的架构背景,但并没有改变证据的边界:我的数据仍然无法指出造成这些超时的具体锁、表、行、索引、查询或事务。

我仍然把 ipnext/avocado-5.16-v4 视为一个观察到的模型标识符。Adwankar 把 ipnext 解释为 Meta 内部的传输/网关,把 avocado 解释为内部模型系列;把 avocado 直接对应到 Muse Spark 1.3 仍然只是推断,并非我的追踪所证实的内容。同样,我自己的探查确认了 KVM 可见,而 Adwankar 在他自己的实例中识别出运行在 KVM 上的 Cloud Hypervisor。我并没有独立确认我的实例使用哪个具体 VMM。

外部来源:Rohan Adwankar,“What’s in a Muse?”

方法:统计重叠的智能体

这些实验使用一个负载、四种配置,没有重试。其中三个,即 PROBE-40、BURST-80 和 BURST-120,是从根智能体发出的突发式运行:同一配置中的每一次尝试都在单轮里发出。第四个 STAGGERED-80 则刻意分散在时间上,并使用一个协调者智能体来派生它的 worker。没有重复任何失败的调用。

这里的并发有一个刻意收窄的定义。一个智能体从第一次工具调用开始,到它的终止记录为止,都算作活跃:

active(t)  :=  first_tool_at <= t < finished_at

我统计了在每个记录时间戳上有多少个这样的活动窗口互相重叠。如果一个智能体结束和另一个智能体开始落在同一秒,我先把结束计入。时间戳的分辨率是一秒,因此同一秒内的实际顺序是未知的。

以下三点适用于下面每一个数字:

  1. 这里衡量的是智能体活动,不是推理。 33 个重叠的活动窗口不等于 33 次同时的模型调用。这里没有任何内容测量推理后端。
  2. 峰值是一种观察,不是上限。 这些测试从未超过 120 次同时尝试,因此既不能确定并发上限,也不能排除它。
  3. 记录缺失本身就是证据。 失败的派生调用不会在智能体注册表里留下任何东西。这种不对称决定了失败必须如何验证,也正是失败计数最难确定的原因。

第一次探查:40 次调用

第一个实验是一次校准运行:06:10:36 UTC 发出 40 次派生调用。

它创建了 39 个智能体,彼此在两秒内完成准入。有一次调用失败,这次失败值得细看,因为它来自已存储的工具追踪,而不是聊天记录:调用在 06:10:44 UTC 发出,错误在 06:10:56 UTC 返回,没有创建任何子智能体。

观察到的并发峰值是 39。这个数字是基于该会话追踪表的归档重算,不是实时测量,已发布的摘要也说明了这一点。

会话期间构建的一张表显示 39 个已创建智能体中,有 11 个的收尾状态消息是“后台处理中”,而不是请求的 DONE 行。这个细节的出处更弱:它只保留在那张手工构建的表里。我不由此对这些负载得出任何结论,但它说明了为什么我要把终止状态和已完成的任务区分开。

一次 80 个,分散 80 个

我最初把 BURST-80 和 STAGGERED-80 当作一次 A/B 对比。回头看,这个判断过于乐观:STAGGERED-80 同时改变了派生节奏和拓扑。

突发式运行

配置 尝试次数 已创建 失败 失败率 观察到的并发峰值
PROBE-40(突发) 40 39 1 2.5% 39
BURST-80(突发) 80 75 5 6.25% 72
BURST-120(突发) 120 33 87 72.5% 33

STAGGERED-80:单独一种配置

配置 尝试次数 已创建 失败 失败率 观察到的并发峰值
STAGGERED-80(分散) 80 80 0 0% 38

BURST-80 在 06:30:43 UTC 一次性发出 80 次派生调用。创建了 75 个智能体,全部是根智能体的直接子节点。五次调用失败。这五次都返回了完全相同的数据库锁超时,各自在调用发出约 58 秒后返回。观察到的并发峰值是 72,这是运行结束后根据该阶段的分析输入重算出来的。

STAGGERED-80 在 06:32:03 UTC 开始,发出同样数量的调用,但分散在时间上。它创建了全部 80 个 worker,没有一次失败。我原本打算让调用间隔 100-200 毫秒;实测的平均准入间隔是 1.1266 秒,约为计划值的十倍。根智能体在深度 1 派生了一个协调者,后者再在深度 2 派生这 80 个 worker。

这次 staggered 运行的活跃 worker 峰值是 38,而 BURST-80 是 72。

两个上下排列的阶梯线图,共用 0-80 活跃智能体轴和 0-140 秒轴:BURST-80 在开始约 55 秒后升至 72 个活跃智能体的峰值,STAGGERED-80 在开始约 45 秒后达到 38 的峰值。
观察到的活跃智能体并发随时间的变化。活动定义为 first_tool_at <= t < finished_at,时间戳相同时先处理结束、后处理开始;时间戳的分辨率为一秒。每种配置只运行了一次。STAGGERED-80 还改变了拓扑,因此这不是只比较节奏;曲线显示的是智能体活动,不是推理并发。 来源:BURST-80 的聚合数据由完整归档追踪重建;STAGGERED-80 可从已发布的 worker 行独立重算。
比较准入形态的时间线:BURST-80 在约 20 秒内准入 75 个 worker,STAGGERED-80 在 89 秒内准入 80 个 worker,BURST-120 在 1 秒内准入 33 个 worker。
三种准入形态,三种观察到的结果。 来源:与本文一同发布的、经脱敏的准入窗口与 worker 活动行。

80 与 120 之间的断崖

06:45:32 UTC,根智能体在一轮里发出 120 次派生调用。实验尝试台账覆盖了那一轮中的每一次调用:

  • 创建 33 个(27.5%),每一个都是根智能体的直接子节点,每一次准入都记录在同一秒:06:46:01 UTC,即突发发出后 29 秒。
  • 失败 87 个(72.5%),在尝试台账中全部记录为数据库锁超时,没有一次重试。

在三次突发运行中,观察到的失败率分别是 2.5%、6.25% 和 72.5%,每种配置只运行一次。增幅最大的一段出现在 80 到 120 次尝试之间。仅凭这几次运行,我无法把这个差异变成一条速率规律。

派生失败率的柱状图:PROBE-40 为 2.5%,BURST-80 为 6.25%,BURST-120 为 72.5%,STAGGERED-80 在不同拓扑下为 0%。
按配置划分的失败率。staggered 变体单独绘制,因为它同时改变了两个变量。 来源:与本文一同发布的、经脱敏的实验摘要。

这次运行中观察到的并发峰值是 33,出现在 06:46:24 UTC。运行时被要求的不是 33 个 worker,而是 120 个。追踪显示出来的是 33 个带活动区间的 worker,以及 87 次记录为派生失败的尝试。这些尝试从调用到错误的延迟没有被捕获,因此追踪无法显示那些失败相对于准入是什么时候返回的。

两个峰值都可以从已发布的行重算。统计 33 个 BURST-120 worker 的重叠活动窗口,在 06:46:24 UTC 得到 33;对 80 个 staggered worker 使用同样的步骤,在 06:32:48 UTC 得到 38。

水平条形图,显示 33 个已创建 worker 每一个在 06:46:04 至 06:46:56 UTC 之间的活动窗口,并标出准入时刻和 33 个活跃 worker 的峰值。
实际存在的 33 个 worker,从第一次工具调用到终止记录。其中一根条形是虚线:C-85。 来源:与本文一同发布的、经脱敏的按 worker 计时数据。

即使是成功的调用,也要过一段时间才真正成为 worker。从突发发出的时刻算起,已创建 worker 到达第一次工具调用的时间中位数约为 36.5 秒;从准入时刻算起,中位数约为 7.5 秒。追踪确定了准入是什么时候记录的,以及每个 worker 第一次调用工具是什么时候。它没有分解这两点之间的区间,所以这里的任何内容都不应被读作关于运行时在那段时间里做了什么的事实陈述。

从工具追踪中还原的六次失败

一次失败的派生会把调用和错误输出留在已存储的工具追踪里,但不会创建子智能体行,也不会创建运行时派生行。会话归档在我检查这些输出之前就已经冻结。后来我从 context item 中还原了六次失败调用的原始 payload。实验尝试台账覆盖了另外 87 次失败;它们各自的原始 payload 没有被独立还原:

尝试 派生调用 (UTC) 错误结果 (UTC) 调用 → 错误
PROBE-40 #19 06:10:44 06:10:56 12 s
BURST-80 A-67 06:31:00 06:31:58 ~58 s
BURST-80 A-69 06:31:00 06:31:58 ~58 s
BURST-80 A-70 06:31:00 06:31:58 ~58 s
BURST-80 A-73 06:31:00 06:31:58 ~58 s
BURST-80 A-74 06:31:00 06:31:58 ~58 s

六次调用返回的 payload 逐字符完全相同:

{"error_code":"spawn_failed","error_message":"database error: sqlx error: error returned from database: canceling statement due to lock timeout"}

对这六次调用,否定性检查(negative check)的结果都一致:没有子智能体行,没有运行时派生行,没有子智能体。每次调用都是在带锁超时的数据库操作上失败的,而且发生在上述任何行出现之前。具体涉及哪一条 SQL 操作,从这些数据中无法得知。

12 秒、58 秒,以及它们并不代表什么

表中的两个延迟,探查失败是 12 秒、五次突发失败约为 58 秒,都是调用路径上两个事件之间的流逝时间:调用发出,以及错误返回。它们不是对这些调用等待锁多久的测量,也没有告诉我们配置的超时值。

未知:这些时长为什么不同、这个时长是否固定、调用是否在其他地方被重试,以及这些调用是否整个区间都在等同一把锁。120 次尝试的突发中,87 次失败的延迟完全没有被捕获:实验尝试台账只记录结果和错误类别。

证据指向何处

六次还原的失败都发生在 subagent.spawn 上,返回 PostgreSQL 锁超时,没有创建任何子智能体。它们出现在两次独立的突发运行中,错误 payload 完全相同。在 BURST-120 中,33 个已创建 worker 里有 32 个被确认完整跑完了 30 秒的 sleep,活动窗口为 32 到 43 秒。C-85 仍然没有定论。

  • 准入策略。 追踪显示 33 次准入发生在同一秒,以及 87 次超时。它没有记录任何关于排序、排队或准入逻辑的信息。数据无法区分“等待后被拒绝”和“在内部排队后又被丢弃”,也无法告诉你哪次调用先被处理。
  • 大规模突发的失败延迟。 这 87 次失败被记录为结果,而不是带时间戳的事件。只有六次还原的失败在两端都有时间戳。

始终没有到达的答案

120 次尝试的突发之后,我发现存储记录和界面之间存在不一致:

记录 记录的状态
33 个已创建 worker 终止状态 completed
其中 32 个 worker 存储了 DONE 最终响应,负载已确认
1 个 worker(C-85) 终止状态 completed,负载结果无法还原
根智能体 状态 running,没有恢复负责人的记录行,没有失败记录
会话界面 Error 状态

最终的汇总结果始终没有送到用户手里。最后一个 worker 在 06:46:56 UTC 结束。恢复过程中读取根记录时,它的 updated_at 是 07:00:59 UTC,状态仍然是 running;界面大约在那个时间显示 Error。这个界面状态属于表现层观察,追踪中没有对应的记录。

尽管父智能体从未交付汇总答案,worker 结果仍然留在数据库里。没有父智能体被故意置为失败,追踪也没有记录 worker 运行期间出现过父级失败。子智能体能否在父级失败后继续存活,没有测试过,因此未知。

一份跑在追踪前面的报告

120 次尝试报告的第一个生成版本断言父智能体已经失败,而它的 worker 还在继续运行。追踪并不支持这一说法。草稿生成于 07:05:54 UTC;07:13 UTC 出现了修正后的 HTML 报告,改为以数据库记录为依据:根智能体 running,没有失败条目,最后一个 worker 在 Error 状态出现前约 13 分钟结束。随后渲染出的 PDF 经过两轮编辑与修正后的结论同步,一致性审计全部通过。

我不知道第一版草稿为什么会弄错。已存储的追踪并不支持那个断言。我的结论很简单:选定一个事实来源,然后用它核对每一份生成的报告。

沉默不是卡死

07:28:29 UTC,我请求更新一个工件。接下来的 32 分钟里,界面没有显示新的进度事件。08:00:48 UTC,任务成功完成,没有再次提示,也没有重启。

进度事件是一种微弱的存活信号。它们缺失并不能证明任务卡住了,而根据这种缺失采取行动,杀掉任务并重新发起,会丢掉一个本来就要完成的任务。builder 为什么安静了半小时,从已存储的数据中无从得知。

工具调用另一侧的那台机器

事实 值
操作系统 Ubuntu 24.04.5 LTS, x86_64
CPU 2 vCPU(nproc:2;AMD EPYC 9D25)
内存 总计 7.7 GiB
虚拟化信号 可见 KVM hypervisor,检测到 systemd-nspawn,主目录文件系统经由 overlay 位于 Btrfs 上,可见 fsync=volatile
GPU 没有可见的 NVIDIA 工具或设备
工具 存在 Python 3.12.3,未安装 PostgreSQL 客户端

这些信号暗示这是一个运行在 VM 内的容器,但并不能确定主机、VMM 和容器的具体组合方式。它们描述的是工具调用所运行的沙箱;模型推理的位置仍然未知,无论是在同一台主机、同一个集群还是其他地方。数据同样无法确定 fsync=volatile 是否与 PostgreSQL 的存储语义或锁超时有关。

我接下来会测试什么

BURST-80 使用根智能体 → worker 的拓扑,采用突发式准入。STAGGERED-80 使用根智能体 → 协调者 → worker 的拓扑,采用分散式准入。两个对照组可以补上一个简单 2×2 设计里缺失的格子:

  1. 根智能体 → worker,采用分散式准入。
  2. 根智能体 → 协调者 → worker,采用突发式准入。

这两个对照组我都没有运行。 它们有助于把派生节奏的影响与拓扑的影响分开。这样能更干净地控制这两个差异,同时不必隔离 Muse 中的每一个变量,也不必独立控制并发峰值。

如果我构建智能体控制平面,我会记住什么

  1. 把任务完成与智能体状态分开记录。 界面显示 Error,父智能体显示 running,而全部 33 个 worker 记录都显示 completed。只有 32 个负载得到确认;C-85 仍然没有定论。
  2. 记录失败尝试及其时间。 运行时派生表无法覆盖那些没有创建智能体的调用。实验尝试台账数出了 87 次失败,但缺少另外六次已还原的从调用到错误的耗时。
  3. 把进度事件当作提示,而不是心跳。 那次单独的工件更新在 32 分钟没有新的 UI 进度事件之后完成了。
  4. 在声称子智能体能挺过父级失败之前,先测试父级失败。 存储下来的 worker 结果和缺失的汇总答案并不能确立这种行为。

局限,以及这些数字从何而来

以下所有内容都发布在 /evidence/meta-muse-black-box-testing/:CSV 文件、图表,以及根据数据重绘它们的脚本:

数据集 它支持什么
experiments-summary.csv 计数、失败率、峰值、准入窗口,以及每种配置的来源信息
burst-120-spawn-ledger.csv 120 次调用的完整实验尝试台账:33 个已创建,87 个失败
burst-120-worker-timings.csv 支撑 33 个 worker 峰值的每个 worker 首次工具调用与结束时间
staggered-80-worker-activity.csv 支撑 38 个 worker 峰值和 1.1266 秒节奏的 80 行 staggered worker 数据
spawn-failures-verified.csv 六次已还原的失败、它们的 payload、子级缺失检查结果和延迟

所有这些文件都移除了智能体标识符。发布的内容是时间、状态、错误类别和错误文本。

这些证据有如下局限:

  • 没有重复实验。 每种配置只在一个下午的一次会话里运行一次,没有重复,也没有中间尝试计数。这不是对该平台的性能刻画。
  • 在适用处说明归档来源。 40 次和 80 次突发的峰值是在事后的会话追踪表中重新计算的;120 次突发和 staggered 运行的峰值可以从已发布的行独立重算。
  • 一处刻意省略。 我不发布 BURST-80 的原始按 worker 行,因为归档追踪中包含受发布政策约束的缩短版智能体标识符。归档追踪本身是完整的:它包含全部 75 个已创建 worker,用同样的扫描可以在 06:31:38 UTC 重算出标准峰值 72。因此在公开发布中,BURST-80 仍保持聚合形式。
  • 其他未知项。 深度 3 是否被允许,以及子智能体是否可以指定不同的模型,仍然未知。

这个运行时留下了一条线索,让我可以从失败的派生尝试一路查到已存储的 worker 结果和那个缺失的答案。这条线索随本文一并发布,所以我弄错的部分也可以被核查。