> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bonya.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Sessions

> Long-lived, named command sessions inside a sandbox

A **session** is a managed, named command running inside a sandbox — distinct from a one-shot [exec](https://docs.bonya.ai/concepts/exec.md) call. Sessions are tracked server-side by name, so you can list them, attach to their output, and kill them independently of the connection that created them.

> [!NOTE]
>
> Sessions are one of three ways to run commands in a sandbox. For a synchronous, buffered command or an unnamed interactive TTY, see [Exec](https://docs.bonya.ai/concepts/exec.md).

## Identity

A session is scoped to a `sandbox_id` and keyed by `session_name`, which must match `^[a-z][a-z0-9-]{0,31}$` — lowercase, starting with a letter, up to 32 characters. There's no separate session ID; the name is the identifier within its sandbox.

## Creating a session

```shell cURL theme={null}
curl -s -X POST "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID/sessions" \
  -H "Authorization: Bearer $BONYA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "dev", "cmd": ["npm", "run", "dev"]}'
```

```shell CLI theme={null}
bonya session create $SANDBOX_ID dev -- npm run dev
```

```ts TypeScript theme={null}
const info = await sandbox.createSession("dev", ["npm", "run", "dev"]);
```

```python Python theme={null}
info = sandbox.create_session("dev", ["npm", "run", "dev"])
```

```go Go theme={null}
info, err := sandbox.CreateSession(ctx, "dev", []string{"npm", "run", "dev"})
```

**MCP:** the `session_create` tool covers this.

The request body extends the common command shape (`cmd`, `env`, `path`) with session-specific fields:

**name** `string` *required*

Session name. Must match `^[a-z][a-z0-9-]{0,31}$`.

**cmd** `string[]` *required*

Argv to run. At least one element.

**env** `object`

Environment variables to set.

**path** `string`

Working directory.

**cols** `integer`

Terminal width, 1–512.

**rows** `integer`

Terminal height, 1–512.

**replace** `boolean` (default: false)

Whether to replace an existing session of the same name.

A successful create returns `201` with `{ "session": { ...SessionInfo } }`.

### Name collisions

> [!WARNING]
>
> Creating a session whose name collides with a **live** (non-terminal) session returns `409 session_exists`. `replace: true` only takes effect against a session that has already reached a terminal state (`EXITED`, `KILLED`, or `FAILED`) — it does not let you forcibly replace a session that's still running or attached. To replace a running session, kill it first, then create.

## Session state

Each `SessionInfo` reports:

**name** `string` *required*

The session name.

**command** `string[]` *required*

The argv the session was started with.

**working\_dir** `string` *required*

The session's working directory.

**status** `string` *required*

One of `UNSPECIFIED`, `STARTING`, `IDLE`, `ATTACHED`, `EXITED`, `KILLED`, `FAILED`.

**attached** `boolean` *required*

Whether a client is currently attached.

**started\_at\_unix\_nanos** `integer` *required*

Start time.

**last\_activity\_unix\_nanos** `integer` *required*

Last observed activity.

**ended\_at\_unix\_nanos** `integer`

End time, once the session has reached a terminal state.

**exit** `object`

`{ exit_code, signaled, signal }`, present once the session has exited.

`EXITED`, `KILLED`, and `FAILED` are the terminal states referenced above under name collisions; `STARTING`, `IDLE`, and `ATTACHED` are all live.

## Sessions and suspension

A session's process never blocks idle suspend by itself. Only an **attached** client does, for as long as it stays connected — a quiet, detached session lets the sandbox warm-suspend on its normal idle schedule, and survives the resume with its process and replay buffer intact, the same session rather than a new one. Output the session keeps producing while detached still counts as activity and defers idle suspend on its own.

Attaching to a session on a suspended sandbox wakes it, the same way exec traffic does — see [Sandboxes](https://docs.bonya.ai/concepts/sandboxes.md#lifecycle-states) for the resume path.

## Listing sessions

```shell cURL theme={null}
curl -s "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID/sessions" \
  -H "Authorization: Bearer $BONYA_API_KEY"
```

```shell CLI theme={null}
bonya session list $SANDBOX_ID
```

```ts TypeScript theme={null}
for (const info of await sandbox.listSessions()) {
  console.log(info.name, info.status);
}
```

```python Python theme={null}
for info in sandbox.list_sessions():
    print(info.name, info.status)
```

```go Go theme={null}
result, err := sandbox.ListSessions(ctx)
for _, info := range result.Sessions {
    fmt.Println(info.Name, info.Status)
}
```

**MCP:** the `session_list` tool covers this.

Returns `SessionListResponse`:

**sessions** `SessionInfo[]` *required*

All sessions tracked for the sandbox.

**sandbox\_suspended** `boolean` *required*

Whether the sandbox was suspended when this list was produced.

> [!NOTE]
>
> Listing sessions **never wakes** a suspended sandbox. If the sandbox is suspended, the response answers from a snapshot of session state taken at suspend time, and `sandbox_suspended` is `true` — it does not resume the sandbox to get a live answer. To act on the sessions themselves, resume the sandbox first (see [Sandboxes](https://docs.bonya.ai/concepts/sandboxes.md)).

## Attaching to a session

```
GET /v1/sandbox/{sandbox_id}/sessions/{session_name}/attach
```

This is a WebSocket upgrade — see [WebSockets](https://docs.bonya.ai/concepts/websockets.md) for how the upgrade, the START frame (`AttachStartRequest`), and admission work. Multiple clients can attach to the same session.

```shell CLI theme={null}
bonya session attach $SANDBOX_ID dev
```

```ts TypeScript theme={null}
const session = await sandbox.attachSession("dev");
for await (const event of session) {
  // ...
}
session.detach();
```

```python Python theme={null}
session = sandbox.attach_session("dev")
for event in session:
    ...
session.detach()
```

```go Go theme={null}
stream, err := sandbox.AttachSession(ctx, "dev")
for {
    event, err := stream.Next()
    if err != nil || event == nil {
        break
    }
}
stream.Detach()
```

There's no cURL tab here since this operation has no JSON request/response shape to show — it's a raw WebSocket upgrade, not a request/response call. For the same reason, there's no `session_attach` MCP tool: an agent can start and stop background work via `session_create`/`session_kill` but can't stream its live output.

## Killing a session

```shell cURL theme={null}
curl -s -X POST "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID/sessions/dev/kill" \
  -H "Authorization: Bearer $BONYA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"signal": "TERM", "grace_ms": 5000}'
```

```shell CLI theme={null}
bonya session kill $SANDBOX_ID dev
```

```ts TypeScript theme={null}
await sandbox.killSession("dev");
```

```python Python theme={null}
sandbox.kill_session("dev")
```

```go Go theme={null}
sandbox.KillSession(ctx, "dev")
```

**MCP:** the `session_kill` tool covers this, with `signal` and `grace_ms` fields matching the REST body.

Request body (`SessionKillRequest`, optional):

**signal** `string` (default: TERM)

One of `TERM`, `KILL`, `INT`, `HUP`.

**grace\_ms** `integer` (default: 5000)

Milliseconds to wait after the signal before the session is considered killed, 0–60000.

Returns `200` with `{ "session": { ...SessionInfo } }` reflecting the signal having been delivered.

## Related

> [card] [Sandboxes](https://docs.bonya.ai/concepts/sandboxes.md)

> [card] [Exec](https://docs.bonya.ai/concepts/exec.md)

> [card] [WebSockets](https://docs.bonya.ai/concepts/websockets.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.