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

# Sandbox

> Python SDK - Sandbox API reference

See [Overview](/sandboxes/overview) for configuration examples and [Lifecycle](/sandboxes/lifecycle) for state management.

## Static methods

***

#### Sandbox.create()

```python theme={null}
@staticmethod
async def create(name: str, **kwargs) -> Sandbox
```

Create and boot a sandbox. Keyword arguments provide individual config fields. Pulls the image if needed, boots the VM, starts the guest agent, and waits until it is ready to accept commands. Sandbox names must be non-empty and no longer than 128 UTF-8 bytes.

**Parameters**

| Name                   | Type                                                       | Description                                                                                                                                                          |
| ---------------------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name                   | `str`                                                      | Sandbox name, up to 128 UTF-8 bytes                                                                                                                                  |
| image                  | `str \| ImageSource`                                       | OCI image, local path, or disk image. Required unless `snapshot=` is passed. Use `Image.oci("python:3.12", upper_size_mib=8192)` to set an OCI upper size            |
| snapshot               | `str \| os.PathLike`                                       | Snapshot artifact to boot from instead of `image=`. Mutually exclusive with `image=`                                                                                 |
| cpus                   | `int`                                                      | Virtual CPUs (default `1`)                                                                                                                                           |
| memory                 | `int`                                                      | Guest memory in MiB (default `512`)                                                                                                                                  |
| workdir                | `str`                                                      | Default working directory for commands                                                                                                                               |
| shell                  | `str`                                                      | Shell for `shell()` calls (default `"/bin/sh"`)                                                                                                                      |
| security               | [`SecurityProfile`](#securityprofile) \| `str`             | In-guest security profile. `RESTRICTED` sets `no_new_privs`, drops mount-admin capability from user commands, and forces `nosuid,nodev` on user mounts               |
| hostname               | `str`                                                      | Guest hostname                                                                                                                                                       |
| user                   | `str`                                                      | Default guest user                                                                                                                                                   |
| entrypoint             | `list[str]`                                                | Override the image's stored ENTRYPOINT. Consulted by `msb exec` / `msb run` (CLI command resolution), **not** by `sb.exec` / `sb.shell` — those pass `cmd` literally |
| init                   | `str \| dict \|` [`InitConfig`](#initconfig)               | Hand off PID 1 to a guest init binary. See [Custom init system](/sandboxes/customize#custom-init-system) and [`InitConfig`](#initconfig) for accepted shapes         |
| replace                | `bool`                                                     | Replace existing sandbox with same name (10s SIGTERM grace, then SIGKILL)                                                                                            |
| replace\_with\_timeout | `float`                                                    | Seconds to wait after `SIGTERM` before escalating to `SIGKILL` (default `10`; `0` skips `SIGTERM`). Implies `replace=True`                                           |
| max\_duration          | `float`                                                    | Maximum sandbox lifetime in seconds                                                                                                                                  |
| idle\_timeout          | `float`                                                    | Idle timeout in seconds                                                                                                                                              |
| env                    | `dict[str, str]`                                           | Environment variables visible to all commands                                                                                                                        |
| scripts                | `dict[str, str]`                                           | Named scripts mounted at `/.msb/scripts/` and added to `PATH`                                                                                                        |
| pull\_policy           | `str \| PullPolicy`                                        | Image pull behavior                                                                                                                                                  |
| log\_level             | `str \| LogLevel`                                          | Override log verbosity                                                                                                                                               |
| registry\_auth         | [`RegistryAuth`](#registryauth)                            | Private registry credentials                                                                                                                                         |
| volumes                | `dict[str, MountConfig]`                                   | Volume mounts. See [Volumes](/sdk/python/volumes).                                                                                                                   |
| patches                | `list[PatchConfig]`                                        | Rootfs modifications applied before boot                                                                                                                             |
| ports                  | `dict[int, int] \| Sequence[PortBinding]`                  | Port mappings. Dict form is TCP and binds to `127.0.0.1`; use [`PortBinding`](/sdk/python/networking#portbinding) for explicit bind addresses or UDP                 |
| network                | [`Network`](/sdk/python/networking#network)                | Network policy and configuration                                                                                                                                     |
| secrets                | `list[`[`SecretEntry`](/sdk/python/secrets#secretentry)`]` | Secret injection                                                                                                                                                     |
| detached               | `bool`                                                     | If `True`, spawn the sandbox in detached mode; call [`detach()`](#detach) before dropping the returned handle when it should keep running                            |

**Returns**

| Type                           | Description     |
| ------------------------------ | --------------- |
| [`Sandbox`](#instance-methods) | Running sandbox |

The returned `Sandbox` is an async context manager. Use `async with` to guarantee cleanup; on exit the sandbox is killed and its persisted state removed:

```python theme={null}
async with await Sandbox.create("my-sandbox", image="alpine") as sb:
    output = await sb.shell("echo hello")
    print(output.stdout_text)
# sandbox is automatically killed and removed on exit
```

***

#### Sandbox.create\_with\_progress()

```python theme={null}
@staticmethod
def create_with_progress(name: str, **kwargs) -> PullSession
```

Same parameters as [`Sandbox.create()`](#sandboxcreate) but returns a [`PullSession`](#pullsession) that lets you track image pull progress before the sandbox is ready. This method is synchronous (not awaitable) -- the async work happens through the `PullSession`.

**Parameters**

Same as [`Sandbox.create()`](#sandboxcreate).

**Returns**

| Type                          | Description                                                        |
| ----------------------------- | ------------------------------------------------------------------ |
| [`PullSession`](#pullsession) | Session for tracking pull progress and obtaining the final sandbox |

***

#### Sandbox.start()

```python theme={null}
@staticmethod
async def start(name: str, *, detached: bool = False) -> Sandbox
```

Restart a previously stopped sandbox. The VM reboots using the persisted configuration.

**Parameters**

| Name     | Type   | Description                                                            |
| -------- | ------ | ---------------------------------------------------------------------- |
| name     | `str`  | Name of a stopped sandbox, up to 128 UTF-8 bytes                       |
| detached | `bool` | If `True`, sandbox survives after your process exits (default `False`) |

**Returns**

| Type                           | Description     |
| ------------------------------ | --------------- |
| [`Sandbox`](#instance-methods) | Running sandbox |

***

#### Sandbox.get()

```python theme={null}
@staticmethod
async def get(name: str) -> SandboxHandle
```

Get a handle to an existing sandbox (running or stopped). The handle provides status, configuration, and lifecycle control.

**Parameters**

| Name | Type  | Description                         |
| ---- | ----- | ----------------------------------- |
| name | `str` | Sandbox name, up to 128 UTF-8 bytes |

**Returns**

| Type                              | Description                              |
| --------------------------------- | ---------------------------------------- |
| [`SandboxHandle`](#sandboxhandle) | Handle with status and lifecycle control |

***

#### Sandbox.list()

```python theme={null}
@staticmethod
async def list() -> list[SandboxHandle]
```

List all sandboxes (running, stopped, and crashed).

**Returns**

| Type                                        | Description   |
| ------------------------------------------- | ------------- |
| `list[`[`SandboxHandle`](#sandboxhandle)`]` | All sandboxes |

***

#### Sandbox.remove()

```python theme={null}
@staticmethod
async def remove(name: str) -> None
```

Delete a stopped sandbox and all its state from disk. Fails if the sandbox is still running.

**Parameters**

| Name | Type  | Description                         |
| ---- | ----- | ----------------------------------- |
| name | `str` | Sandbox name, up to 128 UTF-8 bytes |

***

## Instance properties

***

#### name

```python theme={null}
@property
async def name(self) -> str
```

Sandbox name. Names are limited to 128 UTF-8 bytes. This is an async property -- use `await sb.name`.

***

#### owns\_lifecycle

```python theme={null}
@property
async def owns_lifecycle(self) -> bool
```

Whether this handle owns the sandbox lifecycle. A sandbox returned directly by [`create()`](#sandboxcreate) or [`start()`](#sandboxstart) owns lifecycle, including when created with `detached=True`, until you call [`detach()`](#detach). Handles returned by [`connect()`](#connect) do not own lifecycle. This is an async property -- use `await sb.owns_lifecycle`.

***

#### fs

```python theme={null}
@property
def fs(self) -> SandboxFsOps
```

Get a filesystem handle for reading and writing files inside the running sandbox. This is a synchronous property -- use `sb.fs` (no `await`). See [Filesystem](/sdk/python/filesystem) for API details.

**Returns**

| Type                                     | Description       |
| ---------------------------------------- | ----------------- |
| [`SandboxFsOps`](/sdk/python/filesystem) | Filesystem handle |

***

## Instance methods

***

#### attach()

```python theme={null}
async def attach(
    cmd: str,
    args: list[str] | None = None,
    *,
    cwd: str | None = None,
    user: str | None = None,
    env: Mapping[str, str] | None = None,
    detach_keys: str | None = None,
) -> int
```

Bridge your terminal directly to a process inside the sandbox for a fully interactive PTY session.

**Parameters**

| Name         | Type                        | Description                |
| ------------ | --------------------------- | -------------------------- |
| cmd          | `str`                       | Command to run             |
| args         | `list[str] \| None`         | Command arguments          |
| cwd          | `str \| None`               | Working directory          |
| user         | `str \| None`               | Guest user                 |
| env          | `Mapping[str, str] \| None` | Environment variables      |
| detach\_keys | `str \| None`               | Custom detach key sequence |

**Returns**

| Type  | Description              |
| ----- | ------------------------ |
| `int` | Exit code of the process |

***

#### attach\_shell()

```python theme={null}
async def attach_shell() -> int
```

Attach to the sandbox's default shell.

**Returns**

| Type  | Description |
| ----- | ----------- |
| `int` | Exit code   |

***

#### detach()

```python theme={null}
async def detach() -> None
```

Release the handle without stopping the sandbox. Reconnect later with [`Sandbox.get()`](#sandboxget).

***

#### request\_drain()

```python theme={null}
async def request_drain() -> None
```

Request a graceful drain and return once the request is sent. Existing commands run to completion, but new `exec` calls are rejected. Use [`wait_until_stopped()`](#wait_until_stopped) when the caller needs stopped-state observation.

***

#### exec()

```python theme={null}
async def exec(
    cmd: str,
    args: list[str] | None = None,
    *,
    cwd: str | None = None,
    user: str | None = None,
    env: Mapping[str, str] | None = None,
    timeout: float | None = None,
    stdin: Stdin | bytes | str | None = None,
    tty: bool = False,
    rlimits: list[Rlimit] | None = None,
) -> ExecOutput
```

Run a command inside the sandbox and wait for it to complete. Collects all stdout and stderr into memory and returns them along with the exit code. For long-running processes or large output, use [`exec_stream()`](#exec_stream) instead.

Pass per-execution options as keyword-only arguments. The SDK still accepts the older second-positional options dict for compatibility, but prefer kwargs for new code.

**Parameters**

| Name    | Type                                                             | Description                                             |
| ------- | ---------------------------------------------------------------- | ------------------------------------------------------- |
| cmd     | `str`                                                            | Command to execute (e.g. `"python"`, `"/usr/bin/node"`) |
| args    | `list[str] \| None`                                              | Command arguments                                       |
| cwd     | `str \| None`                                                    | Working directory                                       |
| user    | `str \| None`                                                    | Guest user                                              |
| env     | `Mapping[str, str] \| None`                                      | Environment variables                                   |
| timeout | `float \| None`                                                  | Kill the process after this many seconds                |
| stdin   | [`Stdin`](/sdk/python/execution#stdin) `\| bytes \| str \| None` | Stdin source                                            |
| tty     | `bool`                                                           | Allocate a pseudo-terminal                              |
| rlimits | `list[`[`Rlimit`](/sdk/python/execution#rlimit)`] \| None`       | Resource limits                                         |

**Returns**

| Type                                             | Description                               |
| ------------------------------------------------ | ----------------------------------------- |
| [`ExecOutput`](/sdk/python/execution#execoutput) | Collected stdout, stderr, and exit status |

```python theme={null}
output = await sb.exec(
    "python",
    ["compute.py"],
    cwd="/app",
    env={"PYTHONPATH": "/app/lib"},
    timeout=30.0,
)
```

***

#### exec\_stream()

```python theme={null}
async def exec_stream(
    cmd: str,
    args: list[str] | None = None,
    *,
    cwd: str | None = None,
    user: str | None = None,
    env: Mapping[str, str] | None = None,
    timeout: float | None = None,
    stdin: Stdin | bytes | str | None = None,
    tty: bool = False,
    rlimits: list[Rlimit] | None = None,
) -> ExecHandle
```

Run a command with streaming output. Returns a handle that emits stdout, stderr, and exit events as they happen, rather than buffering everything.

Like [`exec()`](#exec), pass per-execution options as keyword-only arguments. The older second-positional options dict remains accepted for compatibility.

**Parameters**

| Name    | Type                                                             | Description                              |
| ------- | ---------------------------------------------------------------- | ---------------------------------------- |
| cmd     | `str`                                                            | Command to execute                       |
| args    | `list[str] \| None`                                              | Command arguments                        |
| cwd     | `str \| None`                                                    | Working directory                        |
| user    | `str \| None`                                                    | Guest user                               |
| env     | `Mapping[str, str] \| None`                                      | Environment variables                    |
| timeout | `float \| None`                                                  | Kill the process after this many seconds |
| stdin   | [`Stdin`](/sdk/python/execution#stdin) `\| bytes \| str \| None` | Stdin source                             |
| tty     | `bool`                                                           | Allocate a pseudo-terminal               |
| rlimits | `list[`[`Rlimit`](/sdk/python/execution#rlimit)`] \| None`       | Resource limits                          |

**Returns**

| Type                                             | Description                                                       |
| ------------------------------------------------ | ----------------------------------------------------------------- |
| [`ExecHandle`](/sdk/python/execution#exechandle) | Streaming handle for receiving events and controlling the process |

***

#### kill()

```python theme={null}
async def kill(timeout: float | None = None) -> None
```

Force-terminate the sandbox and wait until stopped state is observed. Pass `timeout` to control the observation timeout. No graceful shutdown. Pending writes that the workload hasn't `fsync`'d may be lost — same durability semantics as a sudden power loss on a physical machine. Use [`stop()`](#stop) for graceful shutdown that gives the workload a chance to flush.

***

#### metrics()

```python theme={null}
async def metrics() -> SandboxMetrics
```

Get a point-in-time snapshot of the sandbox's resource usage.

**Returns**

| Type                                | Description                        |
| ----------------------------------- | ---------------------------------- |
| [`SandboxMetrics`](#sandboxmetrics) | CPU, memory, disk, network metrics |

***

#### metrics\_stream()

```python theme={null}
async def metrics_stream(interval: float = 1.0) -> MetricsStream
```

Stream resource metrics at a regular interval. The returned stream supports both `recv()` and `async for`.

**Parameters**

| Name     | Type    | Description                                      |
| -------- | ------- | ------------------------------------------------ |
| interval | `float` | Seconds between metric snapshots (default `1.0`) |

**Returns**

| Type                              | Description             |
| --------------------------------- | ----------------------- |
| [`MetricsStream`](#metricsstream) | Async stream of metrics |

***

#### logs()

```python theme={null}
async def logs(
    tail: int | None = None,
    since_ms: float | None = None,
    until_ms: float | None = None,
    sources: list[str] | None = None,
) -> list[LogEntry]
```

Read captured output from the sandbox's `exec.log`. Backed by an on-disk JSON Lines file the runtime writes via the relay tap. Works on running and stopped sandboxes alike — there is no protocol traffic. The same method is available on [`SandboxHandle`](#sandboxhandle) for callers that don't want to start the sandbox first.

The default sources are `"stdout"`, `"stderr"`, and `"output"` (PTY-merged). Pass `"system"` to also include synthetic lifecycle markers and runtime/kernel diagnostic lines.

```python theme={null}
import asyncio
import microsandbox

async def main():
    handle = await microsandbox.Sandbox.get("web")

    # Default — all user-program output, regardless of pipe/pty mode
    entries = await handle.logs()

    for e in entries:
        source = {
            "stdout": "OUT",
            "stderr": "ERR",
            "output": "PTY",
            "system": "SYS",
        }[e.source]
        print(
            f"[{e.timestamp_ms / 1000:.3f}] "
            f"{source} {e.session_id}: {e.text().rstrip()}"
        )

    # Filtered: last 50 entries from the past hour, including system lines
    import time
    recent = await handle.logs(
        tail=50,
        since_ms=(time.time() - 3600) * 1000,
        sources=["stdout", "stderr", "output", "system"],
    )

asyncio.run(main())
```

Timestamps are exposed as `float` ms since the Unix epoch (UTC) for parity with `SandboxMetrics.timestamp_ms`. Convert to `datetime` if needed:

```python theme={null}
from datetime import datetime, timezone
dt = datetime.fromtimestamp(e.timestamp_ms / 1000, timezone.utc)
```

**Parameters**

| Name      | Type                | Description                                                                                                                                               |
| --------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| tail      | `int \| None`       | Show only the last N entries after other filters apply                                                                                                    |
| since\_ms | `float \| None`     | Inclusive lower bound on entry timestamp (ms since epoch)                                                                                                 |
| until\_ms | `float \| None`     | Exclusive upper bound on entry timestamp (ms since epoch)                                                                                                 |
| sources   | `list[str] \| None` | Sources to include. `None` = `["stdout", "stderr", "output"]`. Add `"system"` to merge runtime/kernel diagnostics. Use `"all"` as shorthand for all four. |

**Returns**

| Type                              | Description                             |
| --------------------------------- | --------------------------------------- |
| `list[`[`LogEntry`](#logentry)`]` | Matching entries in chronological order |

***

#### remove()

```python theme={null}
async def remove() -> None
```

Remove the sandbox and all its persisted state from disk.

***

#### shell()

```python theme={null}
async def shell(
    script: str,
    *,
    cwd: str | None = None,
    user: str | None = None,
    env: Mapping[str, str] | None = None,
    timeout: float | None = None,
    stdin: Stdin | bytes | str | None = None,
    tty: bool = False,
    rlimits: list[Rlimit] | None = None,
) -> ExecOutput
```

Run a command through the sandbox's configured shell (defaults to `/bin/sh`). Shell syntax like pipes, redirects, and `&&` chains works.

**Parameters**

| Name    | Type                                                             | Description                                              |
| ------- | ---------------------------------------------------------------- | -------------------------------------------------------- |
| script  | `str`                                                            | Shell command string (e.g. `"ls -la /app && echo done"`) |
| cwd     | `str \| None`                                                    | Working directory                                        |
| user    | `str \| None`                                                    | Guest user                                               |
| env     | `Mapping[str, str] \| None`                                      | Environment variables                                    |
| timeout | `float \| None`                                                  | Kill the process after this many seconds                 |
| stdin   | [`Stdin`](/sdk/python/execution#stdin) `\| bytes \| str \| None` | Stdin source                                             |
| tty     | `bool`                                                           | Allocate a pseudo-terminal                               |
| rlimits | `list[`[`Rlimit`](/sdk/python/execution#rlimit)`] \| None`       | Resource limits                                          |

**Returns**

| Type                                             | Description                               |
| ------------------------------------------------ | ----------------------------------------- |
| [`ExecOutput`](/sdk/python/execution#execoutput) | Collected stdout, stderr, and exit status |

***

#### shell\_stream()

```python theme={null}
async def shell_stream(script: str) -> ExecHandle
```

Shell command with streaming output.

**Parameters**

| Name   | Type  | Description          |
| ------ | ----- | -------------------- |
| script | `str` | Shell command string |

**Returns**

| Type                                             | Description      |
| ------------------------------------------------ | ---------------- |
| [`ExecHandle`](/sdk/python/execution#exechandle) | Streaming handle |

***

#### stop()

```python theme={null}
async def stop(timeout: float | None = None) -> None
```

Gracefully shut down the sandbox and wait until stopped state is observed. Lets the sandbox finish writing any pending data to disk before it exits, so files written inside the sandbox aren't lost across a later restart. Waits up to ten seconds by default; pass `timeout` to override the graceful shutdown window before force-kill escalation.

***

#### request\_stop()

```python theme={null}
async def request_stop() -> None
```

Request graceful shutdown and return once the request is sent.

***

#### request\_kill()

```python theme={null}
async def request_kill() -> None
```

Request force termination and return once the signal is sent.

***

#### wait\_until\_stopped()

```python theme={null}
async def wait_until_stopped() -> SandboxStopResult
```

Block until the sandbox is observed in a terminal non-running state, without triggering a stop or kill request.

**Returns**

| Type                                      | Description                                     |
| ----------------------------------------- | ----------------------------------------------- |
| [`SandboxStopResult`](#sandboxstopresult) | Terminal status and optional observed exit code |

***

## Patch

Factory class for rootfs patches passed to `Sandbox.create(..., patches=[...])`. Each static method returns a [`PatchConfig`](#patchconfig). By default a patch that targets a path already present in the image errors at boot; pass `replace=True` on the operation to allow overwriting. `mkdir` and `remove` are idempotent.

See [Patches](/sandboxes/customize#patches) for conceptual context.

***

#### Patch.append()

```python theme={null}
@staticmethod
def append(path: str, content: str) -> PatchConfig
```

Append `content` to an existing file at `path`. If the file lives in a lower image layer, it's copied up first.

**Parameters**

| Name    | Type  | Description                    |
| ------- | ----- | ------------------------------ |
| path    | `str` | Absolute path inside the guest |
| content | `str` | Text to append                 |

***

#### Patch.copy\_dir()

```python theme={null}
@staticmethod
def copy_dir(src: str, dst: str, *, replace: bool = False) -> PatchConfig
```

Recursively copy a host directory at `src` into the guest rootfs at `dst`.

**Parameters**

| Name    | Type   | Description                                      |
| ------- | ------ | ------------------------------------------------ |
| src     | `str`  | Host source directory                            |
| dst     | `str`  | Absolute destination path inside the guest       |
| replace | `bool` | When `True`, overwrite an existing path at `dst` |

***

#### Patch.copy\_file()

```python theme={null}
@staticmethod
def copy_file(
    src: str,
    dst: str,
    *,
    mode: int | None = None,
    replace: bool = False,
) -> PatchConfig
```

Copy a single host file at `src` into the guest rootfs at `dst`.

**Parameters**

| Name    | Type          | Description                                           |
| ------- | ------------- | ----------------------------------------------------- |
| src     | `str`         | Host source file                                      |
| dst     | `str`         | Absolute destination path inside the guest            |
| mode    | `int \| None` | File mode, e.g. `0o644`. `None` keeps the source mode |
| replace | `bool`        | When `True`, overwrite an existing path at `dst`      |

***

#### Patch.mkdir()

```python theme={null}
@staticmethod
def mkdir(path: str, *, mode: int | None = None) -> PatchConfig
```

Create a directory at `path`. Idempotent: a no-op if the directory already exists.

**Parameters**

| Name | Type          | Description                    |
| ---- | ------------- | ------------------------------ |
| path | `str`         | Absolute path inside the guest |
| mode | `int \| None` | Directory mode, e.g. `0o755`   |

***

#### Patch.remove()

```python theme={null}
@staticmethod
def remove(path: str) -> PatchConfig
```

Delete a file or directory at `path`. Idempotent: a no-op if the path doesn't exist.

**Parameters**

| Name | Type  | Description                    |
| ---- | ----- | ------------------------------ |
| path | `str` | Absolute path inside the guest |

***

#### Patch.symlink()

```python theme={null}
@staticmethod
def symlink(target: str, link: str, *, replace: bool = False) -> PatchConfig
```

Create a symlink at `link` pointing to `target`.

**Parameters**

| Name    | Type   | Description                                              |
| ------- | ------ | -------------------------------------------------------- |
| target  | `str`  | What the symlink points to (literal symlink target text) |
| link    | `str`  | Absolute path of the symlink itself                      |
| replace | `bool` | When `True`, overwrite an existing path at `link`        |

***

#### Patch.text()

```python theme={null}
@staticmethod
def text(
    path: str,
    content: str,
    *,
    mode: int | None = None,
    replace: bool = False,
) -> PatchConfig
```

Write UTF-8 text content at `path`.

**Parameters**

| Name    | Type          | Description                             |
| ------- | ------------- | --------------------------------------- |
| path    | `str`         | Absolute path inside the guest          |
| content | `str`         | Text content                            |
| mode    | `int \| None` | File mode, e.g. `0o644`                 |
| replace | `bool`        | When `True`, overwrite an existing path |

***

## Types

### LogLevel

Sandbox process log verbosity.

| Value     | Description                           |
| --------- | ------------------------------------- |
| `"debug"` | Debug and higher                      |
| `"error"` | Errors only                           |
| `"info"`  | Info and higher                       |
| `"trace"` | Most verbose -- all diagnostic output |
| `"warn"`  | Warnings and errors only              |

### SecurityProfile

```python theme={null}
class SecurityProfile(StrEnum):
    DEFAULT = "default"
    RESTRICTED = "restricted"
```

Sandbox-wide in-guest security profile.

### LogEntry

A single captured log entry returned by [`logs()`](#logs).

| Property / Method | Type          | Description                                                              |
| ----------------- | ------------- | ------------------------------------------------------------------------ |
| timestamp\_ms     | `float`       | Wall-clock capture time (ms since Unix epoch, UTC)                       |
| source            | `str`         | Where the chunk came from. See [LogSource](#logsource).                  |
| session\_id       | `int \| None` | Relay-monotonic session id; `None` for `"system"` entries                |
| data              | `bytes`       | The chunk's bytes (UTF-8 lossy decoded by default)                       |
| `text()`          | `str`         | Convenience: UTF-8 decode of `data` (lossy — invalid bytes are replaced) |

### LogSource

The string values that the `source` field on a [`LogEntry`](#logentry) can take, also accepted by `logs(sources=[...])`.

| Value      | Description                                                                                                                                                                                                             |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `"stdout"` | Captured from a session's stdout (pipe mode — streams stayed separated)                                                                                                                                                 |
| `"stderr"` | Captured from a session's stderr (pipe mode)                                                                                                                                                                            |
| `"output"` | Captured from a session running in PTY mode. PTY allocation merges stdout and stderr at the kernel level inside the guest, so they arrive as a single stream — tagged `"output"` rather than mislabelled as `"stdout"`. |
| `"system"` | Synthetic entry: lifecycle markers in `exec.log` plus runtime/kernel diagnostic lines merged in at read time when `"system"` is requested.                                                                              |

In addition, `logs(sources=["all"])` is a shorthand for all four.

### MetricsStream

Async stream for receiving periodic metrics snapshots.

| Method      | Returns                                                | Description                                                 |
| ----------- | ------------------------------------------------------ | ----------------------------------------------------------- |
| `__aiter__` | `AsyncIterator[`[`SandboxMetrics`](#sandboxmetrics)`]` | Use with `async for`                                        |
| `recv()`    | [`SandboxMetrics`](#sandboxmetrics) `\| None`          | Receive next snapshot. Returns `None` when the stream ends. |

### PatchConfig

A single rootfs patch. Produced by the [`Patch`](#patch) factory; you'd normally not construct one directly. Frozen dataclass.

| Field   | Type          | Description                                                                                            |
| ------- | ------------- | ------------------------------------------------------------------------------------------------------ |
| kind    | `str`         | One of `"text"`, `"file"`, `"copy_file"`, `"copy_dir"`, `"symlink"`, `"mkdir"`, `"remove"`, `"append"` |
| path    | `str \| None` | Absolute guest path (used by text / file / mkdir / remove / append)                                    |
| content | `str \| None` | Text content (text / append)                                                                           |
| src     | `str \| None` | Host source path (copy\_file / copy\_dir)                                                              |
| dst     | `str \| None` | Guest destination path (copy\_file / copy\_dir)                                                        |
| target  | `str \| None` | Symlink target                                                                                         |
| link    | `str \| None` | Symlink path                                                                                           |
| mode    | `int \| None` | File / directory mode (e.g. `0o644`)                                                                   |
| replace | `bool`        | When `True`, overwrite an existing path at the destination. Defaults to `False`                        |

### PullPolicy

Controls when the SDK fetches an OCI image from the registry.

| Value          | Description                                                        |
| -------------- | ------------------------------------------------------------------ |
| `"always"`     | Pull the image every time, even if cached locally                  |
| `"if-missing"` | Pull only if the image is not already cached. This is the default. |
| `"never"`      | Never pull; fail if the image is not cached locally                |

### PullEvent

Native event object emitted by [`PullSession.progress`](#pullsession). Inspect `event_type` and the fields relevant to that event:

| event\_type                    | Fields                                                                |
| ------------------------------ | --------------------------------------------------------------------- |
| `"resolving"`                  | `reference`                                                           |
| `"resolved"`                   | `reference`, `manifest_digest`, `layer_count`, `total_download_bytes` |
| `"layer_download_progress"`    | `layer_index`, `digest`, `downloaded_bytes`, `total_bytes`            |
| `"layer_download_complete"`    | `layer_index`, `digest`, `downloaded_bytes`                           |
| `"layer_download_verifying"`   | `layer_index`, `digest`                                               |
| `"layer_materialize_started"`  | `layer_index`, `diff_id`                                              |
| `"layer_materialize_progress"` | `layer_index`, `bytes_read`, `total_bytes`                            |
| `"layer_materialize_writing"`  | `layer_index`                                                         |
| `"layer_materialize_complete"` | `layer_index`, `diff_id`                                              |
| `"stitch_merging_trees"`       | `layer_count`                                                         |
| `"stitch_writing_fsmeta"`      | -                                                                     |
| `"stitch_writing_vmdk"`        | -                                                                     |
| `"stitch_complete"`            | -                                                                     |
| `"complete"`                   | `reference`, `layer_count`                                            |

Fields that do not apply to a particular event are `None`.

```python theme={null}
from microsandbox import Sandbox

session = Sandbox.create_with_progress("my-sandbox", image="ubuntu:latest")
async with session:
    async for event in session.progress:
        if event.event_type == "resolved":
            print(f"{event.layer_count} layers, {event.total_download_bytes} bytes")
        elif event.event_type == "layer_download_progress":
            print(f"layer {event.layer_index}: {event.downloaded_bytes}/{event.total_bytes}")
    sb = await session.result()
```

### PullSession

Returned by [`Sandbox.create_with_progress()`](#sandboxcreate_with_progress). `Sandbox.create_with_progress()` itself is synchronous; use the returned session as an async context manager to track image pull progress.

```python theme={null}
session = Sandbox.create_with_progress("my-sandbox", image="ubuntu:latest")
async with session:
    async for event in session.progress:
        print(event)
    sb = await session.result()
```

| Property / Method | Type                                          | Description                                                                       |
| ----------------- | --------------------------------------------- | --------------------------------------------------------------------------------- |
| progress          | `AsyncIterator[`[`PullEvent`](#pullevent)`]`  | Async iterator of pull progress events                                            |
| result()          | `Awaitable[`[`Sandbox`](#instance-methods)`]` | Await once to get the final running sandbox. A second call raises `RuntimeError`. |

### RegistryAuth

Private registry credentials.

| Field    | Type  | Description       |
| -------- | ----- | ----------------- |
| username | `str` | Registry username |
| password | `str` | Registry password |

### SandboxConfig

The keyword arguments accepted by [`Sandbox.create()`](#sandboxcreate) and [`Sandbox.create_with_progress()`](#sandboxcreate_with_progress).

| Field                  | Type                                                       | Default                   | Description                                                                                                                                                          |
| ---------------------- | ---------------------------------------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| image                  | `str \| ImageSource`                                       | -                         | OCI image, local path, or disk image. Required unless `snapshot=` is passed. Use `Image.oci("python:3.12", upper_size_mib=8192)` to set an OCI upper size            |
| snapshot               | `str \| os.PathLike`                                       | -                         | Snapshot artifact to boot from instead of `image=`. Mutually exclusive with `image=`                                                                                 |
| cpus                   | `int`                                                      | `1`                       | Virtual CPUs                                                                                                                                                         |
| memory                 | `int`                                                      | `512`                     | Guest memory in MiB. This is a limit, not a reservation.                                                                                                             |
| workdir                | `str`                                                      | -                         | Default working directory for commands                                                                                                                               |
| shell                  | `str`                                                      | `"/bin/sh"`               | Shell for `shell()` calls                                                                                                                                            |
| security               | [`SecurityProfile`](#securityprofile) \| `str`             | `SecurityProfile.DEFAULT` | In-guest security profile                                                                                                                                            |
| hostname               | `str`                                                      | -                         | Guest hostname                                                                                                                                                       |
| user                   | `str`                                                      | -                         | Default guest user                                                                                                                                                   |
| entrypoint             | `list[str]`                                                | -                         | Override the image's stored ENTRYPOINT. Consulted by `msb exec` / `msb run` (CLI command resolution), **not** by `sb.exec` / `sb.shell` — those pass `cmd` literally |
| init                   | `str \| dict \|` [`InitConfig`](#initconfig)               | -                         | Hand off PID 1 to a guest init binary. See [Custom init system](/sandboxes/customize#custom-init-system)                                                             |
| replace                | `bool`                                                     | `False`                   | Replace existing sandbox with same name                                                                                                                              |
| replace\_with\_timeout | `float`                                                    | `10`                      | Seconds to wait after `SIGTERM` before escalating to `SIGKILL`; implies `replace=True`                                                                               |
| max\_duration          | `float`                                                    | -                         | Maximum sandbox lifetime in seconds                                                                                                                                  |
| idle\_timeout          | `float`                                                    | -                         | Idle timeout in seconds                                                                                                                                              |
| env                    | `dict[str, str]`                                           | `{}`                      | Environment variables visible to all commands                                                                                                                        |
| scripts                | `dict[str, str]`                                           | `{}`                      | Named scripts mounted at `/.msb/scripts/` and added to `PATH`                                                                                                        |
| pull\_policy           | `str \| PullPolicy`                                        | `"if-missing"`            | Image pull behavior                                                                                                                                                  |
| log\_level             | `str \| LogLevel`                                          | -                         | Override log verbosity                                                                                                                                               |
| registry\_auth         | [`RegistryAuth`](#registryauth)                            | -                         | Private registry credentials                                                                                                                                         |
| volumes                | `dict[str, MountConfig]`                                   | `{}`                      | Volume mounts. See [Volumes](/sdk/python/volumes).                                                                                                                   |
| patches                | `list[PatchConfig]`                                        | `[]`                      | Rootfs modifications applied before boot                                                                                                                             |
| ports                  | `dict[int, int] \| Sequence[PortBinding]`                  | `{}`                      | Port mappings. Dict form is TCP and binds to `127.0.0.1`; use [`PortBinding`](/sdk/python/networking#portbinding) for explicit bind addresses or UDP                 |
| network                | [`Network`](/sdk/python/networking#network)                | `public_only`             | Network policy and configuration                                                                                                                                     |
| secrets                | `list[`[`SecretEntry`](/sdk/python/secrets#secretentry)`]` | `[]`                      | Secret injection                                                                                                                                                     |
| detached               | `bool`                                                     | `False`                   | If `True`, spawn the sandbox in detached mode; call [`detach()`](#detach) before dropping the returned handle when it should keep running                            |

### InitConfig

Custom init specification. Pass it (or one of the equivalent shorthand shapes) as the `init=` kwarg to [`Sandbox.create()`](#sandboxcreate) to hand PID 1 inside the guest off to your own init binary after agentd's setup. See [Custom init system](/sandboxes/customize#custom-init-system) for image picks, shutdown semantics, and tradeoffs.

```python theme={null}
from dataclasses import dataclass

@dataclass(frozen=True, slots=True)
class InitConfig:
    cmd: str
    args: tuple[str, ...] = ()
    env: Mapping[str, str] = {}
```

| Field | Type              | Description                                                          |
| ----- | ----------------- | -------------------------------------------------------------------- |
| cmd   | `str`             | Absolute path or `"auto"` to the init binary inside the guest rootfs |
| args  | `tuple[str, ...]` | Supplemental argv (`argv[0]` is implicitly `cmd`)                    |
| env   | `dict[str, str]`  | Extra env vars merged on top of the inherited env                    |

The `init=` kwarg follows the same shape as other `Sandbox.create` kwargs that carry structured values (`image=`, `network=`, etc.): a bare scalar for the simple case, or a dataclass / dict for the rich case.

| Form                                                 | Equivalent to                   |
| ---------------------------------------------------- | ------------------------------- |
| `init="auto"` or `init="/sbin/init"`                 | `InitConfig(cmd=...)`           |
| `init={"cmd": ..., "args": [...], "env": {...}}`     | dict equivalent of `InitConfig` |
| `init=InitConfig(cmd="/sbin/init", args=("--foo",))` | itself                          |

```python theme={null}
from microsandbox import InitConfig, Sandbox

# Common case: bare string.
sb = await Sandbox.create("worker", image="jrei/systemd-debian:12", init="auto")

# Argv / env: dataclass.
sb = await Sandbox.create(
    "worker",
    image="jrei/systemd-debian:12",
    init=InitConfig(
        cmd="/lib/systemd/systemd",
        args=("--unit=multi-user.target",),
        env={"container": "microsandbox"},
    ),
)
```

### SandboxHandle

A lightweight handle to an existing sandbox (running or stopped). Obtained via [`Sandbox.get()`](#sandboxget) or [`Sandbox.list()`](#sandboxlist). Provides status, configuration, and lifecycle control **without** an active connection to the guest agent. Call `.start()` or `.connect()` to upgrade to a full [`Sandbox`](#instance-methods).

| Property / Method         | Type                                                     | Description                                                                      |
| ------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------- |
| name                      | `str`                                                    | Sandbox name, up to 128 UTF-8 bytes                                              |
| status                    | `str`                                                    | Current status (`"running"`, `"stopped"`, `"crashed"`, `"draining"`, `"paused"`) |
| config\_json              | `str`                                                    | Raw JSON configuration                                                           |
| created\_at               | `float \| None`                                          | Creation timestamp (ms since epoch)                                              |
| updated\_at               | `float \| None`                                          | Last update timestamp (ms since epoch)                                           |
| connect(timeout=None)     | `Awaitable[`[`Sandbox`](#instance-methods)`]`            | Connect to a running sandbox, optionally with an explicit timeout in seconds     |
| start(\*, detached=False) | `Awaitable[`[`Sandbox`](#instance-methods)`]`            | Start in attached or detached mode                                               |
| stop(timeout=None)        | `Awaitable[None]`                                        | Gracefully shut down and wait until stopped state is observed                    |
| request\_stop()           | `Awaitable[None]`                                        | Request graceful shutdown without waiting                                        |
| kill(timeout=None)        | `Awaitable[None]`                                        | Force terminate and wait until stopped state is observed                         |
| request\_kill()           | `Awaitable[None]`                                        | Request force termination without waiting                                        |
| request\_drain()          | `Awaitable[None]`                                        | Request graceful drain without waiting                                           |
| wait\_until\_stopped()    | `Awaitable[`[`SandboxStopResult`](#sandboxstopresult)`]` | Block until the sandbox reaches terminal state                                   |
| remove()                  | `Awaitable[None]`                                        | Delete sandbox and state                                                         |
| metrics()                 | `Awaitable[`[`SandboxMetrics`](#sandboxmetrics)`]`       | Point-in-time resource metrics                                                   |
| logs()                    | `Awaitable[list[`[`LogEntry`](#logentry)`]]`             | Read captured `exec.log` (works without starting)                                |

### SandboxStopResult

Observed terminal sandbox state returned by [`wait_until_stopped()`](#wait_until_stopped).

| Field      | Type          | Description                            |
| ---------- | ------------- | -------------------------------------- |
| status     | `str`         | Terminal status that was observed      |
| exit\_code | `int \| None` | Process exit code when it is available |

### SandboxMetrics

Point-in-time resource usage snapshot.

| Field                | Type    | Description                                      |
| -------------------- | ------- | ------------------------------------------------ |
| cpu\_percent         | `float` | CPU usage as a percentage                        |
| disk\_read\_bytes    | `int`   | Total bytes read from disk since boot            |
| disk\_write\_bytes   | `int`   | Total bytes written to disk since boot           |
| memory\_bytes        | `int`   | Current memory usage in bytes                    |
| memory\_limit\_bytes | `int`   | Memory limit in bytes                            |
| net\_rx\_bytes       | `int`   | Total bytes received over the network since boot |
| net\_tx\_bytes       | `int`   | Total bytes sent over the network since boot     |
| timestamp\_ms        | `float` | When this measurement was taken (ms since epoch) |
| uptime\_ms           | `int`   | Time since the sandbox was created (ms)          |
