# 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.

[`BlaxelSandbox`](https://mastra.ai/reference/workspace/blaxel-sandbox), [`DaytonaSandbox`](https://mastra.ai/reference/workspace/daytona-sandbox), [`E2BSandbox`](https://mastra.ai/reference/workspace/e2b-sandbox), and [`LocalSandbox`](https://mastra.ai/reference/workspace/local-sandbox) 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:**

**command** (`string`): The command to run. Interpreted by the shell.

**options** (`SpawnProcessOptions`): Optional settings for the spawned process.

**options.timeout** (`number`): Timeout in milliseconds. Kills the process if exceeded.

**options.env** (`NodeJS.ProcessEnv`): Environment variables for the process.

**options.cwd** (`string`): Working directory for the process.

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

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

**options.abortSignal** (`AbortSignal`): Signal to abort the process. When aborted, the process is killed.

**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()`.

**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`

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

**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

#### `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:**

**options** (`WaitOptions`): Optional settings for waiting.

**options.onStdout** (`(data: string) => void`): Callback for stdout chunks while waiting.

**options.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()`

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.

## Related

- [Sandbox](https://mastra.ai/docs/workspace/sandbox)
- [WorkspaceSandbox interface](https://mastra.ai/reference/workspace/sandbox)
- [LocalSandbox reference](https://mastra.ai/reference/workspace/local-sandbox)
- [E2BSandbox reference](https://mastra.ai/reference/workspace/e2b-sandbox)
- [DaytonaSandbox reference](https://mastra.ai/reference/workspace/daytona-sandbox)