From bd88b873243e19993802f2741266d1519a0e24c4 Mon Sep 17 00:00:00 2001 From: Mandalorian-Wang Date: Sun, 26 Apr 2026 00:38:34 +0800 Subject: [PATCH] docs: reshape navigation, add concept/reference content, drop BoxRun MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three things change at once because they're tightly coupled — the new IA only works once the new pages exist, and the writing rules are what kept the new pages from drifting from real SDK behaviour. ──────────────────────────────────────────────────────────────────── 1. Navigation ──────────────────────────────────────────────────────────────────── The single user-facing tab "BoxLite" is reordered into 8 groups in user-cognition order: Introduction → Get Started → Concepts → Tutorials → How-to Guides → SDK Reference → Architecture → Resources. Why: the previous sidebar mixed Tutorial / How-to / Reference / Explanation in one bucket called "Guides", buried the "What is BoxLite / Why BoxLite" story behind installation, and still surfaced BoxRun even though boxlite-ai/boxrun has been archived. Concrete moves: - Promote Concepts to its own top-level group so sandbox primitives (lifecycle / execution / filesystem / network / snapshot) finally have a home that isn't a sub-section of Get Started. - Rename "Guides" → "How-to Guides" so the Diátaxis quadrant is unambiguous from the sidebar label alone. Sharpen tutorials/index and guides/index to state explicitly that tutorials are task-shaped step-by-steps and how-to guides are production patterns. - Move design-principles into Introduction and rename it to "Why BoxLite". Append a competitor-dimension comparison table (deployment model / isolation / state / SDK languages) without naming products. - Hide the Development tab from public navigation. The two internal-facing files (development/cli, development/rust-style) stay in the repo for direct linking but no longer surface in the sidebar. ──────────────────────────────────────────────────────────────────── 2. New pages (8) + content corrections to existing pages ──────────────────────────────────────────────────────────────────── REST API reference (`reference/rest/index.mdx`): - Endpoint inventory taken from the canonical OpenAPI spec. - Every JSON snippet is real curl output captured from a live `boxlite serve --port 8100` running on the validation host — create / start / exec / SSE output / snapshot / metrics / errors. - Documents the URL shape `/v1/{prefix}/...`, the SSE event vocabulary (stdout/stderr base64 + exit with duration_ms+exit_code), and the uniform error envelope `{ error: { message, type, code } }`. 5 concept deep-dives (`concepts/`): - lifecycle: states (configured / running / stopping / stopped / paused / unknown), the valid transition table, and the precondition table for start/stop/remove/exec. - execution: exec vs run, streaming, stdin, kill, resize_tty, timeout patterns, exit-code conventions. - filesystem: copy_in/copy_out vs volume vs base64; ephemeral vs QCOW2-persistent disks; cross-box sharing. - network: enabled/disabled mode, allowlist semantics, port forwarding, host-side secret substitution, no host privileges required. - snapshot: snapshot vs clone vs export, disk-only boundary made explicit (no in-memory checkpoint, no host-volume capture). Tutorial (`tutorials/snapshot-fork-restore.mdx`): - End-to-end clone-based fork walkthrough; full Python script. - Output block in the page is real stdout from a real run, not a synthesized example. Go SDK reference (`reference/go/index.mdx`): - API surface (Runtime, Box, options, NetworkSpec, Secret, ExecResult) documented from the user's perspective. - Quick Start verified end-to-end against the main branch + locally-built native lib. - Honest "Known issue" callout for the published v0.8.2 wire-format mismatch with workaround using a Go module replace. Corrections to existing pages along the way: - Scrub every BoxRun reference from `index.mdx`, `guides/ai-agent- integration.mdx`, `llms.txt`, and `CLAUDE.md`. The product no longer exists as a separate surface. - Trim `getting-started/core-concepts.mdx` to remove the Lifecycle / Images / Resource configuration / Security material that is now better placed in (respectively) the Concepts deep-dives, the per-language quickstarts, How-to Guides → AI Agent Integration, and Architecture → Security. Page keeps its only unique contribution — the SimpleBox / CodeBox / BrowserBox / ComputerBox / InteractiveBox decision table. - Multi-language tab fixes caught while validating Quick Starts: - Python: low-level `Execution.wait()` returns an `ExecResult` that carries only `exit_code` + `error_message`; `stdout` / `stderr` live on the SimpleBox wrapper. Tutorial uses the appropriate pattern (consume stdout iterator, then await wait()). - Node: `SimpleBox.exec(...)` returns `ExecResult` directly — no `.wait()` step. Earlier multi-language tabs had the wrong shape. - Go: SimpleBox-style wrappers don't exist; reference describes the Runtime/Box API only. - `box.snapshot` is a property accessor, not a method (`box.snapshot.create(name=...)`). - `box.stop()` invalidates the handle; reattach via `runtime.get(id)`. ──────────────────────────────────────────────────────────────────── 3. Authoring conventions (CLAUDE.md) ──────────────────────────────────────────────────────────────────── Three rules that apply to every future doc edit: - Ground every factual claim in real code; verify against the SDK or the upstream BoxLite source before writing or editing. Don't document what the code doesn't do. - Stay strictly in the user's SDK frame. No internal source paths, no Rust struct definitions, no `pub` / `Vec` / `Option` syntax in user-facing copy. Allowed external links: SDK module paths users import, README, OpenAPI spec, upstream `examples/`. - Every demo code block must be end-to-end validated. Install the real SDK, run in a clean environment matching the page's stated prerequisites, capture and match actual stdout. If you can't run it, don't ship it as runnable. The Concepts/Network/Snapshot pages were rewritten under these rules — struct snippets reframed as field tables; "MITM proxy" / "gvproxy" / "SIGSTOP" reframed as user-visible behaviour rather than implementation tech. ──────────────────────────────────────────────────────────────────── 4. Polish and asset hygiene ──────────────────────────────────────────────────────────────────── - `getting-started/index.mdx` → `getting-started/install.mdx`. Mintlify was absorbing the `/index`-suffixed page into the group header, leaving "Installation" un-clickable in the sidebar. The new path is a plain Mintlify route. All inbound links rewritten across faq / quickstart-nodejs / guides/index / llms.txt / python & rust references. - Logo and favicon: pull `logo/{light,dark}.png` from boxlite-website/public so docs share the marketing brand assets. Replace the 1 MB `favicon.svg` with the 30 KB `favicon.png` (the same square icon the marketing site uses). ──────────────────────────────────────────────────────────────────── What is intentionally NOT included ──────────────────────────────────────────────────────────────────── - A dedicated `introduction/vs-competitors` page — held back until legal/marketing review. The neutral comparison table inside Why BoxLite is the stand-in. - Splitting `architecture/networking-storage.mdx` into separate networking + storage pages — left for a follow-up after the Concepts pages have stabilised. - DX tooling (Copy-for-LLM button, Mermaid diagrams, RAG search) — not in scope here. ──────────────────────────────────────────────────────────────────── Validation ──────────────────────────────────────────────────────────────────── - `mint broken-links` reports 0 broken links across the whole site. - Python / Node / Go SDK Quick Starts and the snapshot-fork-restore tutorial were each run end-to-end on macOS Apple Silicon against real micro-VMs (`alpine:latest` / `python:slim`). Output captured in pages matches what was observed. - REST page snippets were captured live from `boxlite serve` v0.8.2. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 2 + .mintignore | 5 +- CLAUDE.md | 60 ++++-- concepts/execution.mdx | 109 ++++++++++ concepts/filesystem.mdx | 75 +++++++ concepts/lifecycle.mdx | 83 ++++++++ concepts/network.mdx | 82 +++++++ concepts/snapshot.mdx | 77 +++++++ docs.json | 49 +++-- faq.mdx | 2 +- favicon.png | Bin 0 -> 30630 bytes favicon.svg | 25 --- getting-started/core-concepts.mdx | 136 ++---------- getting-started/design-principles.mdx | 24 ++- getting-started/{index.mdx => install.mdx} | 0 getting-started/quickstart-nodejs.mdx | 2 +- guides/ai-agent-integration.mdx | 6 - guides/index.mdx | 10 +- index.mdx | 13 +- llms.txt | 28 ++- logo/dark.png | Bin 31577 -> 20585 bytes logo/light.png | Bin 29475 -> 25664 bytes reference/go/index.mdx | 235 +++++++++++++++++++++ reference/python/index.mdx | 2 +- reference/rest/index.mdx | 197 +++++++++++++++++ reference/rust/index.mdx | 2 +- tutorials/index.mdx | 10 +- tutorials/snapshot-fork-restore.mdx | 210 ++++++++++++++++++ 28 files changed, 1225 insertions(+), 219 deletions(-) create mode 100644 .gitignore create mode 100644 concepts/execution.mdx create mode 100644 concepts/filesystem.mdx create mode 100644 concepts/lifecycle.mdx create mode 100644 concepts/network.mdx create mode 100644 concepts/snapshot.mdx create mode 100644 favicon.png delete mode 100644 favicon.svg rename getting-started/{index.mdx => install.mdx} (100%) create mode 100644 reference/go/index.mdx create mode 100644 reference/rest/index.mdx create mode 100644 tutorials/snapshot-fork-restore.mdx diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96dfebd --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +content-design/ +.claude/ diff --git a/.mintignore b/.mintignore index 9ee1503..c3465ec 100644 --- a/.mintignore +++ b/.mintignore @@ -4,4 +4,7 @@ # Draft content drafts/ -*.draft.mdx \ No newline at end of file +*.draft.mdx + +# Internal content-design workspace (also git-ignored) +content-design/ \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 3020c71..c5749e1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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`) @@ -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 @@ -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") @@ -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 | diff --git a/concepts/execution.mdx b/concepts/execution.mdx new file mode 100644 index 0000000..0b8ee5b --- /dev/null +++ b/concepts/execution.mdx @@ -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. diff --git a/concepts/filesystem.mdx b/concepts/filesystem.mdx new file mode 100644 index 0000000..f09279b --- /dev/null +++ b/concepts/filesystem.mdx @@ -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. diff --git a/concepts/lifecycle.mdx b/concepts/lifecycle.mdx new file mode 100644 index 0000000..9977257 --- /dev/null +++ b/concepts/lifecycle.mdx @@ -0,0 +1,83 @@ +--- +title: "Box lifecycle" +sidebarTitle: "Lifecycle" +description: "States a box moves through — from create to remove — and which operations are valid in each state." +icon: "circle-nodes" +--- + +A box has a small, well-defined state machine. Reading this page once will save you a lot of guessing about why an SDK call returned `InvalidState`. + +## States + +A box is always in exactly one of these states: + +| State | Meaning | +|---|---| +| `configured` | Persisted to the database; no VM process yet. The state every newly-created box starts in. | +| `running` | VM is alive and the in-guest agent is accepting commands. | +| `stopping` | A graceful shutdown is in progress (transient). | +| `stopped` | VM process terminated; the rootfs and any persistent disk are preserved. The box can be restarted. | +| `paused` | VM frozen via `SIGSTOP` (used internally during snapshot or export for point-in-time consistency). | +| `unknown` | The runtime can't determine the state — only seen during error recovery. | + +## Transitions + +The transitions BoxLite allows are: + +``` +configured ──────► running (start succeeds) +configured ──────► stopped (start fails) +running ──────► stopping ──► stopped +running ──────► stopped (crash) +running ◄──────► paused (snapshot/export pause and resume) +stopped ──────► running (restart) +unknown ──────► any (recovery) +``` + +Anything not in that list is rejected by the SDK with an `InvalidState` error. + +## Which operations are valid in which state + +Each public lifecycle method has a precondition table baked into the state enum: + +| Operation | Valid from | +|---|---| +| `start()` | `configured`, `stopped` | +| `stop()` | `running`, `paused` | +| `remove()` | `configured`, `stopped`, `unknown` | +| `exec()` | `configured`, `running`, `stopped` (for `configured` and `stopped`, an implicit `start()` runs first) | + +Implicit-start on `exec()` is the reason `async with boxlite.SimpleBox(...) as box: await box.exec(...)` works without an explicit `start()` — the wrapper relies on it. + +## Cross-process reattach + +A box's identity is its `box_id` (a ULID-style string the runtime hands you on `create`). Boxes are persisted in the runtime's local SQLite store, so any process running on the same host can reattach by id: + +```python +runtime = boxlite.Boxlite.default() +box = await runtime.get(box_id) # state preserved, no re-create +await box.exec("echo", "still here") +``` + +This works across process restarts and across users sharing the same `home_dir`. It's also how a long-running CLI client reconnects after a crash. + +## Auto-remove and detach + +`BoxOptions` exposes two flags that interact with the lifecycle: + +- `auto_remove` — defaults to `true`. When the box reaches `stopped` and `auto_remove` is set, the runtime also removes it. Most quickstart-style code relies on this behaviour. +- `detach` — defaults to `false`. When set, the box keeps running after the parent process exits; you must reattach by id and stop it explicitly. + +`auto_remove=true` together with `detach=true` is rejected at creation time with a configuration error — a detached box that vanishes when its parent dies cannot be managed. + +## Where each state shows up in the SDKs + +- Python and Node SDKs surface state via `box.info()` / `box.status` and `runtime.list()`. +- The REST API returns the lowercase string (`"configured"`, `"running"`, …) as the `status` field on `GET /v1/{prefix}/boxes/{box_id}`. +- The `paused` state is currently driven only by the runtime itself during snapshot/export. There is no public `pause()` API. + +## See also + +- [Tutorials → Quick Start](/getting-started/quickstart-python) for the happy path through `configured → running → stopped`. +- [Snapshot, clone, export](/concepts/snapshot) — uses the `paused` state internally. +- [Execution](/concepts/execution) — what `exec` actually does, including implicit start. diff --git a/concepts/network.mdx b/concepts/network.mdx new file mode 100644 index 0000000..5679c08 --- /dev/null +++ b/concepts/network.mdx @@ -0,0 +1,82 @@ +--- +title: "Network" +sidebarTitle: "Network" +description: "Outbound allowlists, port forwarding, and host-side secret injection — what the SDK exposes for shaping a box's network." +icon: "network-wired" +--- + +Networking in BoxLite has four moving parts: the bridge that gets traffic in and out, an outbound allowlist, inbound port forwarding, and host-side secret substitution. They compose, but each is small enough to understand on its own. + +## NetworkSpec + +`BoxOptions.network` accepts a network config with two fields: + +| Field | Values | Purpose | +|---|---|---| +| `mode` | `"enabled"` (default) \| `"disabled"` | Whether the guest has any network at all | +| `allow_net` | list of host patterns | Outbound destinations the guest may reach | + +`mode="disabled"` removes the guest interface entirely — the box has no network at all, host or otherwise. `mode="enabled"` is the default and is the only mode that respects `allow_net`. + +### Allowlist semantics + +`allow_net` is a list of host patterns the guest is allowed to reach for outbound traffic. Anything not on the list is dropped before it leaves the host. Patterns are matched against the destination host of HTTP(S) traffic and DNS. + +For an AI-agent box, the typical shape is: + +```python +boxlite.SimpleBox( + image="python:slim", + network=boxlite.NetworkConfig( + mode="enabled", + allow_net=["api.openai.com", "pypi.org", "files.pythonhosted.org"], + ), +) +``` + +If you want a box that genuinely cannot phone home, set `mode="disabled"` — but be aware this also blocks `pip install` and any image-pull-time package fetches. + +## Inbound — port forwarding + +`BoxOptions.ports` is a list of `(host_port, guest_port, protocol)` tuples (`protocol` ∈ `"tcp" | "udp"`). When the box is running, the host port is mapped to the guest port — exactly the model `docker run -p` uses, but on a real micro-VM. + +This is what makes [Tutorials → Run services with port forwarding](/tutorials/long-running-services) work: start a server inside the guest, expose its port, hit it from the host. + +Per-box network metrics are reported back via `BoxMetrics.network_bytes_sent` / `network_bytes_received` (visible on `GET /v1/{prefix}/boxes/{box_id}/metrics`). + +## Secrets — host-side substitution + +`BoxOptions.secrets` is the safe alternative to passing API keys in environment variables. Each entry has four fields: + +| Field | Purpose | +|---|---| +| `name` | Human-readable label, e.g. `"openai_api_key"`. | +| `hosts` | Host patterns where injection happens (e.g. `["api.openai.com"]`). | +| `placeholder` | The string the guest sees. Defaults to ``. | +| `value` | The real value. Never enters the VM. | + +BoxLite intercepts the guest's outbound HTTPS at the host boundary and substitutes the placeholder with the real value, only for matching hosts. Inside the guest you see — and your code logs — only the placeholder string. The real secret never crosses into the VM, so a guest compromise can't exfiltrate it directly. + +Secrets are also surfaced as environment variables. A secret named `openai` with placeholder `` becomes `BOXLITE_SECRET_OPENAI=` inside the box (uppercase, with non-alphanumeric characters in the name replaced by `_`). Guest code can read the env var and embed the placeholder anywhere — Authorization headers, JSON bodies, query strings — and the proxy will rewrite it on the way out. + +## No host-side privileges required + +BoxLite ships its own user-space network bridge, so the host never needs kernel-level networking changes — no TUN devices, no `sudo`, no daemon. Inbound and outbound traffic both pass through this bridge, which is why allowlists and port forwarding work uniformly across macOS, Linux (KVM), and Windows (WSL2). + +## Picking a configuration + +| Scenario | `mode` | `allow_net` | `ports` | `secrets` | +|---|---|---|---|---| +| Untrusted code, no network at all | `Disabled` | — | — | — | +| Untrusted code that needs `pip install` | `Enabled` | `["pypi.org", "files.pythonhosted.org"]` | — | — | +| Agent calling one API | `Enabled` | `["api.openai.com"]` | — | one `Secret` for the API key | +| Run a server, hit it from host | `Enabled` | (set if outbound is needed) | `[(host_port, guest_port, "tcp")]` | — | + +Each row maps directly onto a box-creation call — see the per-language [SDK reference](/reference/index) for the exact field names. + +## See also + +- [Tutorials → Run services with port forwarding](/tutorials/long-running-services) — task form for the inbound case. +- [Architecture → Networking & storage](/architecture/networking-storage) — host-side network bridge and storage internals. +- [Architecture → Security & isolation](/architecture/security) — how the network boundary fits the broader threat model. +- [How-to guides → AI Agent Integration](/guides/ai-agent-integration) — `network_enabled` interaction with security presets. diff --git a/concepts/snapshot.mdx b/concepts/snapshot.mdx new file mode 100644 index 0000000..702b63a --- /dev/null +++ b/concepts/snapshot.mdx @@ -0,0 +1,77 @@ +--- +title: "Snapshot, clone, export" +sidebarTitle: "Snapshot" +description: "Three different ways to capture box state, and what each one can and cannot do." +icon: "camera" +--- + +These are three operations that look similar from the outside but solve different problems. They share one important boundary: **all three are disk-only.** None of them captures memory, page cache, or in-flight I/O. + +## What is captured + +Every snapshot exposes the same shape across the SDKs and the REST API: + +| Field | Purpose | +|---|---| +| `id` | Server-generated identifier | +| `box_id` | The box this snapshot belongs to | +| `name` | Your label, unique per box (≤ 255 characters; `.` and `..` are rejected) | +| `created_at` | Unix timestamp of capture | +| `size_bytes` / `container_disk_bytes` | Disk footprint, in bytes | + +Behind the metadata, BoxLite keeps a single QCOW2 disk image per snapshot under the BoxLite home directory (`~/.boxlite/boxes/{box_id}/snapshots/`). That disk image is the immutable container disk at the moment of capture. Nothing else is captured. + +## What is not captured + +- **Process state.** Running PIDs are not preserved. After `restore()`, the box is brought up like a fresh start — your daemons, shell sessions, and exec'd commands are gone. +- **Memory pages.** No memory checkpoint exists today. `SnapshotOptions` is currently a forward-compatible empty struct in the public SDKs — the field is reserved for future memory-capture work. +- **Host volumes.** Volume mounts are a host concern; they aren't part of the snapshot, so writes to a volume between snapshot and restore are not undone. +- **Open network connections.** Sockets are dropped when the VM shuts down for the snapshot. + +If a workflow assumes "rewind to exactly what was running" — including a half-finished syscall — BoxLite cannot do that today. Plan for "rewind disk state, then re-run from a known prefix instead." + +## Snapshot vs clone vs export + +| Op | Captures | Result | Stays inside the runtime? | +|---|---|---|---| +| `snapshot(name)` | Container disk at point in time | A named entry under the source box | Yes | +| `clone()` | Container disk at point in time | A **new box** with its own `box_id`, ready to run | Yes | +| `export()` / `import()` | Container disk + box config metadata | A portable archive on disk | No (movable across hosts) | + +In other words: + +- **Snapshot** is "I want to roll this *same* box back to here later." It's the cheapest of the three because nothing is duplicated up front. +- **Clone** is "I want a second, independent box that starts from the same disk." Use this when you want to fork execution paths or stamp out N parallel copies. +- **Export/import** is "I want to ship this state to another machine," or archive it. The archive is a regular file you can rsync. + +The three options structs are placeholders today (`SnapshotOptions`, `CloneOptions`, `ExportOptions` are empty in the public SDKs); the operations themselves are real and shipping. + +## Restore is destructive on the source + +`restore(name)` rolls the box's *current* disk back to the snapshot's disk. It is destructive — the writes you've done since the snapshot are gone. Two practical consequences: + +1. The runtime refuses to remove a snapshot whose disk the current box still depends on. The error is explicit: `"current disk depends on this snapshot. Restore a different snapshot first."` +2. If you want to keep both timelines, take a fresh snapshot of the current state first, then restore. + +If you want both branches to remain live and runnable in parallel, use `clone` instead of `snapshot + restore`. + +## State machine interaction + +Snapshot, clone, and export all need a quiescent disk. The SDK briefly moves the box through the `paused` state for the duration of the operation, then returns it to `running`. This is one of the only places the `paused` state shows up in the public lifecycle (see [Lifecycle](/concepts/lifecycle)). + +## Where each operation surfaces + +| Surface | Snapshot create | Snapshot restore | Clone | Export / Import | +|---|---|---|---|---| +| Python | `await box.snapshot(name)` / `await box.restore(name)` | yes | `await box.clone()` | `await box.export(path)` / `runtime.import_box(path)` | +| Node.js | same shape | yes | `await box.clone()` | `await box.export(path)` | +| REST | `POST .../boxes/{id}/snapshots` | `POST .../snapshots/{name}/restore` | `POST .../clone` | `POST .../export`, `POST .../boxes/import` | +| CLI | `boxlite snapshot ...` | `boxlite restore ...` | `boxlite clone ...` | `boxlite export ...` / `boxlite import ...` | + +For a full end-to-end walkthrough, see [Tutorials → Snapshot, fork, restore](/tutorials/snapshot-fork-restore). + +## See also + +- [Lifecycle](/concepts/lifecycle) — the `paused` state that snapshots use under the hood. +- [Filesystem](/concepts/filesystem) — what's on `disk.qcow2` and what's outside it. +- [REST API reference → Snapshots](/reference/rest/index#snapshots-over-rest) — the wire format. diff --git a/docs.json b/docs.json index 59d0ac3..6ce66be 100644 --- a/docs.json +++ b/docs.json @@ -7,23 +7,38 @@ "light": "#FFFFFF", "dark": "#000000" }, - "favicon": "/favicon.svg", + "favicon": "/favicon.png", "navigation": { "tabs": [ { "tab": "BoxLite", "groups": [ { - "group": "Getting Started", + "group": "Introduction", "pages": [ "index", - "getting-started/index", + "getting-started/design-principles" + ] + }, + { + "group": "Get Started", + "pages": [ + "getting-started/install", "getting-started/quickstart-python", "getting-started/quickstart-nodejs", "getting-started/quickstart-rust", - "getting-started/quickstart-c", + "getting-started/quickstart-c" + ] + }, + { + "group": "Concepts", + "pages": [ "getting-started/core-concepts", - "getting-started/design-principles" + "concepts/lifecycle", + "concepts/execution", + "concepts/filesystem", + "concepts/network", + "concepts/snapshot" ] }, { @@ -39,11 +54,12 @@ "tutorials/interactive-terminal", "tutorials/run-any-language", "tutorials/long-running-services", + "tutorials/snapshot-fork-restore", "tutorials/error-handling" ] }, { - "group": "Guides", + "group": "How-to Guides", "pages": [ "guides/index", "guides/ai-agent-integration", @@ -95,6 +111,20 @@ "reference/c/memory-json-threading", "reference/c/errors-metrics" ] + }, + { + "group": "Go SDK", + "icon": "golang", + "pages": [ + "reference/go/index" + ] + }, + { + "group": "REST API", + "icon": "globe", + "pages": [ + "reference/rest/index" + ] } ] }, @@ -113,13 +143,6 @@ "faq", "guides/changelog" ] - }, - { - "group": "Development", - "pages": [ - "development/cli", - "development/rust-style" - ] } ] } diff --git a/faq.mdx b/faq.mdx index 318472a..b6459c3 100644 --- a/faq.mdx +++ b/faq.mdx @@ -756,7 +756,7 @@ Frequently asked questions and common issues with BoxLite. **Documentation:** - - [Getting Started](/getting-started/index) -- Quick onboarding + - [Getting Started](/getting-started/install) -- Quick onboarding - [How-to Guides](/guides/index) -- Practical guides - [SDK Reference](/reference/index) -- API and configuration reference - [Architecture](/architecture/index) -- How BoxLite works diff --git a/favicon.png b/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..b74b0b48cd185ff49f9ce582c0ab02fa8f6651a0 GIT binary patch literal 30630 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 literal 0 HcmV?d00001 diff --git a/favicon.svg b/favicon.svg deleted file mode 100644 index 6c82b77..0000000 --- a/favicon.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - diff --git a/getting-started/core-concepts.mdx b/getting-started/core-concepts.mdx index 3b25b17..24c9278 100644 --- a/getting-started/core-concepts.mdx +++ b/getting-started/core-concepts.mdx @@ -81,131 +81,21 @@ async with boxlite.SimpleBox(image="python:slim") as box: **For BrowserBox and ComputerBox, technically yes but practically no.** You could use SimpleBox with the same images (e.g. `SimpleBox(image="mcr.microsoft.com/playwright:v1.58.0-jammy")`), but you'd have to manually set up port forwarding, start the browser server, wait for it to be ready, and connect — all things that BrowserBox handles in a single `connect()` call. The specialized APIs (`screenshot()`, `mouse_move()`, `playwright_endpoint()`, etc.) save significant work. -## Lifecycle +## Primitive deep-dives -Every box follows the same lifecycle: **create → use → stop**. +Box types are the *shape* of a sandbox. The five concept pages below cover the *primitives* every box exposes — read them in order if you want a complete mental model, or jump to whichever topic you're about to use. -### Context manager (recommended) +- [Lifecycle](/concepts/lifecycle) — the box state machine, valid transitions, and what `auto_remove` and `detach` actually do +- [Execution](/concepts/execution) — `exec` vs `run`, streaming output, stdin, timeouts, PTY, exit codes +- [Filesystem](/concepts/filesystem) — `copy_in` / `copy_out`, volume mounts, ephemeral vs persistent disks +- [Network](/concepts/network) — outbound allowlists, port forwarding, host-side secret injection +- [Snapshot](/concepts/snapshot) — snapshot vs clone vs export, and the disk-only boundary -The simplest pattern uses `async with`, which handles startup and cleanup automatically: +Anything related to install, resource sizing, or low-level architecture lives elsewhere: -```python -async with boxlite.SimpleBox(image="python:slim") as box: - await box.exec("echo", "hello") - # Box is automatically stopped when the block exits -``` - -### Manual lifecycle - -When you need more control (long-lived boxes, cross-function usage), manage the lifecycle explicitly: - - - - ```python - box = boxlite.SimpleBox(image="python:slim") - await box.start() - - try: - await box.exec("echo", "hello") - finally: - await box.shutdown() - ``` - - - ```typescript - const box = new SimpleBox({ image: 'python:slim' }); - - try { - const result = await box.exec('echo', 'hello'); - console.log(result.stdout); - } finally { - await box.stop(); - } - ``` - - - -### What happens at each stage - -| Stage | What happens | -|-------|-------------| -| **Create** | BoxLite pulls the OCI image (cached after first use), allocates resources, and boots a microVM. This typically takes under a second for cached images. | -| **Use** | Run commands with `exec()` (SimpleBox, CodeBox, InteractiveBox) or `run()` (CodeBox only). Use specialized methods for BrowserBox (`connect()`) and ComputerBox (`screenshot()`, `mouse_move()`, etc.). Files you create and packages you install persist within the box. Note: each `CodeBox.run()` call starts a new Python process, so Python variables and function definitions do not carry over between calls. | -| **Stop** | The VM shuts down and all resources are released. With `auto_remove=True` (the default), the box is fully cleaned up. | - -## Images - -BoxLite uses standard **OCI container images** — the same images you use with Docker. For box types that accept a custom image (SimpleBox, CodeBox, InteractiveBox), specify it when creating the box: - -```python -# Docker Hub images — use any image you want -boxlite.SimpleBox(image="python:slim") -boxlite.SimpleBox(image="alpine:latest") -boxlite.SimpleBox(image="ubuntu:22.04") -boxlite.SimpleBox(image="node:20-slim") - -# CodeBox defaults to python:slim — no image needed -boxlite.CodeBox() -``` - -BrowserBox and ComputerBox use **fixed images** (Playwright and webtop respectively) that can't be changed, because their convenience methods depend on specific software being pre-installed. - -The image is pulled on first use and cached locally. Subsequent starts reuse the cached image. - - - Use slim or minimal images (`python:slim`, `alpine:latest`) for faster boot times. Full images like `ubuntu:latest` work but are larger and slower to pull. - - -## Resource configuration - -Every box accepts CPU and memory parameters. The example below uses SimpleBox/CodeBox/InteractiveBox parameter names: - -```python -boxlite.SimpleBox( - image="python:slim", - cpus=2, # CPU cores (default: 1) - memory_mib=4096, # Memory in MiB (default: 2048) - disk_size_gb=10, # Persistent disk in GB (default: ephemeral) -) -``` - -| Parameter | Default | Range | Notes | -|-----------|---------|-------|-------| -| `cpus` | 1 | 1 to host CPU count | More CPUs help with parallel workloads | -| `memory_mib` | 2048 | 128–65536 | Increase for data-heavy workloads or large packages | -| `disk_size_gb` | None (ephemeral) | Any positive integer | Set only if you need data to survive a restart | - - -BrowserBox and ComputerBox use slightly different parameter names: `cpu` (singular) and `memory` (instead of `memory_mib`). See their [SDK reference](/reference/python/box-types) pages for details. - - -For most use cases, the defaults work well. Increase memory when running data-heavy code or installing large packages, and add CPUs for compute-intensive workloads. - -## Security model - -Each box is a real **microVM** — not a container. BoxLite provides multiple layers of isolation: - -- **Hardware virtualization** — KVM on Linux, Hypervisor.framework on macOS. Each box has its own kernel. -- **Jailer** — restricts the VM process with seccomp filters and cgroups. -- **Network isolation** — boxes get their own network namespace by default. - -You can tune security with `SecurityOptions` presets: - -```python -from boxlite.boxlite import SecurityOptions - -async with boxlite.SimpleBox( - image="python:slim", - security=SecurityOptions.maximum(), # Maximum isolation -) as box: - await box.exec("echo", "locked down") -``` - - -`SecurityOptions` is currently imported from `boxlite.boxlite`, not from the top-level `boxlite` module. Available presets: `development()`, `standard()`, `maximum()`. - - -For full details on the security architecture, see [Security](/architecture/security). +- Install + verify your platform → [Installation](/getting-started/install) +- Pick CPU / memory / disk for production agents → [How-to guides → AI Agent Integration](/guides/ai-agent-integration#recommended-configuration) +- The defense-in-depth security model → [Architecture → Security](/architecture/security) ## What's next? @@ -219,7 +109,7 @@ For full details on the security architecture, see [Security](/architecture/secu Full API reference for all box types. - - How BoxLite works under the hood — microVMs, the jailer, networking. + + Components, isolation layers, networking, and storage. diff --git a/getting-started/design-principles.mdx b/getting-started/design-principles.mdx index 8e94c4c..1896623 100644 --- a/getting-started/design-principles.mdx +++ b/getting-started/design-principles.mdx @@ -1,10 +1,11 @@ --- -title: "Design principles" -description: "The ideas behind BoxLite's architecture and API decisions." +title: "Why BoxLite" +sidebarTitle: "Why BoxLite" +description: "The ideas behind BoxLite — embeddable, stateful, snapshottable, hardware-isolated — and how it differs from other sandboxes." icon: "compass" --- -# Design principles +# Why BoxLite BoxLite is built on a simple belief: AI agents deserve sandboxes that are secure, stateful, and trivially easy to adopt. Every design decision we make traces back to four principles. @@ -77,3 +78,20 @@ Together they define what we think a sandbox for AI agents should be: something ``` $ pip install boxlite ``` + +--- + +## How BoxLite compares to other sandboxes + +A neutral, dimension-by-dimension look at where BoxLite sits in the landscape. We deliberately do not name specific products here — sandbox tools evolve fast, and the dimensions matter more than the names of the moment. + +| Dimension | Hosted cloud sandbox | Container-based local sandbox | Process-level sandbox | **BoxLite** | +|---|---|---|---|---| +| Deployment model | Cloud account required | Daemon or root often required | In-process | **Library import, no daemon, no root** | +| Isolation level | VM or strong container | Shared host kernel | Same process / user | **Hardware-virtualized micro-VM** | +| State persistence | Often ephemeral | Sometimes; image rebuild common | None | **Stateful by default, survives stop/restart** | +| Snapshot / fork | Varies, often paid feature | Rare | None | **First-class disk snapshots** | +| Multi-language SDK | Usually 1–2 languages | N/A | Language-bound | **Python, Node.js, Rust, Go, C** | +| Where it runs | Vendor cloud | Your machine + daemon | Your process | **Your machine, your code, no extra services** | + +BoxLite's wedge is the combination: VM-grade isolation, embedded SDK ergonomics, and stateful boxes — without depending on a hosted control plane. Hosted offerings remain a good fit when fleet management and remote scale are the primary needs. BoxLite is for the rest of the time, when you want sandboxing to behave like SQLite: local, embedded, and out of the way. diff --git a/getting-started/index.mdx b/getting-started/install.mdx similarity index 100% rename from getting-started/index.mdx rename to getting-started/install.mdx diff --git a/getting-started/quickstart-nodejs.mdx b/getting-started/quickstart-nodejs.mdx index c921ae7..c3c0238 100644 --- a/getting-started/quickstart-nodejs.mdx +++ b/getting-started/quickstart-nodejs.mdx @@ -13,7 +13,7 @@ npm install @boxlite-ai/boxlite **Requirements:** - Node.js 18 or later -- Platform with hardware virtualization (see [Installation](/getting-started/index#system-requirements)) +- Platform with hardware virtualization (see [Installation](/getting-started/install#system-requirements)) **Verify Installation:** diff --git a/guides/ai-agent-integration.mdx b/guides/ai-agent-integration.mdx index c5e69d6..2ed5b79 100644 --- a/guides/ai-agent-integration.mdx +++ b/guides/ai-agent-integration.mdx @@ -7,10 +7,6 @@ icon: "robot" This guide covers production patterns for deploying BoxLite as a sandboxed execution environment for AI agents. It assumes you've already worked through the [Tutorials](/tutorials/index) and know how to create boxes, run code, and transfer files. - - **Using BoxRun?** If your agent communicates via HTTP, [BoxRun's REST API](/boxrun/rest-api) or [Python SDK](/boxrun/python-sdk) may be a simpler integration path. BoxRun handles sandbox lifecycle, file upload/download, and SSE streaming out of the box. See the [AI agent patterns](/boxrun/python-sdk#ai-agent-patterns) section in the BoxRun SDK docs. - - ## Recommended Configuration ### Workload-Type Reference @@ -645,7 +641,5 @@ Putting it all together: security configuration, concurrent execution with timeo ## See also - [Tutorials](/tutorials/index) — Step-by-step walkthroughs for code execution, file transfer, LLM integration, and browser automation -- [BoxRun Python SDK — AI agent patterns](/boxrun/python-sdk#ai-agent-patterns) — Agent integration via REST API -- [BoxRun REST API](/boxrun/rest-api) — HTTP endpoints for sandbox management - [SDK Reference](/reference/index) — Full API reference for all BoxLite SDKs - [Architecture](/architecture/index) — How BoxLite isolation works diff --git a/guides/index.mdx b/guides/index.mdx index df8b0ed..01dce4d 100644 --- a/guides/index.mdx +++ b/guides/index.mdx @@ -1,11 +1,13 @@ --- -title: "Guides" +title: "How-to guides" sidebarTitle: "Overview" -description: "Practical how-to guides for building, configuring, and integrating BoxLite into your projects." +description: "Production-grade how-to guides for building, configuring, and integrating BoxLite — picking up where the tutorials leave off." icon: "book-open" --- -These guides cover advanced topics: building from source, AI agent integration patterns, image registry configuration, and debugging. Each guide is self-contained and can be read independently. +How-to guides solve a different problem from tutorials. **Tutorials teach you to complete a specific task** (run AI-generated code, automate a browser, transfer files). **How-to guides show you how to run BoxLite well in production** — handling concurrency, configuring security, choosing image registries, debugging the host sandbox. They assume you've already done the basics and want to go deeper. + +Each guide is self-contained and can be read independently. Looking for step-by-step task tutorials? See the [Tutorials](/tutorials/index) section for code execution, file transfer, LLM integration, and browser automation walkthroughs. @@ -56,5 +58,5 @@ These guides cover advanced topics: building from source, AI agent integration p The source README also covers networking, volume mounting, debugging, resource tuning, deployment patterns, and integration examples. These topics are woven into the guides above and the [Architecture](/architecture/index) and [SDK Reference](/reference/index) sections. - New to BoxLite? Start with the [Getting Started](/getting-started/index) guide, then work through the [Tutorials](/tutorials/index) before diving into these advanced guides. + New to BoxLite? Start with the [Getting Started](/getting-started/install) guide, then work through the [Tutorials](/tutorials/index) before diving into these advanced guides. diff --git a/index.mdx b/index.mdx index b4ab221..3e4b406 100644 --- a/index.mdx +++ b/index.mdx @@ -58,17 +58,6 @@ Runs on macOS (Apple Silicon), Linux (KVM), and Windows (WSL2). -## BoxRun — sandbox management - -**BoxRun** is the management layer for BoxLite sandboxes. Create, list, stop, and delete boxes through a CLI, REST API, Python SDK, or web dashboard — all in a single binary. - -```bash -curl -fsSL https://boxlite.ai/boxrun/install | sh -boxrun shell ubuntu -``` - -BoxLite creates and runs micro-VMs. BoxRun manages them — tracking state, persisting data, and exposing operations over HTTP and CLI. - ## SDKs and platforms | SDK | Install | @@ -90,7 +79,7 @@ BoxLite creates and runs micro-VMs. BoxRun manages them — tracking state, pers Execute code, connect LLMs, transfer files, and automate browsers — step-by-step. - + AI agent integration, security configuration, and production patterns. diff --git a/llms.txt b/llms.txt index 083b861..570a284 100644 --- a/llms.txt +++ b/llms.txt @@ -2,14 +2,21 @@ > BoxLite is a local-first micro-VM sandbox for AI agents — stateful, lightweight, hardware-level isolation, no daemon required. -## Getting Started +## Introduction - [Introduction](/index.mdx): BoxLite overview, features, and quick start -- [Installation](/getting-started/index.mdx): Install BoxLite for Python, Node.js, Rust, or C +- [Why BoxLite](/getting-started/design-principles.mdx): Design principles and how BoxLite compares to other sandboxes + +## Get Started + +- [Installation](/getting-started/install.mdx): Install BoxLite for Python, Node.js, Rust, or C - [Python Quick Start](/getting-started/quickstart-python.mdx): Get started with BoxLite in Python - [Node.js Quick Start](/getting-started/quickstart-nodejs.mdx): Get started with BoxLite in Node.js - [Rust Quick Start](/getting-started/quickstart-rust.mdx): Get started with BoxLite in Rust - [C Quick Start](/getting-started/quickstart-c.mdx): Get started with BoxLite in C + +## Concepts + - [Core Concepts](/getting-started/core-concepts.mdx): Boxes, images, execution model, and lifecycle ## Tutorials @@ -26,14 +33,13 @@ - [Run services with port forwarding](/tutorials/long-running-services.mdx): Start servers, forward ports, access from host - [Handle errors and debug](/tutorials/error-handling.mdx): Exit codes, exceptions, timeouts, streaming -## Guides +## How-to Guides -- [Guides Overview](/guides/index.mdx): Advanced patterns and configuration guides +- [How-to Guides Overview](/guides/index.mdx): Production patterns and configuration guides - [AI Agent Integration](/guides/ai-agent-integration.mdx): Concurrency, timeouts, security presets - [Building from Source](/guides/building-from-source.mdx): Compile BoxLite from source - [Image Registry Configuration](/guides/image-registry-configuration.mdx): Configure OCI image registries - [macOS Sandbox Debugging](/guides/macos-sandbox-debugging.mdx): Debug sandbox issues on macOS -- [Changelog](/guides/changelog.mdx): Release history ## SDK Reference @@ -74,15 +80,7 @@ - [Security & Isolation](/architecture/security.mdx): Security model and isolation guarantees - [Networking & Storage](/architecture/networking-storage.mdx): Network and storage configuration -## BoxRun - -- [BoxRun Overview](/boxrun/index.mdx): Managed sandbox platform -- [BoxRun Quick Start](/boxrun/quickstart.mdx): Get started with BoxRun -- [BoxRun CLI](/boxrun/cli.mdx): Command-line reference -- [BoxRun Python SDK](/boxrun/python-sdk.mdx): Python SDK for BoxRun -- [BoxRun REST API](/boxrun/rest-api.mdx): REST API reference -- [BoxRun Configuration](/boxrun/configuration.mdx): Configuration options - -## FAQ +## Resources - [FAQ & Troubleshooting](/faq.mdx): Common questions and solutions +- [Changelog](/guides/changelog.mdx): Release history diff --git a/logo/dark.png b/logo/dark.png index 438cc083afeb5963617679cda794378dcd157ef1..2e6264ec784f9e51a24c9cb461da5889010606c2 100644 GIT binary patch literal 20585 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 F{SQ>i6G#96 literal 31577 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 xE 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%0DR15Q=Tm5_RTnWQ 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 diff --git a/logo/light.png b/logo/light.png index 722e2fbe5a61c73352c86fb1eeb074191f540228..f325a13cfc4e0b2f44e9a0fbc399179c48aafd6d 100644 GIT binary patch literal 25664 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 D&*+U% literal 29475 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 z�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 + Requires Go 1.24+ with CGO enabled. The first install downloads a prebuilt native library (`libboxlite.a`, ~110 MB on darwin-arm64) into your Go module cache. Set `GITHUB_TOKEN` if you hit GitHub API rate limits during setup. + + +## Install + +```bash +go get github.com/boxlite-ai/boxlite/sdks/go +go run github.com/boxlite-ai/boxlite/sdks/go/cmd/setup +``` + +The `cmd/setup` step is one-time per module cache: it downloads the prebuilt native library and header for your platform and places them next to the Go sources. + +## Quick start + +```go main.go +package main + +import ( + "context" + "fmt" + "log" + + boxlite "github.com/boxlite-ai/boxlite/sdks/go" +) + +func main() { + rt, err := boxlite.NewRuntime() + if err != nil { + log.Fatal(err) + } + defer rt.Close() + + ctx := context.Background() + box, err := rt.Create(ctx, "python:slim", + boxlite.WithNetwork(boxlite.NetworkSpec{Mode: boxlite.NetworkModeEnabled}), + ) + if err != nil { + log.Fatal(err) + } + defer rt.Remove(ctx, box.ID()) + + if err := box.Start(ctx); err != nil { + log.Fatal(err) + } + defer box.Stop(ctx) + + result, err := box.Exec(ctx, "python", "-c", "print('Hello from BoxLite Go!')") + if err != nil { + log.Fatal(err) + } + fmt.Println("STDOUT:", result.Stdout) + fmt.Println("EXIT:", result.ExitCode) +} +``` + +Run it: + +```bash +go run main.go +# STDOUT: Hello from BoxLite Go! +# +# EXIT: 0 +``` + +The output above was captured live on macOS Apple Silicon against the `main` branch of the BoxLite repository. See [Known issue](#known-issue-boxlite-v0-8-2-release) below for what to do on the released `v0.8.2`. + +## Runtime + +```go +rt, err := boxlite.NewRuntime(opts ...RuntimeOption) (*Runtime, error) +``` + +| Method | Purpose | +|---|---| +| `rt.Create(ctx, image, opts...)` | Create a new box; returns `*Box` | +| `rt.Get(ctx, idOrName)` | Reattach to an existing box by id or name | +| `rt.ListInfo(ctx)` | Snapshot of all boxes' metadata | +| `rt.Remove(ctx, idOrName)` | Remove a stopped box | +| `rt.ForceRemove(ctx, idOrName)` | Remove regardless of state | +| `rt.Metrics(ctx)` | Aggregate runtime metrics | +| `rt.Images()` | Image manager (`Pull`, `List`) | +| `rt.Close()` | Release the runtime lock; required for clean shutdown | +| `rt.Shutdown(ctx, timeout)` | Graceful shutdown of all running boxes | + +### Runtime options + +| Option | Effect | +|---|---| +| `WithHomeDir(dir)` | Override `$BOXLITE_HOME` (default `~/.boxlite`) | +| `WithRegistries(refs...)` | Add custom OCI registry hosts | + +## Box + +Returned by `rt.Create` and `rt.Get`. Methods: + +| Method | Purpose | +|---|---| +| `box.ID() string` | Box id (ULID) | +| `box.Name() string` | Box name (empty if unset) | +| `box.Start(ctx) error` | Start a `configured` or `stopped` box | +| `box.Stop(ctx) error` | Stop a `running` box | +| `box.Close() error` | Release the box handle (does not stop the box) | +| `box.Info(ctx) (*BoxInfo, error)` | Current state, PID, image, resources | +| `box.Metrics(ctx) (*BoxMetrics, error)` | Per-box metrics (CPU, memory, network, exec counters) | +| `box.Exec(ctx, name string, args ...string) (*ExecResult, error)` | Run one command and collect stdout/stderr/exit | +| `box.Command(name string, args ...string) *Cmd` | Build a configurable command (see `Cmd` below) | + +`Exec` is the simple, buffered form — it returns once the guest process exits and gives you the full output. Use `Command` when you need to set environment variables, capture streams differently, or kill mid-run. + +### Box options + +The full list: + +| Option | Effect | +|---|---| +| `WithName(string)` | Set a unique box name | +| `WithCPUs(n int)` | Number of CPU cores | +| `WithMemory(mib int)` | Memory in MiB | +| `WithRootfsPath(string)` | Use a pre-prepared rootfs directory instead of an image | +| `WithEnv(key, value string)` | Append an environment variable (call repeatedly) | +| `WithVolume(host, guest string)` | Read-write bind mount | +| `WithVolumeReadOnly(host, guest string)` | Read-only bind mount | +| `WithWorkDir(string)` | Working directory inside the guest | +| `WithEntrypoint(args ...string)` | Override the image entrypoint | +| `WithCmd(args ...string)` | Override the image cmd | +| `WithNetwork(NetworkSpec)` | Outbound mode + allowlist (see below) | +| `WithSecret(Secret)` | Host-side HTTPS secret substitution | +| `WithAutoRemove(bool)` | Auto-remove on stop (default `true`) | +| `WithDetach(bool)` | Survive parent exit (default `false`); incompatible with `WithAutoRemove(true)` | + +## Cmd — fine-grained execution + +`box.Command(name, args...)` returns a `Cmd` that mirrors the shape of `os/exec.Cmd`: + +```go +cmd := box.Command("python", "-c", "print('hi')") +cmd.Env = []string{"FOO=bar"} +cmd.Stdout = os.Stdout // any io.Writer +cmd.Stderr = os.Stderr +if err := cmd.Run(ctx); err != nil { ... } +``` + +Use it when streaming directly into Go writers, when you need to attach stdin (`cmd.Stdin = ...`), or when you want fine-grained signal control. + +## Networking + +```go +type NetworkSpec struct { + Mode NetworkMode + AllowNet []string +} + +const ( + NetworkModeEnabled NetworkMode = "Enabled" + NetworkModeDisabled NetworkMode = "Disabled" +) +``` + +`Disabled` removes the guest network interface entirely. `Enabled` is the default and is the only mode that respects `AllowNet`. See [Network](/concepts/network) for the broader picture. + +## Secrets + +```go +type Secret struct { + Name string + Value string + Hosts []string + Placeholder string // default "" +} +``` + +`WithSecret(...)` enables host-side substitution: the placeholder string in your guest's outbound HTTPS traffic is rewritten to `Value` only for matching `Hosts`. The real secret never enters the VM. Same model as the Python and Node SDKs — see [Network → Secrets](/concepts/network#secrets-host-side-substitution). + +## Result types + +```go +type ExecResult struct { + ExitCode int + Stdout string + Stderr string +} + +type BoxInfo struct { + ID string + Name string + Image string + State State // configured / running / stopping / stopped / paused / unknown + Running bool + PID int + CPUs int + MemoryMiB int + CreatedAt int64 +} +``` + +Aggregate `RuntimeMetrics` and per-box `BoxMetrics` follow the same shape as the metrics surfaced over REST. + +## Parity with other SDKs + +The Go SDK currently does **not** expose the high-level box-type wrappers (`SimpleBox`, `CodeBox`, `BrowserBox`, `ComputerBox`, `InteractiveBox`) that the Python and Node.js SDKs offer. Those wrappers are a thin convenience layer; their behaviour is reproducible in Go by combining `rt.Create` + `WithCmd` / `WithEntrypoint` and the matching image. The runtime is the same. + +For runtime-level parity (lifecycle, exec, file transfer, snapshot, clone, export, networking, secrets), the Go SDK is on par with Python and Node. + +## Known issue: BoxLite v0.8.2 release + +The released `v0.8.2` of the Go SDK and its bundled native library disagree on the network-mode wire format, so box creation against `v0.8.2` fails immediately with: + +``` +boxlite: internal error: Invalid JSON options: unknown variant `Isolated`, +expected `Enabled` or `Disabled` (code=1) +``` + +The fix is already on the `main` branch (the API on this page — `WithNetwork(NetworkSpec)`, `WithSecret(Secret)`, etc. — is what `main` exports) and will ship in the next tagged release. To use the SDK today, clone the repository and point a Go module replace at the local checkout: + +```text +replace github.com/boxlite-ai/boxlite/sdks/go => /path/to/boxlite/sdks/go +``` + +The same checkout produces a matching native library via the repository's `make dev:go` target. + +## See also + +- [SDK reference overview](/reference/index) — feature comparison across all SDKs +- [Python SDK](/reference/python/index) and [Node.js SDK](/reference/nodejs/index) — the reference shapes the Go SDK mirrors +- [Network](/concepts/network) and [Lifecycle](/concepts/lifecycle) — primitives that map 1:1 across SDKs diff --git a/reference/python/index.mdx b/reference/python/index.mdx index 738298e..31afa75 100644 --- a/reference/python/index.mdx +++ b/reference/python/index.mdx @@ -257,5 +257,5 @@ Default values used by BoxLite. - [Box Types](/reference/python/box-types) - SimpleBox, CodeBox, BrowserBox, ComputerBox, InteractiveBox - [Execution](/reference/python/execution) - Command execution, streaming I/O, stdin/stdout/stderr - [Errors & Metrics](/reference/python/errors-metrics) - Exception hierarchy, metrics classes -- [Getting Started](/getting-started/index) - Installation and quickstart guide +- [Getting Started](/getting-started/install) - Installation and quickstart guide - [Configuration Reference](/reference/index) - BoxOptions details diff --git a/reference/rest/index.mdx b/reference/rest/index.mdx new file mode 100644 index 0000000..58c13b9 --- /dev/null +++ b/reference/rest/index.mdx @@ -0,0 +1,197 @@ +--- +title: "REST API reference" +sidebarTitle: "REST API" +description: "HTTP endpoints exposed by `boxlite serve` — language-neutral access to BoxLite from any client." +icon: "globe" +--- + +`boxlite serve` starts a long-running HTTP server that exposes the full BoxLite runtime over REST. Use it when you want to drive boxes from a language without a first-party SDK, or when you want a single process to mediate many short-lived clients. + +The full request and response schemas live in the OpenAPI specification at [`openapi/rest-sandbox-open-api.yaml`](https://github.com/boxlite-ai/boxlite/blob/main/openapi/rest-sandbox-open-api.yaml). This page summarises the surface and walks through the create → start → exec → cleanup flow. + +## Start the server + +```bash +boxlite serve --port 8100 +# BoxLite REST API server listening on http://0.0.0.0:8100 +``` + +The CLI binary ships separately from the SDKs. Install it with `cargo install boxlite-cli` or download the prebuilt archive for your platform from the [BoxLite releases page](https://github.com/boxlite-ai/boxlite/releases). + +## URL shape + +Every runtime endpoint is prefixed by an API version and a tenant identifier: + +``` +/v1/{prefix}/... +``` + +For a single-host installation, `prefix` is always `default`. The two unprefixed endpoints are `/v1/config` (server capabilities) and `/v1/oauth/tokens` (authentication). + + + The current single-host server assumes a single trusted user. The authentication endpoint is wired but not enforced for local installations. Treat the server as you would any local-only daemon — do not expose `0.0.0.0:8100` to a network you don't control. + + +## Endpoint groups + +| Group | Endpoints | +|---|---| +| Configuration | `GET /v1/config` | +| Authentication | `POST /v1/oauth/tokens` | +| Boxes | `POST /v1/{prefix}/boxes`, `GET /v1/{prefix}/boxes`, `GET /v1/{prefix}/boxes/{box_id}`, `HEAD /v1/{prefix}/boxes/{box_id}`, `DELETE /v1/{prefix}/boxes/{box_id}`, `POST /v1/{prefix}/boxes/{box_id}/start`, `POST /v1/{prefix}/boxes/{box_id}/stop` | +| Execution | `POST /v1/{prefix}/boxes/{box_id}/exec`, `GET /v1/{prefix}/boxes/{box_id}/executions/{exec_id}`, `GET /v1/{prefix}/boxes/{box_id}/executions/{exec_id}/output` (SSE), `POST .../input`, `POST .../signal`, `POST .../resize`, `WS /v1/{prefix}/boxes/{box_id}/exec/tty` | +| Files | `POST /v1/{prefix}/boxes/{box_id}/files` (upload), `GET /v1/{prefix}/boxes/{box_id}/files` (download) | +| Snapshots | `POST /v1/{prefix}/boxes/{box_id}/snapshots`, `GET .../snapshots`, `GET .../snapshots/{name}`, `DELETE .../snapshots/{name}`, `POST .../snapshots/{name}/restore` | +| Clone & archive | `POST /v1/{prefix}/boxes/{box_id}/clone`, `POST /v1/{prefix}/boxes/{box_id}/export`, `POST /v1/{prefix}/boxes/import` | +| Images | `POST /v1/{prefix}/images/pull`, `GET /v1/{prefix}/images`, `GET /v1/{prefix}/images/{image_id}`, `HEAD /v1/{prefix}/images/{image_id}` | +| Metrics | `GET /v1/{prefix}/metrics`, `GET /v1/{prefix}/boxes/{box_id}/metrics` | + +## Server capabilities + +Probe the server before driving any flow — capability flags tell you which optional surfaces are wired. + +```bash +curl -s http://localhost:8100/v1/config +``` + +```json +{ + "capabilities": { + "snapshots_enabled": true, + "clone_enabled": true, + "export_enabled": true, + "import_enabled": true + } +} +``` + +## End-to-end flow + +The four steps below match what every SDK does internally. + +### 1. Create a box + +```bash +curl -s -X POST http://localhost:8100/v1/default/boxes \ + -H 'Content-Type: application/json' \ + -d '{"rootfs":{"image":"alpine:latest"},"network":"Enabled"}' +``` + +```json +{ + "box_id": "k14sYHce2Rqi", + "name": null, + "status": "configured", + "created_at": "2026-04-25T15:48:04.718781+00:00", + "updated_at": "2026-04-25T15:48:04.719016+00:00", + "pid": null, + "image": "alpine:latest", + "cpus": 2, + "memory_mib": 512, + "labels": {} +} +``` + +The returned `box_id` is the only identifier you need afterwards. `status: "configured"` means the VM is provisioned but not yet booted. + +### 2. Start the box + +```bash +curl -s -X POST http://localhost:8100/v1/default/boxes/k14sYHce2Rqi/start +``` + +```json +{ + "box_id": "k14sYHce2Rqi", + "status": "running", + "pid": 39338, + "image": "alpine:latest", + "cpus": 2, + "memory_mib": 512 +} +``` + +### 3. Execute a command + +`exec` returns immediately with an `execution_id`. The execution itself runs asynchronously. + +```bash +curl -s -X POST http://localhost:8100/v1/default/boxes/k14sYHce2Rqi/exec \ + -H 'Content-Type: application/json' \ + -d '{"command":"echo","args":["Hello from REST!"]}' +``` + +```json +{ "execution_id": "61357ee0-f841-4502-8a3b-fc3cf5b24ac0" } +``` + +Poll status, or — preferred — stream output as Server-Sent Events: + +```bash +curl -N "http://localhost:8100/v1/default/boxes/k14sYHce2Rqi/executions/61357ee0-f841-4502-8a3b-fc3cf5b24ac0/output" +``` + +```text +event: stdout +data: {"data":"SGVsbG8gZnJvbSBSRVNUIQo="} + +event: stderr +data: {"data":"..."} + +event: exit +data: {"duration_ms":8482,"exit_code":0} +``` + + + `stdout` and `stderr` payloads are **base64-encoded** in the SSE `data` field — decode before displaying. The terminal `exit` event carries `duration_ms` and `exit_code`, mirroring what the SDKs surface as `ExecResult`. + + +### 4. Clean up + +```bash +curl -X POST http://localhost:8100/v1/default/boxes/k14sYHce2Rqi/stop +curl -X DELETE http://localhost:8100/v1/default/boxes/k14sYHce2Rqi +``` + +## Snapshots over REST + +```bash +curl -s -X POST http://localhost:8100/v1/default/boxes/k14sYHce2Rqi/snapshots \ + -H 'Content-Type: application/json' \ + -d '{"name":"demo-snap"}' +``` + +```json +{ + "id": "6YfnIqpz", + "box_id": "k14sYHce2Rqi", + "name": "demo-snap", + "created_at": 1777132120, + "container_disk_bytes": 268435456, + "size_bytes": 655360 +} +``` + +A snapshot has both an `id` (server-generated) and a `name` (your label). Path-style URLs use the **name** as the addressable segment: `/snapshots/{name}` and `/snapshots/{name}/restore`. See [Snapshot, clone, export](/concepts/snapshot) for what is and isn't captured (snapshot is **disk-only** — there is no in-memory checkpoint). + +## Errors + +Errors come back with a uniform envelope: + +```json +{ + "error": { + "message": "invalid state: Cannot remove snapshot: current disk depends on this snapshot. Restore a different snapshot first.", + "type": "InternalError", + "code": 500 + } +} +``` + +`code` mirrors the HTTP status. `type` enumerates the runtime error kind (`InvalidState`, `NotFound`, `InternalError`, …); see the OpenAPI components for the full list. + +## See also + +- [Python](/reference/python/index), [Node.js](/reference/nodejs/index), [Rust](/reference/rust/index), [C](/reference/c/index) SDK references — same primitives, library form +- [Snapshot, clone, export](/concepts/snapshot) — what snapshots can and cannot capture +- [`openapi/rest-sandbox-open-api.yaml`](https://github.com/boxlite-ai/boxlite/blob/main/openapi/rest-sandbox-open-api.yaml) — full request and response schemas diff --git a/reference/rust/index.mdx b/reference/rust/index.mdx index 1166659..bc6593b 100644 --- a/reference/rust/index.mdx +++ b/reference/rust/index.mdx @@ -347,7 +347,7 @@ async fn main() -> Result<(), Box> { ## See Also -- [Getting Started Guide](/getting-started/index) +- [Getting Started Guide](/getting-started/install) - [Architecture Overview](/architecture/index) - [Configuration Reference](/reference/index) - [Box Configuration](/reference/rust/box-config) diff --git a/tutorials/index.mdx b/tutorials/index.mdx index 32f7db4..6de0d11 100644 --- a/tutorials/index.mdx +++ b/tutorials/index.mdx @@ -21,6 +21,7 @@ You've installed BoxLite and run "Hello from BoxLite!" — now what? These tutor | Run an interactive shell session | [**InteractiveBox**](/getting-started/core-concepts#box-types) | [Interactive terminal](/tutorials/interactive-terminal) | | Run Node.js, Go, Rust, or any tool | [**SimpleBox**](/getting-started/core-concepts#box-types) | [Run any language](/tutorials/run-any-language) | | Run a server and access it from host | [**SimpleBox**](/getting-started/core-concepts#box-types) | [Long-running services](/tutorials/long-running-services) | +| Fork or branch sandbox state | [**SimpleBox**](/getting-started/core-concepts#box-types) or [**CodeBox**](/getting-started/core-concepts#box-types) | [Snapshot, fork, restore](/tutorials/snapshot-fork-restore) *(coming soon)* | | Handle errors and debug failures | All | [Error handling](/tutorials/error-handling) | @@ -93,6 +94,13 @@ You've installed BoxLite and run "Hello from BoxLite!" — now what? These tutor > Start HTTP servers and JSON APIs inside a sandbox, forward ports, and access them from the host. + + Take a snapshot, branch two execution paths from the same state, then roll one back — like git branch for sandbox environments. + {path}"]) + await h.wait() + + +async def main(): + runtime = boxlite.Boxlite.default() + + source = await runtime.create( + boxlite.BoxOptions(image="alpine:latest", auto_remove=False), + name="source", + ) + print(f"source.id = {source.id}") + + await write(source, "/root/marker.txt", "baseline") + print(f"source baseline: {await cat(source, '/root/marker.txt')!r}") +``` + +Two notes about this snippet: + +- `auto_remove=False` is on purpose — we want both boxes to outlive the script's `stop()` call so we can compare the timelines. +- The runtime-level handle returned by `runtime.create(...)` exposes `box.exec(...)` directly. This is the lower-level surface where you can stream stdout/stderr; the `cat()` helper above shows the pattern (consume the iterator first, then `await wait()` for the exit code). + +Running just step 1 prints: + +```text +source.id = egsJA9Qr4vZR +source baseline: 'baseline' +``` + +## Step 2 — fork by cloning the running box + +```python + clone = await source.clone_box(name="clone") + print(f"clone.id = {clone.id}") + print(f"clone sees: {await cat(clone, '/root/marker.txt')!r}") +``` + +`clone_box()` works while the source is running. Internally the runtime auto-quiesces the source box (briefly moves it through `paused`, see [Lifecycle](/concepts/lifecycle)) so the clone gets a consistent, point-in-time copy of the disk. The source returns to `running` automatically — you don't need to manage the pause yourself. + +The clone's disk is **independent**: it's a fresh copy-on-write image rooted at the source's disk state. The two boxes share nothing after this call returns. + +```text +clone.id = 2kSCsg5kLWUm +clone sees: 'baseline' +``` + +The clone correctly inherits the marker file we wrote in step 1. + +## Step 3 — diverge + +Now write something different in each box. + +```python + await write(source, "/root/marker.txt", "branch_a") + await write(clone, "/root/marker.txt", "branch_b") +``` + +## Step 4 — verify independence + +```python + print(f"source after branch A: {await cat(source, '/root/marker.txt')!r}") + print(f"clone after branch B: {await cat(clone, '/root/marker.txt')!r}") +``` + +Output: + +```text +source after branch A: 'branch_a' +clone after branch B: 'branch_b' +``` + +Two boxes, two timelines, no contamination. This is the basic shape you'd reach for whenever you want to A/B a build, fan out an evaluation, or speculatively try something risky while preserving the baseline. + +## Step 5 — clean up + +```python + for b in (clone, source): + try: + await b.stop() + except Exception: + pass + await runtime.remove(b.id, force=True) + print("cleaned up") +``` + +Because we set `auto_remove=False`, neither box was removed by its `stop()`. We tear them down explicitly via `runtime.remove(box_id, force=True)`. `force=True` removes a box regardless of state — the safest default for cleanup paths. + +## When to reach for clone vs snapshot vs export + +- **Clone** — what we just did. Two live boxes from the same point; both can run, both can be modified, neither blocks the other. +- **Snapshot** — captures the disk under the current box itself, no second box. Use it when you want to roll the *same* box back to a known state later. +- **Export / Import** — produces a portable archive on disk. Use it to ship a state to another host or to back it up. + +All three are disk-only — none of them captures memory, in-flight syscalls, or open sockets. See [Snapshot, clone, export](/concepts/snapshot) for the full breakdown. + +## Full script + +The complete file used to validate every output above: + +```python tutorials/snapshot_fork_demo.py +import asyncio +import boxlite + + +async def cat(box, path): + h = await box.exec("cat", [path]) + chunks = [] + stdout = h.stdout() + if stdout is not None: + async for line in stdout: + chunks.append(line.decode() if isinstance(line, bytes) else line) + await h.wait() + return "".join(chunks).strip() + + +async def write(box, path, value): + h = await box.exec("sh", ["-c", f"echo {value} > {path}"]) + await h.wait() + + +async def main(): + runtime = boxlite.Boxlite.default() + + source = await runtime.create( + boxlite.BoxOptions(image="alpine:latest", auto_remove=False), + name="source", + ) + print(f"source.id = {source.id}") + + await write(source, "/root/marker.txt", "baseline") + print(f"source baseline: {await cat(source, '/root/marker.txt')!r}") + + clone = await source.clone_box(name="clone") + print(f"clone.id = {clone.id}") + print(f"clone sees: {await cat(clone, '/root/marker.txt')!r}") + + await write(source, "/root/marker.txt", "branch_a") + await write(clone, "/root/marker.txt", "branch_b") + + print(f"source after branch A: {await cat(source, '/root/marker.txt')!r}") + print(f"clone after branch B: {await cat(clone, '/root/marker.txt')!r}") + + for b in (clone, source): + try: + await b.stop() + except Exception: + pass + await runtime.remove(b.id, force=True) + print("cleaned up") + + +if __name__ == "__main__": + asyncio.run(main()) +``` + +Save it as `snapshot_fork_demo.py` and run with `python snapshot_fork_demo.py`. End-to-end output observed during validation: + +```text +source.id = egsJA9Qr4vZR +source baseline: 'baseline' +clone.id = 2kSCsg5kLWUm +clone sees: 'baseline' +source after branch A: 'branch_a' +clone after branch B: 'branch_b' +cleaned up +``` + +## See also + +- [Snapshot, clone, export](/concepts/snapshot) — when each primitive is the right one +- [Lifecycle](/concepts/lifecycle) — the `paused` state the runtime uses during clone +- [`examples/python/03_lifecycle/clone_export_import.py`](https://github.com/boxlite-ai/boxlite/blob/main/examples/python/03_lifecycle/clone_export_import.py) — upstream walkthrough of clone alongside export/import