Introducing Sandbox Lifecycle Controls in Mastra

Control, status, hooks, persistence, and background processes.

Paul ScanlonPaul Scanlon·

Sep 3, 2026

·

4 min read

Agents use sandboxes for reading and writing files, running shell commands, spawning background processes, or operating a real computer. Sandbox lifecycles are typically managed by agents: they start them up on the first tool call, keep them running across requests, and shut them down when they're done.

Now Mastra's APIs give you fine-grained control over the sandbox lifecycle. You can create, mount a filesystem, snapshot, pause, re-start, and destroy entirely. You can also define lifecycle hooks like onStop to trigger other actions, or inspect or kill specific processes on the sandbox.

Lifecycle control

The main Mastra instance is the parent of every registered workspace and handles process-level shutdown:

await mastra.shutdown();
  • mastra.shutdown(): Calls stop() on every registered workspace.

Workspaces are parents to child sandboxes and have three methods of their own:

await workspace.init();
await workspace.stop();
await workspace.destroy();
  • workspace.init(): Pre-starts the workspace and any child sandboxes.
  • workspace.stop(): Suspends live resources without destroying them.
  • workspace.destroy(): Closes all workspace-owned resources and clears cached sandbox references.

Clear the workspace cache so the next build is fresh:

workspace.clearSandboxCache("cache-123");
  • workspace.clearSandboxCache(cacheKey): Synchronous return to clear a cached sandbox.

Sandboxes can also be controlled individually:

await sandbox.start();
await sandbox.stop();
await sandbox.destroy();
  • sandbox.start(): Starts a sandbox, or reconnects to an existing one.
  • sandbox.stop(): Suspends a sandbox without destroying it.
  • sandbox.destroy(): Destroys a sandbox and cleans up its provider resources.

Lifecycle status

Each sandbox has a status with resources used:

await sandbox.getInfo();
  • status: One of starting, running, stopped, or error.
  • resources: Current memory (memoryMB) and CPU (cpuPercent) usage from the sandbox provider.

Lifecycle hooks

Hooks are passed as constructor options:

import { E2BSandbox } from "@mastra/e2b";
 
const sandbox = new E2BSandbox({
  id: "demo-sandbox",
  apiKey: process.env.E2B_API_KEY,
  onStart: ({ sandbox, outcome }) => {
    console.log(`Started ${sandbox.id} ${outcome}`);
  },
  onStop: ({ sandbox }) => {},
  onDestroy: ({ sandbox }) => {}
});
  • onStart: Fires when the sandbox starts. Receives:
    • sandbox: The sandbox instance.
    • outcome: One of created or connected.
  • onStop: Fires when the sandbox stops. Receives:
    • sandbox: The sandbox instance.
  • onDestroy: Fires when the sandbox is destroyed. Receives:
    • sandbox: The sandbox instance.

Persistence

Persistence is provider-specific — some reconnect by sandbox id, some preserve snapshots and volumes, others create a fresh environment every time. Sandboxes expose three ways to persist state:

Save the sandbox's current state where the provider supports it:

await sandbox.snapshot();
  • sandbox.snapshot(): Persists the current sandbox state.

Set env vars that survive provider pause and resume:

sandbox.setEnv(() => {
  return { API_KEY: process.env.API_KEY };
});
  • sandbox.setEnv(): Sets env vars for every command routed through the sandbox. Synchronous return.

Mount an external filesystem to persist files:

await sandbox.mount(new S3Filesystem({ bucket: "..." }), "/workspace");
  • sandbox.mount(filesystem, path): Mounts a filesystem at a sandbox directory.

Background processes

Background processes running in the sandbox have their own lifecycle.

The sandbox exposes methods to spawn, list, and kill processes:

const handle = await sandbox.processes.spawn("node server.js", {
  env: { PORT: "3000" }
});
await sandbox.processes.list();
await sandbox.processes.kill(handle.pid);
  • sandbox.processes.spawn(command, options): Starts a background process. Returns a handle with pid.
  • sandbox.processes.list(): Lists running background processes.
  • sandbox.processes.kill(pid): Kills a running process by pid.

Background processes fire three hooks, configured on the workspace's tool config:

import { WORKSPACE_TOOLS, Workspace } from "@mastra/core/workspace";
import { E2BSandbox } from "@mastra/e2b";
 
new Workspace({
  sandbox: new E2BSandbox({ id: "demo-sandbox", apiKey: process.env.E2B_API_KEY }),
  tools: {
    [WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND]: {
      backgroundProcesses: {
        onStdout: (data, { pid }) => {
          console.log(`onStdout ${pid} ${data}`);
        },
        onStderr: (data, { pid }) => {},
        onExit: ({ pid, exitCode }) => {}
      }
    }
  }
});
  • onStdout: Fires on stdout data. Receives:
    • data: The stdout output.
    • pid: The process id.
  • onStderr: Fires on stderr data. Receives:
    • data: The stderr output.
    • pid: The process id.
  • onExit: Fires when the process exits. Receives:
    • pid: The process id.
    • exitCode: The exit code.

Network safety

Network defaults and controls vary by sandbox provider:

Reach a sandbox-internal port from outside:

sandbox.networking?.getPortUrl(8000);
  • sandbox.networking.getPortUrl(port): Returns a URL for the given sandbox port.

Control outbound network access per sandbox:

new LocalSandbox({ workingDirectory: "./workspace", allowNetwork: true });
  • allowNetwork: Enables outbound network access.

For more information and full configuration options, see:

Share:
Paul Scanlon
Paul ScanlonTechnical Product Marketing Manager

Paul Scanlon sits between Developer Education and Product Marketing at Mastra. Previously, he was a Technical Product Marketing Manager at Neon and worked in Developer Relations at Gatsby, where he created educational content and developer experiences.

All articles by Paul Scanlon