Back to Mastra

Reference: SandboxProcessManager | Workspace

docs/src/content/en/reference/workspace/process-manager.mdx

2025-12-1810.8 KB
Original Source

SandboxProcessManager

Added in: @mastra/[email protected]

Abstract base class for managing background processes in sandboxes. Provides methods to spawn processes, list them, get handles by PID, and kill them.

BlaxelSandbox, DaytonaSandbox, E2BSandbox, and LocalSandbox all include built-in process managers. You don't need to instantiate this class directly unless you're building a custom sandbox provider.

Usage example

Access the process manager through the sandbox's processes property:

typescript
import { LocalSandbox } from '@mastra/core/workspace'

const sandbox = new LocalSandbox({ workingDirectory: './workspace' })
await sandbox.start()

// Spawn a background process
const handle = await sandbox.processes.spawn('node server.js', {
  env: { PORT: '3000' },
  onStdout: data => console.log(data),
})

// List all tracked processes
const procs = await sandbox.processes.list()

// Get a handle by PID
const proc = await sandbox.processes.get(handle.pid)

// Kill a process
await sandbox.processes.kill(handle.pid)

Methods

spawn(command, options?)

Spawn a background process. Returns a ProcessHandle immediately without waiting for the process to finish.

typescript
const handle = await sandbox.processes.spawn('npm run dev', {
  cwd: '/app',
  env: { NODE_ENV: 'development' },
  onStdout: data => console.log(data),
})

Parameters:

<PropertiesTable content={[ { name: 'command', type: 'string', description: 'The command to run. Interpreted by the shell.', }, { name: 'options', type: 'SpawnProcessOptions', description: 'Optional settings for the spawned process.', isOptional: true, properties: [ { type: 'SpawnProcessOptions', parameters: [ { name: 'timeout', type: 'number', description: 'Timeout in milliseconds. Kills the process if exceeded.', isOptional: true, }, { name: 'env', type: 'NodeJS.ProcessEnv', description: 'Environment variables for the process.', isOptional: true, }, { name: 'cwd', type: 'string', description: 'Working directory for the process.', isOptional: true, }, { name: 'onStdout', type: '(data: string) => void', description: 'Callback for stdout chunks. Called as data arrives.', isOptional: true, }, { name: 'onStderr', type: '(data: string) => void', description: 'Callback for stderr chunks. Called as data arrives.', isOptional: true, }, { name: 'abortSignal', type: 'AbortSignal', description: 'Signal to abort the process. When aborted, the process is killed.', isOptional: true, }, ], }, ], }, ]} />

Returns: Promise<ProcessHandle>

list()

List all tracked processes. Returns info about each process including PID, running state, and exit code.

typescript
const procs = await sandbox.processes.list()
for (const proc of procs) {
  console.log(proc.pid, proc.running, proc.exitCode)
}

Returns: Promise<ProcessInfo[]>

get(pid)

Get a handle to a process by PID. Returns undefined if the process isn't found or has already been dismissed.

typescript
const handle = await sandbox.processes.get(1234)
if (handle) {
  console.log(handle.stdout)
  await handle.kill()
}

Returns: Promise<ProcessHandle | undefined>

kill(pid)

Kill a process by PID. Waits for the process to terminate before returning. Returns true if the process was killed, false if it wasn't found.

typescript
const killed = await sandbox.processes.kill(handle.pid)

Returns: Promise<boolean>

ProcessInfo

Information about a tracked process, returned by list().

<PropertiesTable content={[ { name: 'pid', type: 'number', description: 'Process ID.', }, { name: 'command', type: 'string', description: 'The command that was executed.', isOptional: true, }, { name: 'running', type: 'boolean', description: 'Whether the process is still running.', }, { name: 'exitCode', type: 'number', description: 'Exit code if the process has finished.', isOptional: true, }, ]} />


ProcessHandle

Handle to a spawned background process. Provides methods to read output, send stdin, wait for completion, and kill the process.

You don't create ProcessHandle instances directly — they're returned by spawn() and get().

Usage example

typescript
const handle = await sandbox.processes.spawn('npm run dev', {
  onStdout: data => console.log(data),
})

// Read accumulated output
console.log(handle.pid)
console.log(handle.stdout)
console.log(handle.stderr)
console.log(handle.exitCode) // undefined while running

// Wait for completion
const result = await handle.wait()

// Send stdin
await handle.sendStdin('input data\n')

// Kill the process
await handle.kill()

Properties

<PropertiesTable content={[ { name: 'pid', type: 'number', description: 'Process ID.', }, { name: 'stdout', type: 'string', description: 'Accumulated stdout output so far.', }, { name: 'stderr', type: 'string', description: 'Accumulated stderr output so far.', }, { name: 'exitCode', type: 'number | undefined', description: 'Exit code. undefined while the process is still running.', }, { name: 'command', type: 'string | undefined', description: 'The command that was spawned. Set automatically by the process manager.', }, { name: 'reader', type: 'Readable', description: 'Readable stream of stdout. Useful for protocols like LSP or JSON-RPC that communicate over stdio.', }, { name: 'writer', type: 'Writable', description: 'Writable stream to stdin. Useful for protocols like LSP or JSON-RPC that communicate over stdio.', }, ]} />

Methods

wait(options?)

Wait for the process to exit and return the result. Optionally pass onStdout/onStderr callbacks to stream output while waiting. Callbacks are automatically removed when wait() resolves.

typescript
// Simple wait
const result = await handle.wait()
console.log(result.success, result.exitCode, result.stdout)

// Wait with streaming
const result = await handle.wait({
  onStdout: data => process.stdout.write(data),
  onStderr: data => process.stderr.write(data),
})

Parameters:

<PropertiesTable content={[ { name: 'options', type: 'WaitOptions', description: 'Optional settings for waiting.', isOptional: true, properties: [ { type: 'WaitOptions', parameters: [ { name: 'onStdout', type: '(data: string) => void', description: 'Callback for stdout chunks while waiting.', isOptional: true, }, { name: 'onStderr', type: '(data: string) => void', description: 'Callback for stderr chunks while waiting.', isOptional: true, }, ], }, ], }, ]} />

Returns: Promise<CommandResult>

The CommandResult object contains:

<PropertiesTable content={[ { name: 'success', type: 'boolean', description: 'true if exit code is 0.', }, { name: 'exitCode', type: 'number', description: 'Numeric exit code.', }, { name: 'stdout', type: 'string', description: 'Full stdout output.', }, { name: 'stderr', type: 'string', description: 'Full stderr output.', }, { name: 'executionTimeMs', type: 'number', description: 'Execution time in milliseconds.', }, { name: 'timedOut', type: 'boolean', description: 'true if the process was killed due to timeout.', isOptional: true, }, { name: 'killed', type: 'boolean', description: 'true if the process was killed by a signal.', isOptional: true, }, ]} />

kill()

Kill the process. Returns true if the process was killed, false if it had already exited.

typescript
const killed = await handle.kill()

Returns: Promise<boolean>

sendStdin(data)

Send data to the process's stdin. Throws if the process has already exited or stdin isn't available.

typescript
await handle.sendStdin('console.log("hello")\n')

Returns: Promise<void>

Stream interop

ProcessHandle exposes reader and writer properties for integration with Node.js stream-based protocols like LSP or JSON-RPC:

typescript
import {
  createMessageConnection,
  StreamMessageReader,
  StreamMessageWriter,
} from 'vscode-jsonrpc/node'

const handle = await sandbox.processes.spawn('typescript-language-server --stdio')

const connection = createMessageConnection(
  new StreamMessageReader(handle.reader),
  new StreamMessageWriter(handle.writer),
)
connection.listen()

Building a custom process manager

To build a process manager for a custom sandbox provider, extend SandboxProcessManager and implement spawn() and list(). The base class automatically wraps your methods with ensureRunning() so the sandbox starts before any process operation.

typescript
import { SandboxProcessManager, ProcessHandle } from '@mastra/core/workspace'
import type { ProcessInfo, SpawnProcessOptions } from '@mastra/core/workspace'

class MyProcessManager extends SandboxProcessManager<MySandbox> {
  async spawn(command: string, options: SpawnProcessOptions = {}): Promise<ProcessHandle> {
    // Your spawn implementation
    const handle = new MyProcessHandle(/* ... */)
    this._tracked.set(handle.pid, handle)
    return handle
  }

  async list(): Promise<ProcessInfo[]> {
    return Array.from(this._tracked.values()).map(handle => ({
      pid: handle.pid,
      running: handle.exitCode === undefined,
      exitCode: handle.exitCode,
    }))
  }
}

Pass the process manager to your sandbox via the processes option in MastraSandbox:

typescript
class MySandbox extends MastraSandbox {
  constructor() {
    super({
      name: 'MySandbox',
      processes: new MyProcessManager(),
    })
  }
}

When a process manager is provided, MastraSandbox automatically creates a default executeCommand implementation that uses spawn() + wait(), so you don't need to implement both.