Mastra 推出 @mastra/mainbrella:让 Agent 的文件工具与命令工具共用同一 Linux 容器
When It Rains Agents, Bring a Mainbrella
Mastra 在其 fork 中新增 @mastra/mainbrella 包,提供沙箱、HTTP 文件系统和编辑器 provider,让 Agent 的文件写入与命令执行落在同一个 Mainbrella Linux 容器上。
2026年10月8日 · Andrew Arrow
给智能体一个写文件的工具和一个运行命令的工具,你仍然可能得到一个找不到它刚写好的文件的程序。这两个工具可能在看不同的磁盘。Mastra 的工作区接口让我们把它们接入同一个 Mainbrella Linux 容器,这样智能体就能编写脚本、运行它,并检查它产出了什么。
我们在我们的 Mastra 分支中加入了这一连接。新的 @mastra/mainbrella 包提供了一个沙箱、一个 HTTP 文件系统,以及一个用于 Mastra 编辑器的提供程序。下面的示例针对的是 10 月 8 日的那个检出。
我们的Deep Agents 集成遵循了同样的大致思路:保留智能体现有的工具,并在它们下面提供机器。Mastra 让另一种接缝变得有用。它的进程管理器可以分别暴露一个正在运行的作业、它的输出和它的输入管道。一旦某条命令的耗时超过监视它的连接,这就变得有意思了。
两个工具,一个磁盘
Mastra 是一个用于构建智能体应用的 TypeScript 框架。工作区为智能体提供文件操作和命令执行,而无需让模型学习你的基础设施 API。对于一个小型报告任务,有用的循环很熟悉:检查输入、编写程序、运行它、读取错误、编辑、重复。
下面设置中重要的一行是将 sandbox 传入文件系统的那一行。它就是运行命令的同一个对象。没有第二台机器需要保持同步,文件工具与客户机之间也没有对象存储挂载。
import { Workspace } from '@mastra/core/workspace';
import {
MainbrellaFilesystem,
MainbrellaSandbox,
} from '@mastra/mainbrella';
const sandbox = new MainbrellaSandbox({ catalogId: 'node' });
const filesystem = new MainbrellaFilesystem({ sandbox });
const workspace = new Workspace({ sandbox, filesystem });默认设置将两种操作都放在 /workspace 下。文件系统 API 接受相对于该根目录的路径;命令参数使用 Linux 内部的路径。这一区别小到容易被忽视,又大到足以毁掉报告。
| 从文件工具 | 从命令 |
|---|---|
input.json 或 /input.json | /workspace/input.json |
report.txt 或 /report.txt | /workspace/report.txt |
以 /workspace 作为命令的工作目录,node report.cjs 会看到写入的文件为 report.cjs。如果你选择另一个文件系统 basePath,请将沙箱的 workingDirectory 设置为与之匹配,并在那里运行命令之前初始化可写文件系统。
这个提交中有一个粗糙之处:stat('report.txt') 返回 /workspace/report.txt,但将该值交回给 readFile() 时却会在 /workspace/workspace/report.txt 下查找。元数据所公布的路径应当能被文件工具使用。目前,请将文件系统调用保持为上面所示的工作区相对形式。
连接可以在作业之前离开
假设报告程序已经启动并打印了它的第一条进度消息。然后输出连接断开了。如果它已经追加了一行、扣了一笔账或发送了一条消息,那么重新启动该程序将是一种糟糕的恢复策略。有用的恢复是重新获得对现有作业的访问。
进程管理器启动一个受管理的 Mainbrella 执行并保留其执行 ID。输出事件带有递增的序列号。当流失败时,适配器可以从 SDK 最后收到的游标处重新打开它,而原始命令继续运行。它会对符合条件的流失败最多重试三次;它绝不会提交第二条命令来修复失败的输出连接。
在 Mastra 中,executeCommand() 使用此进程管理器并等待完成。后台进程能更快地给你一个句柄,因此你可以检查输出、取消它或发送 stdin。它的 pid 是 Mainbrella 执行 UUID,而不是操作系统 PID。输入管道也需要一个结束:例如,在输入 cat 之后,调用 closeStdin() 以便它能完成。
结果包含 stdout、stderr、退出码和成功标志。超时映射为退出码 124;取消映射为 137。中断和输出限制终止也会报告失败。一条愉快的进度消息是有用的证据,表明程序取得了一些进展。但这不足以证明报告已完成。
重新连接输出
已知执行 ID、最后收到的游标。从该作业读取接下来的事件。
重复副作用
未知命令或 stdin 结果。再次发送可能会执行两次工作;适配器会暴露该错误以供协调。
托管作业带来一个重要的限制:Mainbrella 每个容器从准入起保留 32 条执行记录,持续一小时。即使是一次简短、普通的 executeCommand() 调用,此适配器也会使用托管作业。完成命令会释放 Mastra 的本地句柄,但保留服务器端的记录。在任何记录过期之前运行 32 条命令,第 33 条命令会被拒绝,并返回 execution_history_limit。
这对迭代式编码代理来说是一个真实的约束,它可能仅为了熟悉一个仓库就花掉 32 条命令。此版本需要一种考虑保留历史的执行策略,才能适合长时间的编辑会话。为了清空历史而悄悄停止机器,会丢弃我们试图保留的文件。
记住是哪台机器完成了工作
有两个身份需要理清。Mastra 的 sandbox.id 命名你应用程序中的适配器。Mainbrella 的远程身份同时包含 id 和 createdAt。一个可复用的容器槽位之后可以承载另一台机器;创建时间戳将本次运行与其替代者区分开来。
沙箱在启动后保存该对,并将其用于文件、作业、重新连接和清理。附加到已停止或被替换的代际会失败。它不会悄悄地把代理放入一个空的替代者中,并假装其报告仍然存在。
创建需要不同的恢复身份。如果你需要在应用程序重启后恢复,请在启动前持久化一个 creationKey 及其创建选项。使用相同键和选项的重试可以在 24 小时窗口内协调模糊的创建。丢失回复的文章探讨了为什么这种区分很重要。记住一次创建尝试和记住一台运行中的机器解决的是生命周期中的不同时刻。
一旦多个代理使用一个工作区,共享就成为一个应用程序决策。静态工作区将这些用户指向同一台机器。HTTP 文件系统的写入队列对通过该文件系统实例进行的变更进行排序;它无法对 shell 进程的写入或第二个适配器的更改进行排序。其只读标志限制文件工具变更,而启用的 shell 仍然可以写入。按用户隔离和权限需要在两条路径上保持一致。
在添加模型之前先尝试一个小报告
最短的有用检查会同时练习两条路径:通过文件系统写入输入,在 Linux 内处理它,然后通过文件系统读取输出。使用上面的对象,此示例应打印 total=42:
try {
await workspace.init();
await filesystem.writeFile('input.json', '[12, 17, 13]');
await filesystem.writeFile('report.cjs', `
const fs = require('node:fs');
const values = JSON.parse(fs.readFileSync('input.json', 'utf8'));
const total = values.reduce((sum, value) => sum + value, 0);
fs.writeFileSync('report.txt', 'total=' + total + '\\n');
`);
const result = await sandbox.executeCommand('node', ['report.cjs']);
if (!result.success) throw new Error(result.stderr || 'Report failed');
console.log(await filesystem.readFile('report.txt', { encoding: 'utf8' }));
} finally {
await workspace.destroy();
}从所链接 fork 的检出目录中,运行 pnpm --filter @mastra/mainbrella build。将两个代码块一起保存为 workspaces/mainbrella/report-demo.mjs,或下载完整示例,然后在配置了 MAINBRELLA_API_KEY 的情况下,从该目录运行 node report-demo.mjs。该账户需要有可用的计算配额。
对于本地 Mainbrella 服务,设置 MAINBRELLA_API_URL=http://localhost:8787 并使用该服务的凭据。该密钥属于发起 API 调用的应用程序;它不需要进入访客的命令环境。
这是一次说明性的管道检查,不是一次录制的模型运行。在审查该提交时,我们针对伪造的 HTTP API 运行了它的 38 个单元测试,外加类型检查和 lint。我们还复现了元数据路径问题,并在伪造传输中强制执行后端准入限制的情况下,演练了保留历史失败。本文未运行可选的实时集成测试套件。
该示例在清理之前读取报告,因为 workspace.destroy() 会停止这个静态配置的沙箱并丢弃其未保存的文件。保存对话并不会保存该磁盘。单个 HTTP 文件传输限制为 1 MiB;托管输出限制为 1 MiB,并且作业在机器租约内最多有 15 分钟超时。如果完成的产物需要保留,请将其导出,或通过 sandbox.mainbrella 显式保存工作区。
一旦文件–命令–文件的循环正常工作,就将工作区交给你的 Mastra agent。这样模型就能接触到实际输入,你也可以评估任何沙箱适配器都无法提供的那部分:报告是否正确。
来源:Hacker News · mainbrella.com