# Sandbox

**Added in:** `@mastra/core@1.1.0`

Sandbox providers give agents the ability to execute shell commands. When you configure a sandbox on a workspace, agents can run commands as part of their tasks.

A sandbox provider executes commands in a controlled environment:

- **Command execution**: Run shell commands with arguments
- **Background processes**: Spawn long-running processes like dev servers and watchers
- **Working directory**: Commands run from a specific directory
- **Environment variables**: Control what variables are available
- **Timeouts**: Prevent long-running commands from hanging
- **Isolation**: Optional OS-level sandboxing for security

## Supported providers

- [`LocalSandbox`](https://mastra.ai/reference/workspace/local-sandbox): Executes commands on the local machine
- [`AgentCoreRuntimeSandbox`](https://mastra.ai/reference/workspace/agentcore-runtime-sandbox): Executes commands in AWS Bedrock AgentCore Runtime sessions
- [`BlaxelSandbox`](https://mastra.ai/reference/workspace/blaxel-sandbox): Executes commands in isolated Blaxel cloud sandboxes
- [`DaytonaSandbox`](https://mastra.ai/reference/workspace/daytona-sandbox): Executes commands in isolated Daytona cloud sandboxes
- [`E2BSandbox`](https://mastra.ai/reference/workspace/e2b-sandbox): Executes commands in isolated E2B cloud sandboxes
- [`ModalSandbox`](https://mastra.ai/reference/workspace/modal-sandbox): Executes commands in isolated Modal cloud sandboxes

## Basic usage

Create a workspace with a sandbox and assign it to an agent. The agent can then execute shell commands:

```typescript
import { Agent } from '@mastra/core/agent'
import { Workspace, LocalFilesystem, LocalSandbox } from '@mastra/core/workspace'

const workspace = new Workspace({
  filesystem: new LocalFilesystem({
    basePath: './workspace',
  }),
  sandbox: new LocalSandbox({
    workingDirectory: './workspace',
  }),
})

const agent = new Agent({
  id: 'dev-agent',
  model: 'openai/gpt-5.5',
  instructions: 'You are a helpful development assistant.',
  workspace,
})

// The agent now has the execute_command tool available
const response = await agent.generate('Run `ls -la` in the workspace directory')
```

See [`LocalSandbox` reference](https://mastra.ai/reference/workspace/local-sandbox) for configuration options including environment isolation and native OS sandboxing.

## Dynamic sandbox

The `sandbox` option accepts a resolver function instead of a static instance. The resolver receives `requestContext` and returns a sandbox per request, allowing a single workspace to serve different sandboxes based on the caller's identity, role, or tenant.

```typescript
import { Agent } from '@mastra/core/agent'
import { Workspace, LocalSandbox } from '@mastra/core/workspace'

const workspace = new Workspace({
  sandbox: ({ requestContext }) => {
    const userId = requestContext.get('user-id') as string
    return new LocalSandbox({
      workingDirectory: `/workspaces/${userId}`,
    })
  },
})

const agent = new Agent({
  id: 'multi-tenant-agent',
  model: 'your-provider/your-model',
  workspace,
})
```

Each request resolves its own sandbox at tool execution time:

```typescript
import { RequestContext } from '@mastra/core/request-context'

// User Alice — commands run in /workspaces/alice
const aliceCtx = new RequestContext([['user-id', 'alice']])
await agent.generate('List files in cwd', { requestContext: aliceCtx })

// User Bob — commands run in /workspaces/bob
const bobCtx = new RequestContext([['user-id', 'bob']])
await agent.generate('List files in cwd', { requestContext: bobCtx })
```

By default, workspace instructions describe the dynamic sandbox with stable placeholder text. See [Workspace instructions](#workspace-instructions) to include concrete per-request details.

The resolver can also be asynchronous — for example to look up tenant configuration from a database:

```typescript
const workspace = new Workspace({
  sandbox: async ({ requestContext }) => {
    const tenant = await db.getTenant(requestContext.get('tenant-id'))
    return new LocalSandbox({ workingDirectory: tenant.workspacePath })
  },
})
```

### Lifecycle ownership

When the sandbox is a static instance, `workspace.init()` calls its `start()` method and `workspace.destroy()` calls its `destroy()` method. With a resolver, the workspace has no instance to manage at construction time — the caller owns the returned sandbox's lifecycle.

The resolver must return a sandbox that is ready to use, either already started or able to handle calls without explicit startup. The caller also owns cleanup timing for returned sandboxes. Cleanup might happen per request, per tenant, per user, or as part of a long-lived sandbox pool; `workspace.destroy()` does not destroy resolver-returned sandboxes.

> **Note:** `sandbox` resolvers are incompatible with `mounts` and `lsp: true`. Both require a concrete sandbox instance at construction time, so combining them with a resolver throws an `INVALID_CONFIG` error (for `mounts`) or disables LSP with a warning (for `lsp: true`).

### Tool registration

With a static sandbox, the workspace inspects the instance to decide which tools to register. With a resolver, the workspace assumes full capabilities and registers `execute_command` (with `background` support), `get_process_output`, and `kill_process`. If the resolved sandbox does not implement a capability, the runtime throws a clear `SandboxFeatureNotSupportedError`.

### Background process continuity

Background processes can outlive a single tool call, so `get_process_output` and `kill_process` must reach the same sandbox that started the process. By default, a resolved sandbox is cached per request. For continuity across follow-up requests, such as a later conversation turn, set `sandboxCacheKey` to a stable identifier. The resolved sandbox is then cached by that key instead of by request:

```typescript
const workspace = new Workspace({
  sandbox: ({ requestContext }) => resolveSandbox(requestContext),
  sandboxCacheKey: ({ requestContext }) => requestContext.get('thread-id') as string,
})
```

Without a `sandboxCacheKey`, the resolver must return the same sandbox itself for follow-up calls that share a tenant, user, or session.

When a cached sandbox is no longer needed, destroy the sandbox in your own lifecycle code and call `workspace.clearSandboxCache(cacheKey)` to drop the workspace cache entry. Call `workspace.clearSandboxCache()` to clear all keyed sandbox entries.

### Workspace instructions

Workspace instructions describe the environment in the agent's system message. With a sandbox resolver, the workspace does not call the resolver to build these instructions. It emits stable placeholder text, so constructing the prompt never provisions a caller-owned sandbox and the system message stays consistent across requests, which keeps prompt caching effective.

To include concrete per-request sandbox details, set `instructions.dynamicSandbox` to `'resolve'`:

```typescript
const workspace = new Workspace({
  sandbox: ({ requestContext }) => resolveSandbox(requestContext),
  instructions: { dynamicSandbox: 'resolve' },
})
```

`'resolve'` calls the resolver on every request, which may provision the sandbox and makes the system message request-specific. Pass a function instead to return custom text from `requestContext` without resolving the sandbox:

```typescript
const workspace = new Workspace({
  sandbox: ({ requestContext }) => resolveSandbox(requestContext),
  instructions: {
    dynamicSandbox: ({ requestContext }) =>
      `Sandbox scoped to tenant ${requestContext.get('tenant-id')}.`,
  },
})
```

## Agent tools

When you configure a sandbox on a workspace, agents receive the `execute_command` tool for running shell commands.

If your sandbox provider supports running processes in the background, the `execute_command` tool also accepts `background: true` for starting long-running processes, and two additional tools are registered:

| Tool                 | Description                                                                                                                                |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `execute_command`    | Run a shell command. Returns stdout, stderr, and exit code. Supports `background: true` to spawn a long-running process and return a PID.  |
| `get_process_output` | Get stdout, stderr, and status of a background process by PID. Supports `tail` to limit output lines and `wait: true` to block until exit. |
| `kill_process`       | Stop a background process by PID. Returns recent output.                                                                                   |

These tools are registered automatically. See [Workspace class reference](https://mastra.ai/reference/workspace/workspace-class) for the full tool name list.

## Background process callbacks

When agents start background processes through the `execute_command` tool, you can receive lifecycle callbacks for stdout, stderr, and process exit. Configure these through the `backgroundProcesses` option on the `execute_command` tool:

```typescript
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}`),
      },
    },
  },
})
```

These callbacks fire for all background processes started by the agent through the `execute_command` tool.

### Abort signal

By default, background processes inherit the agent's abort signal and are killed when the agent disconnects. Control this behavior with the `abortSignal` option:

- **`undefined`** (default): Uses the agent's abort signal
- **`AbortSignal`**: Uses a custom signal
- **`null` or `false`**: Disables abort — processes persist after agent shutdown

```typescript
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: {
        abortSignal: null, // Processes survive agent disconnection
      },
    },
  },
})
```

Use `null` or `false` for cloud sandboxes (for example, E2B, Daytona, or Modal) where processes should outlive the agent.

> **Note:** For the full `SandboxProcessManager` API (spawning processes programmatically, reading output, sending stdin), see the [`SandboxProcessManager` reference](https://mastra.ai/reference/workspace/process-manager).

## Related

- [`SandboxProcessManager` reference](https://mastra.ai/reference/workspace/process-manager)
- [`AgentCoreRuntimeSandbox` reference](https://mastra.ai/reference/workspace/agentcore-runtime-sandbox)
- [`DaytonaSandbox` reference](https://mastra.ai/reference/workspace/daytona-sandbox)
- [`E2BSandbox` reference](https://mastra.ai/reference/workspace/e2b-sandbox)
- [`LocalSandbox` reference](https://mastra.ai/reference/workspace/local-sandbox)
- [`ModalSandbox` reference](https://mastra.ai/reference/workspace/modal-sandbox)
- [Workspace overview](https://mastra.ai/docs/workspace/overview)
- [Filesystem](https://mastra.ai/docs/workspace/filesystem)