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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 29 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# Quicksand

[![PyPI](https://img.shields.io/pypi/v/quick-sandbox)](https://pypi.org/project/quick-sandbox/)
[![Changelog](https://img.shields.io/github/v/release/microsoft/quicksand?label=changelog&logo=github)](https://github.com/microsoft/quicksand/blob/main/CHANGELOG.md)
[![Docs](https://img.shields.io/badge/docs-quicksand-blue)](https://microsoft.github.io/quicksand/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)

![Quicksand](docs/banner-light.png)

Quicksand is an async Python API to launch, control, and snapshot [QEMU](https://www.qemu.org) virtual machines with a particular focus on sandboxing AI agents. Quicksand provides pre-built Linux VMs for Ubuntu and Alpine distros. It works on x86_64 and ARM64 across macOS, Linux, and Windows with no root privileges, no Docker, and no system dependencies. Just `pip install quick-sandbox`.
Quicksand is an async Python API to launch, control, and snapshot [QEMU](https://www.qemu.org) virtual machines with a particular focus on sandboxing AI agents. Quicksand provides pre-built Linux VMs for Ubuntu and Alpine distros and supports x86_64 and ARM64 across macOS, Linux, and Windows. Running sandboxes needs no root privileges or Docker; install the QEMU and image extras for a bundled runtime on supported platforms.

## Installation

Expand Down Expand Up @@ -75,6 +76,24 @@ async with Sandbox(
...
```

### Multiple Linux users

Give multiple agents independent Linux user accounts in a single sandbox VM.

```python
async with Sandbox(image="ubuntu") as sb:
alice = await sb.create_user("alice")
bob = await sb.create_user("bob")

await alice.execute("cat > hello.txt", stdin="Hello from Alice!\n")
await bob.execute("cat > hello.txt", stdin="Hello from Bob!\n")

for user in (alice, bob):
result = await user.execute("whoami && pwd && cat hello.txt")
print(result.stdout)
await sb.delete_user(user.name)
```

### Save and load

Save the VM's disk state to a directory. Load it later, even on a different machine.
Expand Down Expand Up @@ -143,12 +162,15 @@ Sandbox(

| Image | Type | Wheel size | Install command | What is it |
|-------|------|-----------|-----------------|------------|
| `ubuntu` | Base | ~341 MB | `quicksand install ubuntu` | Ubuntu 24.04 headless |
| `alpine` | Base | ~78 MB | `quicksand install alpine` | Alpine 3.23 headless (faster boot) |
| `ubuntu-desktop` | Overlay (`ubuntu`) | ~263 MB | `quicksand install ubuntu-desktop` | Ubuntu 24.04 + Xfce4 + Firefox |
| `alpine-desktop` | Overlay (`alpine`) | ~310 MB | `quicksand install alpine-desktop` | Alpine 3.23 + Xfce4 + Chromium |
| `quicksand-agent` | Overlay (`ubuntu`) | ~304 MB | `quicksand install quicksand-agent` | Ubuntu + Python 3.12, uv, build-essential, requests, pyyaml, ddgs, markitdown |
| `quicksand-cua` | Overlay (`quicksand-agent`) | ~445 MB | `quicksand install quicksand-cua` | Agent Sandbox + Xvfb, x11vnc, noVNC, Playwright, Chromium |
| `ubuntu` | Base | ~309-357 MB | `quicksand install ubuntu` | Ubuntu 24.04 headless |
| `alpine` | Base | ~82-85 MB | `quicksand install alpine` | Alpine 3.23 headless (faster boot) |
| `ubuntu-desktop` | Overlay (`ubuntu`) | ~273-281 MB | `quicksand install ubuntu-desktop` | Ubuntu 24.04 + Xfce4 + Firefox |
| `alpine-desktop` | Overlay (`alpine`) | ~346-363 MB | `quicksand install alpine-desktop` | Alpine 3.23 + Xfce4 + Chromium |
| `quicksand-agent` | Overlay (`ubuntu`) | ~306-335 MB | `quicksand install quicksand-agent` | Ubuntu + Python 3.12, uv, build-essential, requests, pyyaml, ddgs, markitdown |
| `quicksand-cua` | Overlay (`quicksand-agent`) | ~489-500 MB | `quicksand install quicksand-cua` | Agent Sandbox + Xvfb, x11vnc, noVNC, Playwright, Chromium |

Sizes are approximate full image-wheel downloads for the September 14, 2026 releases
and vary by architecture. Overlay sizes exclude their base images.

## Building from source

Expand Down
7 changes: 6 additions & 1 deletion docs/contributor-guide/04-releasing.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Releasing

Releases are managed via the `/release` Claude Code skill.
Releases are managed by `uvr`, with the repository's `/release` skill as the workflow guide.

## Flow

Expand Down Expand Up @@ -72,3 +72,8 @@ failure normally requires a fresh dispatch.
## Changelog

Update `CHANGELOG.md` on the release branch before dispatch. Categories: Added, Changed, Deprecated, Removed, Fixed, Security.

The root README's GitHub release badge links to the changelog and updates its
version automatically after publication.
The `sync-readme` pre-commit hook copies the root README to
`packages/quicksand/README.md` for the package description.
13 changes: 10 additions & 3 deletions docs/packages/quicksand-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This package provides the core implementation for the [quicksand](https://github.com/microsoft/quicksand) VM harness.

It includes the abstractions for running VMs that AI agents can interact with, including command execution, file operations, and state checkpointing. Most users should install `quicksand` instead, which includes pre-built images.
It includes the abstractions for running VMs that AI agents can interact with, including command execution, file operations, and state checkpointing. Most users should install `quick-sandbox` with QEMU and image extras instead.

## Installation

Expand All @@ -15,7 +15,7 @@ pip install 'quick-sandbox[qemu,ubuntu]'
For core-only (no bundled images):

```bash
pip install quick-sandbox
pip install quicksand-core
```

## Core Exports
Expand All @@ -26,6 +26,7 @@ This package exports the core building blocks:
from quicksand_core import (
# Main classes
Sandbox,
SandboxUser,
Mount,
ExecuteResult,
# Save support
Expand Down Expand Up @@ -69,16 +70,22 @@ asyncio.run(main())

- **Real VM isolation**: Hypervisor-level isolation (KVM, HVF, WHPX)
- **Cross-platform**: Linux, macOS, Windows
- **Streaming commands**: Incremental stdin and output, with backpressure, EOF, and cancellation
- **Guest users**: Create/delete OS accounts and run commands with user-specific identity and home directories inside one VM
- **Platform abstraction**: Automatic detection of accelerators and machine types
- **Save and load**: Save VM disk state to a directory and load it on any machine
- **File sharing**: CIFS mounts via `quicksand-smb` (pure-Python SMB3 server — a subprocess via QEMU guestfwd on macOS/Linux, an in-process loopback TCP listener on Windows with no admin rights required)
- **Performance optimizations**:
- io_uring disk AIO (~50% lower latency on Linux)
- IOThreads for better concurrent disk I/O (all platforms)

Streaming stdin and guest-user APIs require `quicksand-ubuntu` or
`quicksand-alpine` 0.10.0 or newer, or a custom image rebuilt with the updated
guest agent. Updating the host package does not update saved guest images.

## For Most Users

Install `quicksand` with a bundled image for zero-configuration usage:
Install `quick-sandbox` with a bundled image for zero-configuration usage:

```bash
pip install 'quick-sandbox[qemu,ubuntu]'
Expand Down
70 changes: 29 additions & 41 deletions docs/under-the-hood/01-installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ The `-L` flag tells QEMU where to find firmware and keymap files. Without it, QE

If the bundled package isn't installed, Quicksand falls back to system QEMU found on `PATH`.

### Windows ARM64 and fat wheels
### Windows ARM64 and interpreter architecture

On Windows ARM64, most users run x86_64 Python through Microsoft's transparent emulation layer. The user may not even know they're running emulated Python — it's the default. This creates a conflict:
quicksand-qemu 0.5.12 packages are single-architecture wheels: `win_amd64` contains x86_64
executables and `win_arm64` contains ARM64 executables, directly in `bin/`.
Windows ARM64 can run x86_64 Python through emulation, which affects wheel selection:

| | Python arch | pip accepts | QEMU needed |
|---|---|---|---|
Expand All @@ -36,49 +38,35 @@ On Windows ARM64, most users run x86_64 Python through Microsoft's transparent e

The third row is the problem. pip's wheel compatibility tags (PEP 425) match the Python interpreter's platform, not the hardware. An emulated x86_64 Python will only install `win_amd64` wheels — but the machine needs ARM64 QEMU binaries for hardware acceleration (WHPX).

This problem is unique to Windows. Linux doesn't transparently emulate x86_64 on ARM64, and macOS users on Apple Silicon install ARM64 Python by default (Rosetta 2 exists but isn't the default Python experience).
Use native ARM64 Python on Windows ARM64 so pip selects the ARM64 QEMU and image
wheels. `quicksand install` delegates to pip; it does not override the
interpreter's platform tags. Installing a single-architecture x64 QEMU wheel
under emulated Python can produce an architecture-mismatch error at runtime.

**Our solution: fat `win_amd64` wheels.** The `win_amd64` quicksand-qemu wheel ships both x86_64 and ARM64 QEMU binaries:

```
quicksand_qemu/bin/
├── x86_64/ # x86_64 QEMU binaries
│ ├── qemu-system-x86_64.exe
│ ├── qemu-img.exe
│ └── ...
└── arm64/ # ARM64 QEMU binaries
├── qemu-system-aarch64.exe
├── qemu-img.exe
└── ...
```

At runtime, `_find_bundled_runtime()` reads the native CPU architecture from the Windows Registry (`HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\PROCESSOR_ARCHITECTURE`) — this always reports the true hardware regardless of process emulation — and selects the matching subdirectory. On a single-arch wheel (Linux, macOS, or `win_arm64`), binaries live directly in `bin/` with no subdirectories.

**Build pipeline:**
1. The Windows x64 CI runner builds `win_amd64.whl` with x64 QEMU in `bin/`
2. The GitHub-hosted `windows-11-arm` runner builds `win_arm64.whl` with ARM64 QEMU in `bin/`
3. A `pre_release` hook (runs after all builds, before publishing) opens the `win_amd64` wheel, moves its binaries to `bin/x86_64/`, extracts ARM64 binaries from the `win_arm64` wheel into `bin/arm64/`, and rewrites the wheel
4. The `win_arm64` wheel ships unchanged (lean, single-arch) for the rare native ARM64 Python user

This approach doubles the `win_amd64` wheel size (~44 MB → ~80 MB) but ensures `pip install quicksand-qemu` delivers hardware-accelerated QEMU on every Windows configuration without user intervention.
The current pipeline publishes both Windows wheels without merging them.
`_find_bundled_runtime()` still supports legacy combined wheels with
`bin/x86_64/` and `bin/arm64/` subdirectories, selecting by native hardware
architecture. That is backward compatibility, not the layout of the current release.

### Platform wheel matrix

#### quicksand-qemu: build runners → wheels

quicksand-qemu is in `_SKIP` — never retagged. Each runner produces exactly one wheel.

| Runner | QEMU Binary | Wheel Tag | After `pre_release` merge |
|--------|-------------|-----------|--------------------------|
| `[linux, x64]` | qemu-system-x86_64 (Linux) | `manylinux_<major>_<minor>_x86_64` | unchanged |
| `[linux, arm64]` | qemu-system-aarch64 (Linux) | `manylinux_<major>_<minor>_aarch64` | unchanged |
| `[macos, arm64]` | qemu-system-aarch64 (macOS) | `macosx_11_0_arm64` | unchanged |
| `[windows, x64]` | qemu-system-x86_64.exe | `win_amd64` | → **fat**: x64 in `bin/x86_64/`, arm64 in `bin/arm64/` |
| `windows-11-arm` | qemu-system-aarch64.exe | `win_arm64` ¹ | unchanged (consumed by merge into fat wheel) |
| Runner | QEMU Binary | Wheel Tag |
|--------|-------------|-----------|
| `[linux, x64]` | qemu-system-x86_64 (Linux) | `manylinux_<major>_<minor>_x86_64` |
| `[linux, arm64]` | qemu-system-aarch64 (Linux) | `manylinux_<major>_<minor>_aarch64` |
| `[macos, arm64]` | qemu-system-aarch64 (macOS) | `macosx_11_0_arm64` |
| `[windows, x64]` | qemu-system-x86_64.exe | `win_amd64` |
| `windows-11-arm` | qemu-system-aarch64.exe | `win_arm64` ¹ |

Linux manylinux versions are derived from versioned symbols in the bundled ELF
binaries and libraries. They are not fixed at glibc 2.17; pip selects a wheel
compatible with the host's glibc version.
The published quicksand-qemu 0.5.12 Linux wheels require glibc 2.38 or newer.
On older systems, use a compatible system QEMU instead.

With quicksand-build-tools 0.6.0, custom Linux build hooks must pass the bundled
binary directory as `bin_dir` to `BinaryBundler.set_platform_wheel_tag()` and
Expand All @@ -89,14 +77,13 @@ applications using `Sandbox` do not need to change their build configuration.

#### Image wheels (ubuntu, alpine, etc.): build runners → retag

Image wheels contain qcow2 files that are cross-platform. Retag runs only on `RETAG_RUNNERS`.
Image wheels contain architecture-specific VM data that is portable across host
operating systems. Retag runs only on `RETAG_RUNNERS`; these are the two image builders.

| Runner | Builds | Retag produces |
|--------|--------|----------------|
| `[linux, x64]` | `linux_x86_64` | + `macosx_10_13_x86_64`, `win_amd64` |
| `[macos, arm64]` | `macosx_11_0_arm64` | + `linux_aarch64`, `win_arm64` |
| `[linux, arm64]` | `linux_aarch64` | none (not in `RETAG_RUNNERS`) |
| `[windows, *]` | — | not an image builder |
| `[linux, x64]` | `manylinux_2_17_x86_64` | + `macosx_10_13_x86_64`, `win_amd64` |
| `[macos, arm64]` | `macosx_11_0_arm64` | + `manylinux_2_17_aarch64`, `win_arm64` |

#### quicksand-qemu: host → pip install

Expand All @@ -107,14 +94,15 @@ Image wheels contain qcow2 files that are cross-platform. Retag runs only on `RE
| macOS Intel | x86_64 | x86_64 | — (no wheel) | system QEMU (Homebrew) | HVF ✅ |
| macOS Apple Silicon | arm64 | arm64 | `macosx_11_0_arm64` | qemu-system-aarch64 | HVF ✅ |
| macOS Rosetta | arm64 | x86_64 | — (no wheel) | system QEMU (Homebrew) | TCG ❌ |
| Windows | x86_64 | x86_64 | `win_amd64` (fat) | picks `bin/x86_64/` | WHPX ✅ |
| Windows | arm64 | x86_64 (emulated) | `win_amd64` (fat) | picks `bin/arm64/` | WHPX ✅ |
| Windows | x86_64 | x86_64 | `win_amd64` | qemu-system-x86_64 | WHPX ✅ |
| Windows | arm64 | x86_64 (emulated) | `win_amd64` | architecture mismatch; use native Python | — |
| Windows | arm64 | arm64 (native) | `win_arm64` | qemu-system-aarch64 | WHPX ✅ |

**Notes:**
- **No macOS x86_64 runner** — macOS Intel users fall back to system QEMU via Homebrew.
- **Rosetta Python** — rare; gets no bundled wheel, falls back to system QEMU with software emulation (TCG).
- **Fat wheel** — only `win_amd64` is fat (~2x size). All other wheels are single-arch.
- **Acceleration** — requires support in both the host and QEMU build. The table lists the preferred accelerator, not a guarantee that it is available on every host.
- **Single-architecture wheels** — all current QEMU wheels contain one architecture. Large image wheels also use the term "fat", but that means they carry VM image data rather than being a small PyPI stub.

## `quicksand install ubuntu`

Expand Down
3 changes: 2 additions & 1 deletion docs/under-the-hood/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,8 @@ quicksand install ubuntu # Ubuntu 24.04 headless
quicksand install alpine # Alpine 3.23 headless
quicksand install alpine-desktop # Alpine 3.23 + Xfce4
quicksand install ubuntu-desktop # Ubuntu 24.04 + Xfce4
quicksand install all # everything (QEMU + all images + dev tools)
# QEMU + all images + dev tools
quicksand install qemu ubuntu alpine ubuntu-desktop alpine-desktop agent cua dev
```

Each image package provides:
Expand Down
51 changes: 34 additions & 17 deletions docs/user-guide/01-installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@
pip install 'quick-sandbox[qemu,alpine,ubuntu]'
```

This installs the core Python library, CLI, QEMU, and VM images. No native dependencies needed.
This installs the core Python library, CLI, QEMU, and image packages. On platforms
with a compatible bundled QEMU wheel, no system QEMU installation is needed.

Large image packages are published to PyPI as small stubs. On first use, they
download the full platform-specific wheel from the
[Quicksand index](https://microsoft.github.io/quicksand/simple/). Both automatic
downloads and `quicksand install` require `pip` in that Python environment
(`uv venv --seed` when using uv). Use the CLI to fetch images before first use. Set
`QUICKSAND_AUTO_INSTALL=0` to disable automatic image downloads.

To declare it as a dependency in your `pyproject.toml`:

Expand All @@ -21,11 +29,12 @@ dependencies = [

## Install QEMU and an image

Quicksand bundles its own QEMU, so no system install is needed. Use the `quicksand install` CLI to download platform-specific binaries and images.
Use the `quicksand install` CLI to download bundled QEMU binaries and images.
See [Requirements](#requirements) for platform compatibility.

```bash
quicksand install qemu # Bundled QEMU (~15MB on macOS ARM64)
quicksand install ubuntu # Ubuntu 24.04 headless (~340MB)
quicksand install ubuntu # Ubuntu 24.04 headless (~309-357 MB)
```

That's enough to start using Quicksand:
Expand All @@ -42,25 +51,28 @@ async with Sandbox(image="ubuntu") as sb:

### QEMU (`quicksand install qemu`)

| | macOS ARM64 | Linux ARM64 | Linux x86_64 | Windows x86_64 |
|---|---|---|---|---|
| Download | 14 MB | 15 MB | 15 MB | 43 MB |
| On disk | 58 MB | 60 MB | 53 MB | 124 MB |
| | macOS ARM64 | Linux ARM64 | Linux x86_64 | Windows x86_64 | Windows ARM64 |
|---|---|---|---|---|---|
| Download | 15 MB | 16 MB | 16 MB | 44 MB | 35 MB |

### Images

| Image | Depends on | Display | ARM64 download | ARM64 on disk | Boot p50 | Boot p95 |
|-------|------------|---------|----------------|---------------|----------|----------|
| `alpine` | — | No | 73 MB | 74 MB | 0.37s | 0.45s |
| `ubuntu` | — | No | 341 MB | 346 MB | 0.88s | 0.91s |
| `alpine-desktop` | `alpine` | Yes | 287 MB | 290 MB | 0.47s | 0.54s |
| `ubuntu-desktop` | `ubuntu` | Yes | 252 MB | 257 MB | 0.90s | 0.96s |
| `quicksand-agent` | `ubuntu` | No | ~304 MB | ~308 MB | 0.91s | 0.92s |
| `quicksand-cua` | `quicksand-agent` | No | ~445 MB | ~450 MB | 0.85s | 1.01s |
| Image | Depends on | Display | ARM64 download | Boot p50 | Boot p95 |
|-------|------------|---------|----------------|----------|----------|
| `alpine` | — | No | 82 MB | 0.37s | 0.45s |
| `ubuntu` | — | No | 357 MB | 0.88s | 0.91s |
| `alpine-desktop` | `alpine` | Yes | 346 MB | 0.47s | 0.54s |
| `ubuntu-desktop` | `ubuntu` | Yes | 273 MB | 0.90s | 0.96s |
| `quicksand-agent` | `ubuntu` | No | 306 MB | 0.91s | 0.92s |
| `quicksand-cua` | `quicksand-agent` | No | 489 MB | 0.85s | 1.01s |

Boot times measured on macOS ARM64 (Apple M3 Max, HVF) with `quicksand benchmark -n 5`. First boot is slower due to cold cache.
Download sizes are rounded decimal MB for the September 14, 2026 releases and
exclude dependencies. Boot times are historical measurements on macOS ARM64
(Apple M3 Max, HVF) with `quicksand benchmark -n 5`, not a new benchmark of these
releases. First boot is slower due to cold cache.

`quicksand install all` installs QEMU and all images.
`quicksand install` accepts multiple names, for example
`quicksand install qemu ubuntu alpine`.

Alpine is smaller and boots faster. Ubuntu has a larger package ecosystem. Desktop images are overlays on their base image and add a graphical environment for screenshot/keyboard/mouse interaction. The agent sandbox images are pre-configured agent environments with Python 3.12, browser automation tools, and common AI agent dependencies.

Expand All @@ -87,6 +99,11 @@ If this prints a Linux kernel version, everything is working.
- **No root/admin.** QEMU runs as a normal user process.
- **No Docker.** Quicksand is not container-based (Docker is only needed for *building* new images, not running them).

Use native ARM64 Python on Windows ARM64. The current Windows QEMU wheels each
contain one architecture; emulated x86_64 Python selects the x64 wheel rather
than the ARM64 binaries needed by the host. Linux quicksand-qemu 0.5.12 wheels require
glibc 2.38 or newer; use a compatible system QEMU on older Linux distributions.

## Using system QEMU

If you already have QEMU installed, Quicksand will find it on `PATH` as a fallback. But the bundled QEMU (`quicksand install qemu`) is recommended. It's tested and includes the right firmware files.
Loading