Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
content-design/
.claude/
5 changes: 4 additions & 1 deletion .mintignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@

# Draft content
drafts/
*.draft.mdx
*.draft.mdx

# Internal content-design workspace (also git-ignored)
content-design/
60 changes: 48 additions & 12 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,47 @@
This is the official documentation site for **BoxLite** — a local-first micro-VM sandbox for AI agents. Stateful, lightweight, hardware-level isolation, no daemon required. The site is built with [Mintlify](https://mintlify.com) and deployed automatically on push to `main`.

- **BoxLite repo**: https://github.com/boxlite-ai/boxlite
- **BoxRun repo**: https://github.com/boxlite-ai/boxrun
- **Current versions**: BoxLite Python v0.5.11 (stable), Node.js v0.2.8, C v0.5.11; BoxRun latest
- **SDKs**: Python, Node.js, Rust, Go, C, plus a built-in REST API (`boxlite serve`)
- **Platforms**: macOS (Apple Silicon), Linux (KVM), Windows (WSL2)

## Authoring Principles (must follow)

These two rules override convenience. Documentation that violates them ships incorrect information and erodes trust faster than missing content does.

### 1. Ground every claim in real code — never hallucinate

The source of truth is the BoxLite repository at https://github.com/boxlite-ai/boxlite, **not** prior conversations, training data, or memory. Before writing or editing any factual statement (API name, parameter, default, return type, error class, platform support, performance number, behavior under edge conditions):

- **Open the code first.** Verify against `sdks/{python,nodejs,rust,c,go}/`, `src/boxlite/`, `src/cli/`, `examples/`, the public README, and the latest tagged release notes. If the repo is not cloned locally, fetch the relevant file via `WebFetch` or `gh api repos/boxlite-ai/boxlite/contents/...`.
- **Cite specifically when behavior is non-obvious.** In your working notes (not necessarily in the published page) record the exact path you verified against, e.g. *"verified `disk_size_gb` default in `sdks/python/src/box_options.rs`"*. This makes review and future code-drift checks tractable.
- **Refuse to write what you cannot verify.** If you cannot find a feature in the code, do not document it — flag it for engineering instead. Conversely, if the code clearly does something the docs do not yet describe, raise that as a content gap, do not paper over it.
- **Watch for stale knowledge.** API surfaces evolve; assume any signature you "remember" is wrong until re-checked against the current default branch (or the version the docs target). Capability claims older than ~30 days deserve re-verification.
- **Mark uncertainty explicitly.** If a behavior is partially confirmed (e.g., works on macOS but Linux unchecked), say so in the page rather than generalizing.

### 1a. Stay strictly in the user's SDK frame — never quote or link to internal source

The published documentation describes BoxLite **as the user calls it** — SDK class and function names, options, return shapes, error strings. Internal implementation paths, Rust source files, line numbers, internal struct definitions, build scripts, and any artefact that lives below the SDK boundary do **not** belong in user-facing pages.

- Do **not** write `src/boxlite/...`, `sdks/python/src/...`, `src/deps/...`, `wire.go:81`, internal trait or struct names that the SDK does not export, or links into `github.com/boxlite-ai/boxlite/blob/main/src/...`.
- Do **not** paste Rust struct definitions, `pub` keywords, `Vec<...>`, `Option<...>`, lifetime annotations, or any other syntax that only appears in the runtime's source. If a user-facing concept needs a structured shape, render it as a **field table** or as a snippet in the user's actual SDK language.
- It is fine to link to the SDK module path users `import` (`github.com/boxlite-ai/boxlite/sdks/go`, `pip install boxlite`, `npm install @boxlite-ai/boxlite`), to the BoxLite README, to the OpenAPI spec, and to the upstream `examples/` directory — those are user-facing artefacts.
- Code-grounding still applies: every fact must be verified against the real source. The verification footprint goes into the internal `content-design/architecture-design/stage2-validation-log.md` (gitignored), never into the published page.

If a fact can only be expressed by pointing at internal source, it is the wrong fact for a user-facing page — either rephrase it in user terms, escalate it to engineering, or drop it.

### 2. Every demo code block must be end-to-end validated

A code block in the published docs is a contract: copy-paste, run, get the stated result. If a snippet has not been executed, it must not ship in a Tutorial or Quick Start. Do this before declaring a page complete:

- **Install the real SDK** at the version the docs target (`pip install boxlite` / `npm install boxlite` / `cargo add boxlite` / `go get …` / link `libboxlite`).
- **Run the snippet end-to-end** in a clean environment that matches the page's stated prerequisites (OS, Python/Node version, KVM availability). Capture actual stdout/stderr; the page's expected output must match what you saw.
- **Verify the failure modes you describe.** If the page says "exit code 137 on OOM" or "raises `BoxTimeoutError`", trigger the path and confirm the observation. Do not paraphrase plausible-sounding behavior.
- **Test multi-language tabs symmetrically.** A snippet behind every Tab must have been run in that language — not transliterated from one tab into another and shipped untested.
- **Re-run on bumps.** When pinning to a new SDK version or after a code change in the BoxLite repo, re-run the affected snippets. The CHANGELOG is not enough — APIs change in patch releases.
- **If you cannot run it, do not ship it as runnable.** Either get the prerequisites and run it, or downgrade the page from Tutorial / Quick Start to a Concept page that frames the snippet as illustrative pseudocode (and label it as such). Never publish unverified code under a tutorial heading.

When a code-grounded check or end-to-end run reveals a discrepancy with the existing docs, fix the docs in the same change — don't leave the contradiction in place "for later".

## Tech Stack

- **Mintlify** — documentation platform (config in `docs.json`)
Expand All @@ -21,12 +58,13 @@ This is the official documentation site for **BoxLite** — a local-first micro-
docs.json # Mintlify config: navigation, theme, colors, logo
index.mdx # Home page
faq.mdx # FAQ & troubleshooting
getting-started/ # Quickstart guides (Python, Node.js, Rust, C)
getting-started/ # Installation + quickstarts + core concepts + design principles
concepts/ # Primitive deep-dives (lifecycle, execution, filesystem, network, snapshot)
tutorials/ # Step-by-step task tutorials
guides/ # How-to guides (production patterns) + changelog
reference/ # SDK API reference (python/, nodejs/, rust/, c/, go/, rest/)
architecture/ # Architecture, components, security, networking
reference/ # SDK API reference (python/, nodejs/, rust/, c/)
boxrun/ # BoxRun platform docs (CLI, Python SDK, REST API, config)
guides/ # How-to guides (build, examples, AI integration, etc.)
development/ # Internal docs (CLI, Rust style guide)
development/ # Internal docs (CLI, Rust style guide) — files exist but currently hidden from navigation
snippets/ # Reusable MDX snippets (e.g., prerequisites.mdx)
images/ # Static images (hero, screenshots)
logo/ # Light/dark SVG logos
Expand Down Expand Up @@ -55,9 +93,10 @@ mint update

### Navigation
- All navigation is defined in `docs.json` under `navigation.tabs`
- 4 tabs: Documentation, SDK Reference, Guides, Development
- 1 user-facing tab **BoxLite** with 8 groups: Introduction, Get Started, Concepts, Tutorials, How-to Guides, SDK Reference, Architecture, Resources. (`development/*.mdx` files are still in the repo but intentionally not in navigation right now.)
- **Never add a page to navigation without creating the file first**
- **Never remove a page without checking for inbound links**
- Placeholder pages have `placeholder: true` in frontmatter; treat them as not-yet-shipped content (Phase 2 will fill the bodies)

### Writing Style
- Active voice, second person ("you")
Expand Down Expand Up @@ -93,12 +132,9 @@ Use these terms consistently across all documentation:
| Term | Usage |
|------|-------|
| BoxLite | Local-first micro-VM sandbox (capital B, capital L) |
| BoxRun | Sandbox management platform (capital B, capital R) |
| LiteBox | The VM instance type (capital L, capital B) |
| box | Generic reference to a sandbox instance (lowercase) |
| SimpleBox / CodeBox / BrowserBox | Python/Node.js SDK box types |
| BoxHandle | BoxRun SDK handle to a specific box |
| BoxRunClient | BoxRun Python SDK client class |
| SimpleBox / CodeBox / BrowserBox / ComputerBox / InteractiveBox | Specialized box types (Python and Node.js SDKs only) |
| Guest Agent | The agent running inside the VM |
| Jailer | The security isolation component |
| ShimController | Process lifecycle manager |
109 changes: 109 additions & 0 deletions concepts/execution.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
---
title: "Execution model"
sidebarTitle: "Execution"
description: "How exec, run, streaming, PTY, and timeouts work — the contract between your code and the guest."
icon: "terminal"
---

`exec` is the workhorse of every box. This page describes what actually happens when you call it, the difference between `exec` and `run`, and how stdin/stdout/stderr/timeouts/PTY interact.

## `exec` is the primitive; `run` is sugar

- `SimpleBox.exec(cmd, *args)` is the underlying call. It spawns a new process inside the guest and gives you control over its lifecycle.
- `CodeBox.run(code)` is a thin wrapper that calls `exec("python", "-c", code)`, with `install_package(...)` doing `exec("pip", "install", ...)` underneath. `CodeBox` extends `SimpleBox`, so `exec` is still available on a `CodeBox`.

Picking between them is a matter of ergonomics, not capability — anything `run` does, `exec` can do verbatim.

## Two return shapes

Different SDK surfaces return different shapes by design:

| Surface | Return type | Output | When to use |
|---|---|---|---|
| **Python `box.exec(...)`** | `Execution` handle | Streams via `execution.stdout()` / `stderr()`; collect or iterate | When you want streaming, kill, resize_tty |
| **Python `await execution.wait()`** | `ExecResult` | Buffered `stdout` / `stderr` strings, `exit_code` | When you just want the final outcome |
| **Node `SimpleBox.exec(...)`** | `ExecResult` (await once) | Buffered `stdout` / `stderr` strings, `exitCode` | The simple, one-shot case |
| **Node `JsBoxlite` lower-level** | `Execution` handle | Streams; `await execution.wait()` for `ExecResult` | When you need streams or signal control |
| **REST `POST .../exec`** | `{execution_id}` (immediate) | Stream via SSE on `.../output`; `exit` event carries `exit_code` | Always async on the wire |

The two-shape design is intentional: the simple wrappers (`SimpleBox.exec` in Node, `CodeBox.run` in Python) buffer everything for a one-liner; the lower-level handles let you tail output in real time and kill the process. See [Reference](/reference/index) for each language's exact signature.

## Streaming

When you have an `Execution` handle, stdout and stderr are async iterators. Each yielded item is a chunk (line-buffered by the runtime), not a single character.

```python
execution = await box.exec("python", "-u", "-c",
"import time; [print(i) or time.sleep(0.5) for i in range(5)]")
async for line in execution.stdout():
print("got:", line, end="")
result = await execution.wait()
```

The REST equivalent is the SSE stream at `GET /v1/{prefix}/boxes/{box_id}/executions/{exec_id}/output` — events have `stdout` / `stderr` / `exit` types, and `stdout` / `stderr` payloads are base64-encoded.

## stdin

Each `Execution` exposes an stdin handle (`execution.stdin()` in Python, `execution.stdin()` in the Node low-level API). It is a write-only channel — feed bytes in, the guest sees them on file descriptor 0.

This is what `InteractiveBox` uses to drive a shell session.

## Timeouts and kill

`exec` itself does not enforce a timeout. Cancelling the awaitable in your SDK only cancels your local future — the guest process keeps running. To actually stop a guest process, call:

```python
await execution.kill() # default signal SIGKILL
await execution.kill("SIGTERM") # custom signal
```

Because of this, the canonical timeout pattern in production is:

```python
try:
result = await asyncio.wait_for(execution.wait(), timeout=30)
except asyncio.TimeoutError:
await execution.kill()
raise
```

This pattern is documented end-to-end in [How-to guides → AI Agent Integration](/guides/ai-agent-integration#timeout-handling-and-zombie-prevention).

## Exit codes

`exit_code` is the guest process's exit status — straight from the kernel. The same conventions as Linux apply:

| Exit code | Meaning |
|---|---|
| `0` | Success |
| `1`–`125` | Application-defined |
| `127` | Command not found |
| `128 + N` | Killed by signal `N` (`137` = `SIGKILL`, `143` = `SIGTERM`) |

Exit code `137` from a memory-limited box almost always means the in-guest OOM killer fired. The box itself stays `running`; only the offending process dies. See [How-to guides → AI Agent Integration → Memory limits and OOM](/guides/ai-agent-integration#memory-limits-and-oom) for the recovery pattern.

## PTY (`tty=True`)

Pass `tty=True` to `exec` to allocate a pseudo-terminal. Two effects:

1. The guest sees a real TTY on fds 0/1/2, so programs that switch behaviour based on `isatty(stdin)` (REPLs, `top`, colourised CLIs) will use the TTY path.
2. `execution.resize_tty(rows, cols)` becomes valid (both arguments are unsigned integers). Calling it on a non-TTY execution returns an error.

`InteractiveBox` is the high-level wrapper around this — it sets up the TTY, hooks stdin/stdout, and gives you `wait()` to block until the session ends.

## Working directory, env, user

Available on every `exec`:

- `cwd` — directory inside the guest. Defaults to the image's `WORKDIR` (often `/`).
- `env` — extra environment variables, merged on top of the box's own `env`.
- `user` — `"name"` or `"uid:gid"`. Without this, the guest entrypoint's default user runs the command.

These are the levers you tune when integrating an AI agent that expects a specific account or working directory.

## See also

- [Lifecycle](/concepts/lifecycle) — `exec` triggers an implicit start when the box is `configured` or `stopped`.
- [Tutorials → Execute AI-generated code](/tutorials/code-execution) — the same primitives in task form.
- [Tutorials → Interactive terminal](/tutorials/interactive-terminal) — TTY-driven sessions.
- [Tutorials → Handle errors and debug](/tutorials/error-handling) — exit codes, exceptions, timeouts.
75 changes: 75 additions & 0 deletions concepts/filesystem.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
title: "Filesystem"
sidebarTitle: "Filesystem"
description: "How files move in and out of a box, and how disks persist (or don't) across stop and restart."
icon: "folder"
---

A box has its own filesystem rooted at the image's rootfs. This page is about the three mechanisms you actually have for moving data across that boundary, and what survives a `stop()`.

## Three ways data crosses the boundary

| Mechanism | Direction | Granularity | When to use |
|---|---|---|---|
| `copy_into` / `copy_out` | Both | Files or directories | One-shot transfers; results retrieval; dropping in a script |
| Volume mount | Both, persistent while running | Path-level (host dir ↔ guest dir) | The host is the source of truth (a workspace, an artifact directory) |
| stdin / stdout | Both, in-process | Bytes | Inline data with no on-disk artefact |

`copy_into` / `copy_out` are exposed on the box object in every SDK; volumes are configured at box creation via `BoxOptions.volumes`. See the per-language [SDK reference](/reference/index) for the exact signatures.

## Ephemeral vs persistent disks

Every box has a container disk built from the OCI image. Whether changes outlive the VM depends on the `disk_size_gb` option on `BoxOptions`:

- **`disk_size_gb` not set (default)** — disk lives only as long as the box. `stop()` followed by `auto_remove=true` drops everything. This is the right default for ad-hoc execution.
- **`disk_size_gb = N`** — a persistent QCOW2 image is allocated for the container disk. `stop()` releases the VM but keeps the image; the next `start()` re-uses it. Files written under `/`, installed packages, and any in-place edits all survive.

The on-disk layout per box, under the BoxLite home directory (`~/.boxlite/` by default):

```
~/.boxlite/boxes/{box_id}/
├── disks/
│ ├── disk.qcow2 # live container disk (COW child)
│ └── guest-rootfs.qcow2 # the kernel/init rootfs
└── snapshots/
└── snap-N/disk.qcow2 # immutable per-snapshot disk image
```

QCOW2 means the disk is sparse and grows as you write. Allocating `disk_size_gb=10` does not actually consume 10 GB on the host — it's a ceiling, not an upfront cost.

## Volume mounts

`BoxOptions.volumes` takes a list of `{ host_path, guest_path, read_only }` entries. A volume is the right answer when:

- The host directory is the canonical place where data lives (e.g., a build directory you want to inspect from your editor).
- You want to share data between multiple boxes without an intermediate copy.
- You want write-protected access — set `read_only=true`.

Volumes are wired only while the box is `running`. They are not part of the snapshot, the clone, or the export — they are a host concern, not a box concern.

## Picking between copy and volume

The rule of thumb:

- **Inputs are small and per-task** → `copy_into`. Cheap, isolated, no host coupling.
- **Inputs are big or shared** → volume. The host stays the source of truth; the box reads (or writes) in place.
- **Outputs are artefacts** → `copy_out` after `exec()` finishes — predictable, atomic, simple.
- **Outputs need to be tailed live** → write to a host-side volume, or use `exec` streaming if the data is line-oriented.

## Cross-box sharing

There is no built-in "shared filesystem" between boxes. The supported ways to share data are:

1. A host-side volume mounted into both boxes (the simplest, fastest path).
2. `copy_out` from one, `copy_into` the other.
3. `clone()` if you want two boxes that start from the same point in time but then diverge — see [Snapshot, clone, export](/concepts/snapshot).

## What does and doesn't roll back

A snapshot captures only the **container disk** (`disk.qcow2`), not host volumes or in-flight writes from the guest's page cache. If you `restore()` a snapshot, anything you wrote on a mounted host volume is still there — that's a feature, not a bug.

## See also

- [Tutorials → Upload & download files](/tutorials/file-transfer) — task form, with all three methods.
- [Architecture → Networking & storage](/architecture/networking-storage) — implementation details (QCOW2, COW chains, GC).
- [Snapshot, clone, export](/concepts/snapshot) — what's captured and what isn't.
Loading