跳到主要内容
非官方简体中文翻译,内容版权归 Mastra 所有,遵循 Apache-2.0 许可。

Sandboxes 和文件系统

Sandbox 为你的 agent 提供一个隔离环境,在其中可以运行命令、执行代码、安装依赖并管理进程。这让 agents 能够执行那些直接在应用中运行会有风险、消耗大量资源或不切实际的工作。

Sandboxes 通常是临时的,因此其中创建的文件可能在环境停止时消失。文件系统为 agent 提供一个可读取、写入和搜索文件的位置,其生命周期可以长于 sandbox。你可以使用它在多次运行之间保留输出、用现有文件初始化新 sandbox,或为 agent 提供在工作时可搜索的文档。文件系统也可以在没有 sandbox 的情况下工作,例如 agent 只需要知识库或访问 Google Drive 等服务中的文件时。一个真实示例是 Mastra Factory,它使用 sandboxes 为编程 agent 会话提供仓库检出、依赖和命令环境。

何时使用 sandboxes
何时使用 sandboxes的直接链接

当 agent 需要执行以下操作时,使用 sandbox:

  • 在单独环境中克隆仓库并运行 shell、Git、构建或测试工作流。
  • 使用专用库处理 PDF、演示文稿或其他文件,并生成新的产物。
  • 在具有隔离文件和进程的单独环境中运行长时间或并行工作。

如果 agent 只需要读取、写入或搜索文件,请配置直接文件系统访问。

快速开始
快速开始的直接链接

为 agent 提供一个本地 sandbox:

src/mastra/agents/coding-agent.ts
import { Agent } from '@mastra/core/agent'
import { LocalSandbox, Workspace } from '@mastra/core/workspace'

export const codingAgent = new Agent({
id: 'coding-agent',
name: 'Coding agent',
instructions: 'Use the sandbox to complete coding tasks.',
model: 'openai/gpt-5.6-sol',
workspace: new Workspace({
sandbox: new LocalSandbox({
workingDirectory: './workspace',
}),
}),
})

await codingAgent.generate('List the files in the sandbox directory')
注意

LocalSandbox 默认在应用宿主上运行命令,并且没有隔离、不安全。请启用原生隔离,或在运行不受信任代码时使用远程或容器 sandbox。

静态 sandbox 会在使用该 agent 的每个请求和 memory thread 之间共享。当每个用户或 thread 需要单独环境时,使用解析器。关于共享和清理细节,请参阅生命周期与持久化。

使用 sandbox
使用 sandbox的直接链接

Agents 会获得 sandbox 后端所支持能力对应的工具:

工具作用
execute_command运行 shell 命令并返回 stdout、stderr 和退出码。当后端可以启动长时间运行的进程时支持 background: true。
get_process_output获取后台进程的 stdout、stderr 和状态。支持 tail 限制输出,以及 wait: true 等待进程退出。对于静态 sandbox,仅当后端支持后台进程时才会添加此工具和 kill_process。按请求解析 sandbox 的 workspaces 始终包含这两个工具,因为 sandbox 解析之前无法知道其能力。
kill_process停止后台进程并返回其最近输出。

配置的能力决定哪些工具可用。工具配置随后可以禁用、重命名这些工具,或为其添加策略:

import { LocalSandbox, Workspace, WORKSPACE_TOOLS } from '@mastra/core/workspace'

const workspace = new Workspace({
sandbox: new LocalSandbox({ workingDirectory: './workspace' }),
tools: {
requireApproval: true,
[WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND]: {
enabled: false,
},
},
})

在一个工具上设置 { enabled: false } 可将其移除,或设置顶层 enabled: false 默认禁用生成的工具。单个工具的 { enabled: true } 会覆盖该全局设置。顶层 requireApproval 策略会应用于每个生成的工具,除非单个工具条目覆盖它。

关于所有生成的工具,请参阅 sandbox 工具参考;关于审批、输出限制和 hooks,请参阅工具配置参考。

编写的 runtime 函数(包括工具和 workflow 步骤)可以从其执行上下文获取实时 sandbox。使用它执行命令、安装依赖、处理文件或启动长时间运行的进程。

src/mastra/tools/start-server.ts
import { createTool } from '@mastra/core/tools'
import { z } from 'zod'

export const startServerTool = createTool({
id: 'start-server',
description: 'Starts a server in the sandbox',
inputSchema: z.object({
command: z.string(),
}),
execute: async ({ command }, ctx) => {
if (!ctx.workspace || !ctx.requestContext) {
throw new Error('This tool requires a workspace execution context')
}

const sandbox = await ctx.workspace.resolveSandbox({
requestContext: ctx.requestContext,
})

if (!sandbox?.processes) {
throw new Error('The configured sandbox does not support background processes')
}

const server = await sandbox.processes.spawn(command)
return { pid: server.pid }
},
})

Sandboxes
Sandboxes的直接链接

LocalSandbox
localsandbox的直接链接

LocalSandbox 在与 Mastra 应用相同的机器上执行命令。默认情况下,命令以应用进程的权限直接在宿主上运行。

const sandbox = new LocalSandbox({
workingDirectory: './workspace',
isolation: 'seatbelt', // Use 'bwrap' on Linux
nativeSandbox: {
allowNetwork: false,
readOnlyPaths: ['./reference-data'],
},
})

启用原生隔离可在操作系统层面限制文件系统和网络访问。在 macOS 上,原生隔离使用 Seatbelt(sandbox-exec)。在 Linux 上,它使用 Bubblewrap(bwrap)。使用 LocalSandbox.detectIsolation() 检查当前操作系统上是否有可用的 Seatbelt 或 Bubblewrap。

nativeSandbox 选项控制网络访问、只读或可写路径、工作目录的写入权限以及系统二进制文件。你也可以提供自定义 Seatbelt profile 或 Bubblewrap 参数。完整配置请参阅 LocalSandbox。

远程 sandboxes
远程 sandboxes的直接链接

当命令需要与宿主应用有更强的边界,或工作负载需要扩展到超出应用服务器资源时,使用远程或容器 sandbox。每个 sandbox 都有各自的隔离、持久化、网络和挂载行为:

如果 Mastra 不支持你的 sandbox 提供商,请实现 sandbox 提供商接口以添加支持。

文件系统
文件系统的直接链接

每个 sandbox 都有一个供命令使用的文件系统。LocalSandbox 使用宿主文件系统,而远程 sandboxes 拥有通常为临时的隔离文件系统。对于需要比 sandbox 存活更久的文件,请使用外部存储。

Mastra 文件系统将 agents 和 sandboxes 连接到由提供商支持的存储,例如 Amazon S3、Google Cloud Storage 或 Google Drive。配置文件系统会为 agent 提供读取、写入和搜索文件的工具。当远程 sandbox 和提供商支持挂载时,Mastra 会通过 Filesystem in Userspace (FUSE) 自动挂载存储。命令使用普通文件路径,而提供商将更改存储在 sandbox 之外。

关于提供商、文件工具和挂载,请参阅文件系统。

Computer use
Computer use的直接链接

支持的桌面 sandboxes 为 agents 提供截图、鼠标和键盘控制。关于支持的 sandboxes 和配置,请参阅 Computer。

每个用户或 thread 的 sandboxes
每个用户或 thread 的 sandboxes的直接链接

当每个用户、租户或 thread 需要单独的 sandbox 时,使用解析器。将 sandboxCacheKey 设置为拥有该 sandbox 的身份,以便后续请求复用同一实时环境。

此示例为每个用户创建并缓存一个 Daytona sandbox:

src/mastra/workspaces.ts
import { Workspace } from '@mastra/core/workspace'
import { DaytonaSandbox } from '@mastra/daytona'

const workspace = new Workspace({
sandbox: async ({ requestContext }) => {
const userId = requestContext.get('user-id') as string
const sandbox = new DaytonaSandbox({ id: `user-${userId}` })
await sandbox.start()
return sandbox
},
sandboxCacheKey: ({ requestContext }) => requestContext.get('user-id') as string,
})

用户的第一个请求会创建 sandbox。具有相同用户 ID 的后续请求会复用它。

对于带持久存储的 sandbox,创建一个文件系统并在解析器内部挂载它。此示例为每个 memory thread 创建一个 Daytona sandbox 和一个 S3 存储前缀,然后将存储挂载到 /workspace:

src/mastra/workspaces.ts
import { MASTRA_THREAD_ID_KEY, type RequestContext } from '@mastra/core/request-context'
import { Workspace } from '@mastra/core/workspace'
import { DaytonaSandbox } from '@mastra/daytona'
import { S3Filesystem } from '@mastra/s3'

const getThreadId = (requestContext: RequestContext) =>
requestContext.get(MASTRA_THREAD_ID_KEY) as string

const createThreadFilesystem = (threadId: string) =>
new S3Filesystem({
bucket: process.env.S3_BUCKET!,
region: process.env.S3_REGION!,
prefix: `threads/${threadId}`,
})

const workspace = new Workspace({
filesystem: ({ requestContext }) => createThreadFilesystem(getThreadId(requestContext)),
sandbox: async ({ requestContext }) => {
const threadId = getThreadId(requestContext)
const sandbox = new DaytonaSandbox({
id: `thread-${threadId}`,
language: 'typescript',
})

await sandbox.start()
await sandbox.mount(createThreadFilesystem(threadId), '/workspace')
return sandbox
},
sandboxCacheKey: ({ requestContext }) => getThreadId(requestContext),
})

thread 中的第一个请求会运行解析器并启动 sandbox。具有相同 thread ID 的后续请求会复用缓存的 sandbox。不同的 thread ID 会创建不同的 sandbox 和存储前缀。

此示例在解析器内部挂载文件系统,因为静态 mounts 不能与 sandbox 解析器组合使用。生成的工具会自动从 request context 解析文件系统和 sandbox。

注意

解析器返回的 sandboxes 由你的应用拥有。当用户、thread 或会话结束时,请销毁它们并调用 workspace.clearSandboxCache(cacheKey)。workspace.destroy() 不会销毁解析器返回的 sandboxes。

工具可用性
工具可用性的直接链接

对于静态 sandbox,Mastra 知道后端支持哪些能力,并且只向 agent 提供相应的工具。应用代码在使用可选能力之前也应检查其是否可用。

由于解析器支持的 sandbox在请求时才能确定,Mastra 最初会暴露所有 sandbox 工具。调用解析后后端不支持的工具会失败,并抛出 SandboxFeatureNotSupportedError。

后台进程
后台进程的直接链接

当 agents 通过 execute_command 启动后台进程时,你可以接收 stdout、stderr 和进程退出的生命周期回调。通过其 WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND 条目上的 backgroundProcesses 选项配置这些回调:

src/mastra/workspaces.ts
import { Workspace, LocalSandbox, WORKSPACE_TOOLS } from '@mastra/core/workspace'

const workspace = new Workspace({
sandbox: new LocalSandbox({ workingDirectory: './workspace' }),
tools: {
[WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND]: {
backgroundProcesses: {
onStdout: (data, { pid }) => console.log(`[${pid}] ${data}`),
onStderr: (data, { pid }) => console.error(`[${pid}] ${data}`),
onExit: ({ pid, exitCode }) => console.log(`Process ${pid} exited: ${exitCode}`),
},
},
},
})

这些回调会为 agent 通过 execute_command 启动的所有后台进程触发。

默认情况下,后台进程继承 agent 的 abort signal,并在 agent 断开连接时停止。将 abortSignal 设置为自定义 signal,或在进程应在请求结束后继续运行时使用 null 或 false。

关于完整的 SandboxProcessManager API(包括以编程方式启动进程、输出和标准输入),请参阅 SandboxProcessManager 参考。

生命周期与持久化
生命周期与持久化的直接链接

谁共享 sandbox 取决于你配置它的位置以及是否使用解析器:

配置谁共享 sandbox
Mastra 级配置从 Mastra 实例继承配置的 agents 使用同一 sandbox。
Agent 级配置该 agent 实例处理的每个请求使用同一 sandbox。
Resource 作用域解析器解析器为每个 resource ID 缓存一个 sandbox。
Thread 作用域解析器Memory thread 在该 thread 的多次请求之间保留其 sandbox。

静态 sandbox 不会自动限定到当前 resource 或 memory thread。要获得 resource 或 thread 作用域,请使用解析器并将 sandboxCacheKey 设置为相应的 ID。请参阅每个用户或 thread 的 sandboxes。

启动
启动的直接链接

静态 sandbox 环境通常会在第一条命令运行时启动,因此大多数应用无需显式初始化它们。仅在希望启动静态 sandbox 并在开始服务请求之前准备其挂载时,才在应用启动期间调用 workspace.init()。当凭据、网络或提供商错误需要在启动期间而不是在第一条命令时暴露时,这很有用。

workspace.init() 不会启动解析器支持的 sandboxes,因为在请求运行之前不会选择任何 sandbox。解析器必须返回一个已启动或可在首次使用时自行启动的 sandbox。

Hooks
Hooks的直接链接

使用 onStart、onStop 和 onDestroy 在生命周期转换期间运行应用代码。Hooks 会接收实时 sandbox 实例:

const sandbox = new LocalSandbox({
workingDirectory: './workspace',
onStart: ({ sandbox }) => console.log(`Started ${sandbox.id}`),
onStop: ({ sandbox }) => console.log(`Stopping ${sandbox.id}`),
onDestroy: ({ sandbox }) => console.log(`Destroying ${sandbox.id}`),
})

onStart 在 sandbox 启动后运行。onStop 和 onDestroy 在相应操作之前运行。

清理
清理的直接链接

当你直接配置 sandbox 而不是使用解析器时,Mastra 拥有它。mastra dev 和生成的 Mastra 服务器会处理关闭信号并自动销毁已注册资源。如果你将 Mastra 嵌入自定义服务器或进程,请从其关闭钩子中调用 mastra.shutdown()。独立使用时,直接调用 workspace.destroy()。

stop() 和 destroy() 的行为由后端决定。例如,停止可能会关闭计算资源但保留可重新启动的环境,而销毁可能会删除环境及其临时文件。在依赖任一行为之前,请检查所选后端。

解析器返回的 sandboxes 由你的应用拥有。workspace.destroy() 和 mastra.shutdown() 会清除缓存的引用,但不会销毁解析出的 sandboxes。应用生命周期代码必须跟踪它们,在其用户、thread 或会话结束时调用 destroy(),然后调用 workspace.clearSandboxCache(cacheKey)。这可以防止未使用的计算资源继续运行,并防止后续请求复用已过期的 sandbox。

持久化
持久化的直接链接

持久化因后端而异。文件与进程不保证在 sandbox 停止或应用重启后继续存在。空闲超时也可能丢弃它们。某些后端通过 sandbox ID 重新连接,或保留快照和卷。其他后端会创建全新环境。

每个 sandbox 都有一个 id,但该 ID 不是跨后端的持久化保证。在依赖重新连接或持久状态之前,请阅读所选后端的参考文档。

网络与凭据安全
网络与凭据安全的直接链接

Mastra 没有定义一个适用于所有 sandbox 后端的网络策略。默认值和支持的控制各不相同,因此在运行不受信任代码之前,请检查所选后端。对于敏感或生产工作负载,优先阻止出站访问,或仅允许任务所需的目标。在创建 sandbox 时应用该策略,使其在命令运行之前生效。

如果 agent 在远程 sandbox 内启动 Web 服务器,该服务器最初监听 sandbox 内部的端口。支持端口暴露的后端实现 sandbox.networking.getPortUrl(),返回一个可用于从 sandbox 外部预览或测试服务器的 URL:

const url = await sandbox.networking?.getPortUrl(8000)

根据后端的访问控制,该 URL 可能使服务对其他人可达。这种入站暴露与出站 sandbox 访问是分开的。启用原生隔离的 LocalSandbox 默认阻止网络访问,除非启用了 allowNetwork。没有原生隔离时,本地命令使用宿主网络。

例如,DaytonaSandbox 允许出站访问,除非你配置了限制。当任务只需要特定服务时,设置 networkBlockAll 并配置域允许列表:

import { DaytonaSandbox } from '@mastra/daytona'

const sandbox = new DaytonaSandbox({
networkBlockAll: true,
domainAllowList: 'registry.npmjs.org,api.github.com',
})

Daytona 在创建 sandbox 时应用这些设置。它还通过 networkAllowList 支持 CIDR 规则。完整配置详情请参阅 Daytona 集成页面。

传递给 sandbox 或命令的环境变量对其中运行的代码可用。Mastra 不提供后端中立的凭据代理来将 secrets 保持在 sandbox 之外,同时授权其请求。只向命令提供它所需的凭据,并限定于该任务。请遵循所选后端的 secret 管理指南。

例如,将只读 token 传递给需要它的命令,而不是为 sandbox 中的每条命令都配置它:

await sandbox.executeCommand('node', ['scripts/download-reports.js'], {
env: {
REPORTS_READ_TOKEN: process.env.REPORTS_READ_TOKEN,
},
})