Skip to main content

SandboxProcessManager

Added in: @mastra/core@1.7.0

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

LocalSandbox, E2BSandbox, and DaytonaSandbox 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
Direct link to Usage example

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

src/mastra/index.ts
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
Direct link to Methods

spawn(command, options?)
Direct link to spawncommand-options

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

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

Parameters:

command:

string
The command to run. Interpreted by the shell.

options?:

SpawnProcessOptions
Optional settings for the spawned process.
SpawnProcessOptions

timeout?:

number
Timeout in milliseconds. Kills the process if exceeded.

env?:

NodeJS.ProcessEnv
Environment variables for the process.

cwd?:

string
Working directory for the process.

onStdout?:

(data: string) => void
Callback for stdout chunks. Called as data arrives.

onStderr?:

(data: string) => void
Callback for stderr chunks. Called as data arrives.

abortSignal?:

AbortSignal
Signal to abort the process. When aborted, the process is killed.

Returns: Promise<ProcessHandle>

list()
Direct link to list

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

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

Returns: Promise<ProcessInfo[]>

get(pid)
Direct link to getpid

Get a handle to a process by PID. Returns undefined if the process is not found or has already been dismissed.

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

Returns: Promise<ProcessHandle | undefined>

kill(pid)
Direct link to killpid

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

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

Returns: Promise<boolean>

ProcessInfo
Direct link to ProcessInfo

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

pid:

number
Process ID.

command?:

string
The command that was executed.

running:

boolean
Whether the process is still running.

exitCode?:

number
Exit code if the process has finished.

ProcessHandle
Direct link to 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
Direct link to Usage example

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
Direct link to Properties

pid:

number
Process ID.

stdout:

string
Accumulated stdout output so far.

stderr:

string
Accumulated stderr output so far.

exitCode:

number | undefined
Exit code. undefined while the process is still running.

command:

string | undefined
The command that was spawned. Set automatically by the process manager.

reader:

Readable
Readable stream of stdout. Useful for protocols like LSP or JSON-RPC that communicate over stdio.

writer:

Writable
Writable stream to stdin. Useful for protocols like LSP or JSON-RPC that communicate over stdio.

Methods
Direct link to Methods

wait(options?)
Direct link to waitoptions

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.

// 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:

options?:

WaitOptions
Optional settings for waiting.
WaitOptions

onStdout?:

(data: string) => void
Callback for stdout chunks while waiting.

onStderr?:

(data: string) => void
Callback for stderr chunks while waiting.

Returns: Promise<CommandResult>

The CommandResult object contains:

success:

boolean
true if exit code is 0.

exitCode:

number
Numeric exit code.

stdout:

string
Full stdout output.

stderr:

string
Full stderr output.

executionTimeMs:

number
Execution time in milliseconds.

timedOut?:

boolean
true if the process was killed due to timeout.

killed?:

boolean
true if the process was killed by a signal.

kill()
Direct link to kill

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

const killed = await handle.kill()

Returns: Promise<boolean>

sendStdin(data)
Direct link to sendstdindata

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

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

Returns: Promise<void>

Stream interop
Direct link to Stream interop

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

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
Direct link to 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.

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:

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.