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

# nanoVM for coding agents

> The shortest path from an API key to running code in an isolated microVM — skill install, SDK patterns, MCP tools.

<Note>
  **Initial pilot:** use the invited organization, at most 20 sandboxes, and at most
  4 vCPU / 8 GiB per sandbox. GPU and live migration are unavailable. A host-loss
  recovery creates a new sandbox from a durable checkpoint; it is not automatic
  same-ID failover. See [launch and support](/launch).
</Note>

You are an agent that has (or is about to get) a nanoVM API key. This page is
the shortest path from that key to running code in an isolated microVM, with
the checks that tell you each step worked. The Markdown of any page on this
site is available by appending `.md` to its URL; the whole site is at
[/llms.txt](/llms.txt).

## TL;DR — two commands

```bash theme={null}
python3 -m venv .venv && . .venv/bin/activate
pip install "$(curl -fsSL https://console.nanovm.dev.lithosai.cloud/sdk/LATEST)"
python -m nanovm skill install
```

The venv line is there for a reason: Homebrew and Debian Pythons refuse `pip install` into the
system interpreter (`externally-managed-environment`), and ship `python3`/`pip3` only. `/sdk/LATEST`
is a one-line file naming the current wheel URL, so nothing here pins a version. Do not
`pip install nanovm` from PyPI — that name belongs to an unrelated project.

Then tell your agent: **"set up nanoVM"**. The skill it just loaded carries the
setup procedure: it asks you for an API key (console → API Keys), exports it,
runs the 30-second self-test and reports `VERDICT: PASS`. From there it uses
the SDK for real work. That's the whole onboarding; everything below is
detail.

* `skill install` writes `./.claude/skills/nanovm/` — project scope, this directory only (it
  prints the absolute path and says so). `--dir ~/.claude/skills` installs for every project;
  `--cursor` adds a Cursor rule. `python -m nanovm skill show` prints the skill.
* Prefer tools over code, or your agent has no shell (Claude Desktop, chat
  hosts)? `pip install "mcp>=2,<3"` and
  `claude mcp add nanovm --transport stdio -e NANOVM_TOKEN=lith_sk_... -- python -m lithosbox mcp`.
* With a `lith_sk_` key only `NANOVM_TOKEN` is needed; the SDK defaults to the key
  edge `https://sk.nanovm.dev.lithosai.cloud`.

## 0. What you get

A **sandbox** = a microVM you fully control: run commands, write files, start
servers, expose a port on a public HTTPS URL, checkpoint (snapshot) and
restore or fork it. Boots from any OCI image; the default sandbox has Python
3.12, Node 22 and the usual data-science stack, 2 vCPU / 1 GiB, and starts in
well under a second. Sandboxes are isolated per organization.

## 1. Prove the environment (30 seconds)

```bash theme={null}
export NANOVM_TOKEN=lith_sk_...                      # from the console's API Keys page
python3 -m venv .venv && . .venv/bin/activate
pip install "$(curl -fsSL https://console.nanovm.dev.lithosai.cloud/sdk/LATEST)"
python -m lithosbox quickstart
```

Expected: one `ok` line per step (auth, create, exec, stdin, session, tools,
snapshot, wait-until-durable, restore, delete) and `VERDICT: PASS`. A `FAIL` line names the step
and the exception; the text is enough to act on (bad key → `AuthError`, image
still building → inspect its template status before another create). `--keep` leaves the sandbox running and
prints its `vm_id`; `--image X` tests a specific image; `--json` appends a
machine-readable summary.

## 2. Use it from Python (preferred)

```python theme={null}
import os
from lithosbox import LithosBox

nv = LithosBox()                                   # reads NANOVM_API_URL / NANOVM_TOKEN; ONE per process
sb = nv.sandboxes.create()                      # or image="node:22", template="my-env", snapshot_id=...
r = sb.run("python3 -c 'print(6*7)'")             # runs to completion; r.exit_code, r.stdout, r.stderr
sb.files.write("/work/data.csv", open("data.csv", "rb").read())   # files.read/write/ls/exists; ≤ 4 MiB per write
with sb.session() as sh:                        # persistent shell: cwd/env survive between run() calls
    sh.run("cd /work && pip install -q pandas")
    print(sh.run("python3 -c 'import pandas; print(pandas.__version__)'").stdout)
sb.run("python3 -m http.server 8080", background=True)   # something that must outlive the call
url = sb.expose(8080).url                       # public HTTPS URL to that port
snap = sb.snapshot(wait_durable=True)           # checkpoint; durable (survives its node) once .durable — seconds
sb.delete()
```

Rules that save you time:

* **One `LithosBox()` per process.** Its connections are reused; a new client per call costs \~200 ms each.
* **`run`/`exec` are scoped**: they return when the command *and everything it spawned* exit. Use `background=True` for servers, `session()` for multi-step shell work, and the argv form `exec(["cmd", arg])` whenever the command carries untrusted input.
* **Bulk data goes through `stdin`**, not argv (argv strings are capped at 128 KiB by the guest kernel; stdin at 4 MiB per call — loop for more).
* **First use of an image builds a template once** (seconds to tens of minutes depending on image size and cache state). `create()` waits for it by default; the wait is per image, not per sandbox, and public images are shared by every organization.
* **Delete what you create.** `with nv.sandboxes.create() as sb:` deletes on exit. Snapshots outlive their sandbox.
* **Long jobs**: one request has \~2 minutes at the edge (`create` gets 5). Start long work with `background=True` or in a session, poll a file or a log with later calls.

## 3. Claude Code skill (default)

```bash theme={null}
npx skills add https://docs.nanovm.dev.lithosai.cloud
```

Or from the SDK: `python -m nanovm skill install`. The skill teaches the agent
the SDK patterns above and when to reach for a sandbox (untrusted code, heavy
dependencies, a clean environment per task, parallel experiments via `fork`).

## 4. Use it as MCP tools (no shell / interactive)

```bash theme={null}
pip install "$(curl -fsSL https://console.nanovm.dev.lithosai.cloud/sdk/LATEST)" "mcp>=2,<3"
python -m nanovm mcp --check      # prints the tool list
# Claude Code:
claude mcp add nanovm --transport stdio -e NANOVM_API_URL=$NANOVM_API_URL -e NANOVM_TOKEN=$NANOVM_TOKEN -- python -m lithosbox mcp
# Any MCP host (stdio): {"command": "python", "args": ["-m", "nanovm.mcp_server"], "env": {"NANOVM_API_URL": "...", "NANOVM_TOKEN": "..."}}
```

Tools: `create_sandbox`, `exec`, `shell` (persistent session), `exec_background`,
`write_file`, `read_file`, `expose_port`, `snapshot`, `branch`, `list_sandboxes`,
`delete_sandbox`, and the lifecycle verbs `pause`, `resume`, `sleep`, `stop`, `start`, `reboot`
plus `list_snapshots` / `delete_snapshot`. Ids are plain strings you carry between calls.

To query **these docs** as an MCP tool instead, connect this site's own server:
`claude mcp add --transport http nanovm-docs https://docs.nanovm.dev.lithosai.cloud/mcp`.

## 5. Raw HTTP, if you must

`POST /vms` `{}` → `{"vm_id": …}`; `POST /vms/{id}/exec` `{"args": [...], "stdin_b64": "..."}` →
`{"exit_code", "stdout", "stderr"}`; `DELETE /vms/{id}`. Header
`Authorization: Bearer $NANOVM_TOKEN`. `503` with `template_id` = first use of
an image: poll `GET /templates/{template_id}` until `status` is `ready`, then
retry. `503` whose body says `connection error` = a control-plane component
restarting: retry in a few seconds. Full route table: [HTTP API](/reference/http-api).

## 6. Verify before you report success

* `sb.exec(["true"]).exit_code == 0` after any wake/start (sleep and `snapshot(leave_paused=True)` wake transparently; `stop()` needs `start()`).
* Read back files you wrote (`read_file` / `exec(["cat", path])`) — the write path is a shell redirect and its exit code is the truth.
* Snapshots are the only artifact that outlives a sandbox. One is restorable the moment it is taken
  and durable (survives its node; exportable) a few seconds later when `.durable` is True —
  `snapshot(wait_durable=True)` waits. A deleted sandbox's disk is gone.

## Long-running work and idle sleep

The platform never auto-sleeps a sandbox that is doing work (a background process, guest CPU, live
connections, or an exposed port). You do not need to poll or "keep-alive" a sandbox to keep a job,
server or in-sandbox agent running. Prefer `run(..., background=True)` for anything that outlives the call;
`exec()` returns as soon as the foreground command exits even if it left daemons behind.
