Skip to content

Engineering guide

Run commands from the repository root. mise tasks ls lists public tasks; mise run ci runs the local check sequence, and mise run ci -- check "test pvisor-core" runs selected tasks in order and stops on failure. Internal tasks stay callable but are hidden from the default help.

Install mise 2026.8.5 or newer, then prepare the development environment:

mise trust
mise install --locked
mise run setup
mise run doctor

mise.toml pins Rust (including rustfmt and Clippy), Python 3.12, uv, Ruff, actionlint, jq, Zig, nextest and cargo-zigbuild. setup installs the current architecture's Linux musl guest target and syncs development dependencies into .venv from uv.lock; it neither installs pVisor itself nor builds firmware. The Python product still supports 3.10+; development tools use 3.12.

CI and wheel builds use mise 2026.10.18 with the committed tool versions and lockfiles. For compatible local tools already on PATH, set disable_tools = ["python", "uv", "jq"] in the [settings] table of the ignored mise.local.toml; disabled tools must be provided by the caller. Python development tasks require 3.12 or newer, and UV_PYTHON falls back to python3 when Python is disabled. Select other mise-managed versions in that file's [tools] table and check them with mise ls --current. Release builds and recorded benchmark comparisons use a clean checkout without personal overrides or disabled tools.

mise run selects the project tools and environment without shell activation. For direct Cargo or Python commands, use mise exec -- cargo … / mise exec -- python3 …, or activate your shell following mise's shell setup instructions. Separate task arguments from mise with --; Rust tests use a second -- between package names and nextest options, for example mise run test -- core -- --offline.

Platform package managers still provide native dependencies: Linux build libraries, FUSE, KVM and cgroup permissions, or macOS Xcode Command Line Tools, LLVM/GNU tools and macFUSE. See Build environment below and the executor guides. Machine-specific overrides belong in ignored mise.local.toml; keep personal paths and credentials out of shared configuration. Update tool versions in mise.toml, use CI's mise 2026.10.18 to run mise lock --platform linux-x64,macos-arm64 and commit mise.lock; after changing Python dependencies, run mise exec -- uv lock and commit uv.lock.

Repository layout and ownership

The Cargo workspace has 13 crates organized by product responsibility. Its default member is pvisor-cli. Python pvisor/ is an installable version marker, not a launcher or runtime implementation. Wheels install native executable scripts directly into the environment's bin directory; the old Python launcher and its binary override are obsolete.

Directory Responsibility
crates/pvisor/ Embeddable runtime, Session/Attempt orchestration, executors, durable Job service, image preparation and cache mechanisms
crates/pvisor-cli/ Three application binaries, CLI commands, terminal frontends and replay/TUI companion dispatch
crates/pvisor-vm/ Native VM runtime, portable API, private VMM/platform implementations, embedded guest and kernel/firmware integration
crates/pvisor-daemon/ Linux x86_64 sandbox API, native guest protocol adapters and cross-platform cache/pool components
crates/pvisor-core/ Operation, Placement, policies, external interactions and Event contracts
crates/pvisor-journal/ Shared fact Journal storage and readers
crates/pvisor-gateway/ Agent protocol forwarding/conversion, capture and projections
crates/pvisor-overlay-core/ FUSE-independent OverlayFS operations and file access controls
crates/pvisor-overlayfs/ FUSE adaptation and mounts
crates/pvisor-overlaynet/ Egress policy, HTTP proxy and VM virtio-net path
crates/pvisor-guest/ Linux PID 1, shared launch contracts and native process/file control engine
crates/pvisor-shim/ containerd Runtime v2 shim; optional VM execution
crates/pvisor-replay/ Replay planning, native agent adapters and continuation protocol bridges
pvisor/, setup.py, scripts/packaging/ Python version marker and native-script wheel packaging
crates/*/tests/ Rust integration tests; unit tests stay with their modules
tests/ Python packaging and repository workflow tests
examples/, benchmark/ Executable product scenarios and performance measurements
scripts/ci/ CI checks and smoke-test entry points
docs/src/zh/, docs/src/en/ Documentation source; docs/site/ is generated
vendor/ Patched third-party dependencies; product orchestration belongs in crates/

Direct normal workspace dependencies (including target-specific edges; names below omit the pvisor- prefix):

cli ──> pvisor, core, journal, replay, overlaynet, overlay-core, vm
pvisor ──> core, journal, overlaynet, overlayfs, overlay-core, guest, vm
cli --features gateway ──> gateway, pvisor/gateway
pvisor --features gateway ──> gateway
vm ──> overlay-core, guest
daemon ──> pvisor, core, guest
replay ──> core, journal
shim ──> guest, overlay-core
shim --features vm ──> vm, overlaynet, core
gateway ──> core, journal, overlaynet
overlaynet ──> core
overlayfs ──> core, overlay-core
overlay-core ──> core, journal
journal ──> core
core, guest ──> no other workspace crates

pvisor has no CLI or Clap normal dependency; Clap is a development dependency for examples only. The pvisor-replay engine has no normal dependency on pvisor or Clap. The pvisor-tui crate has been removed; its executable name is unchanged.

pVisor source modules

Implementation entry Ownership
pvisor-cli/src/cli/mod.rs Build parsing/dispatch for the selected command; root help describes command groups
pvisor-cli/src/cli/host*.rs, run/ Frontend/owner startup, terminal, signals and Job command adaptation
pvisor/src/runtime/job_service/ Durable Job lifecycle, workspace, forks, capture, views and policy
pvisor/src/runtime/attempt_service.rs In-process live Attempt status/pause/resume/termination shared by Jobs and daemon
pvisor/src/session*, runtime/attempt.rs Per-Attempt driver preparation, execution and finalization
pvisor/src/runtime/completion.rs Commit and validate completion binding Bundle bytes to terminal results
pvisor/src/docker.rs, image/ Per-container VMs, image catalog and shared compressed image pool; ordinary OCI execution remains separate
pvisor-daemon/src/daemon/, runtime.rs APIs, admission, records, supervisors and cgroup/VM ownership
pvisor-daemon/src/native/, prepared_image.rs OpenSandbox/E2B guest protocol adaptation and template publication
pvisor-replay/src/adapter/, bridge/ Trajectory planning, agent selection and native continuation bridges

The CLI owns arguments/display; the runtime owns execution and durable Jobs. VM, filesystem and network implementations stay in their owning crates. Core defines shared policies and wire DTOs. Jobs and daemon reuse in-process control while retaining their own authentication, admission, storage and confirmed cleanup.

See Core architecture for public interfaces and migration status. Only VM, OverlayFS and Journal currently use a sole api entry. Other crates retain their actual exports; this logical table does not imply unified Rust visibility or stability.

Core implementation boundaries

Core defines Operation, Event and shared policies; pvisor implements admission, actual rewrites, Placement and scheduling. Session owns Attempt resources/terminal states; executors execute and return observations; OverlayCore owns file application/recovery. AgentCtl and approval socket I/O remain in pvisor. See Core architecture for ownership and Operation and Event for fields/order.

Core reduction budget

CI checks the default runtime and application dependency boundaries before the capture-enabled distribution. scripts/ci/check_core_budget.py rejects the CLI, Gateway, replay, Clap, TUI and terminal dependencies in the normal pvisor closure. The default pvisor-cli application includes the replay engine and integrated TUI, while Gateway remains optional. It records toolchain, runtime/application dependency counts, runtime-closure source lines, public Core declarations and application binary bytes. The dependency count, closure source lines and Core declarations ratchet against a committed baseline (scripts/ci/core-budget.json): any metric above the baseline fails CI, so no absolute caps are hand-maintained in the CI config. A change that intentionally grows the budget regenerates the baseline with mise run update-core-budget in the same commit, keeping the exact delta reviewable; regenerating after a reduction tightens the baseline to reality. The script owns budget/measurement definitions; CI reports contain measured results. Compare on the same platform/toolchain.

Contributor commands

Command Purpose
mise run build / mise run build -- release Build all workspace executables supported on this platform; sign Hypervisor entitlement on macOS
mise run install-cli Install signed release CLI to CARGO_INSTALL_ROOT or ~/.cargo
mise run wheel / mise run wheel -- debug Build a fresh wheel; place it in dist/ after installation validation
mise run fw -- build / mise run fw -- test Build firmware or run bundle/ABI regressions
mise run doctor / mise run doctor -- test Tool/platform diagnostics; mise installs missing tools first
mise run check Compilation checks for the product and its dependencies
mise run fmt / mise run ci -- "fmt-rust --check" "fmt-py --check" Format Rust/Python or check formatting
mise run lint Clippy, Python and workflow lint
mise run test Workspace Rust tests via nextest, followed by Python tests
mise run test -- core pvisor cli Selected Rust packages: shared contracts, runtime and application
mise run test -- cli / mise run test -- pvisor-cli Executable/frontend tests; mise run test -- pvisor selects runtime tests
mise run test -- pvisor-vm VM-owner tests; macOS signs Hypervisor entitlement before nextest
mise run test-py -- -k packaging Filter Python unittest checks in both test directories
mise run test-py -- discover -s benchmark/support Benchmark tool unittest checks; also included in default Python tests
PVISOR_TEST_VM_BIN=target/release/pvisor mise run test-py -- tests/test_vm_terminal.py Real VM terminal/TUI interaction regressions
PVISOR_TEST_ZCODE=1 mise run test-py -- tests/test_zcode_integration.py Explicit integration requiring Linux rootless, FUSE3 and zcode
mise run test-isolation Strict Linux rootless/FUSE regressions; missing user namespaces do not skip checks
mise run smoke Build debug CLI and check main commands
mise run examples Build release CLI and run all examples; append names for a subset
mise run cases -- --case S-DOC-001,S-DOC-002 Selected documentation cases
mise run benchmark / mise run benchmark -- nightly Process and Run Bundle benchmarks
mise run docs-build Build bilingual documentation and check links
mise run docs-serve / mise run docs-serve -- en Native Zensical preview with live reload; Chinese on port 3000, English on port 3001
mise run ci Check format/lint/tests and build without rewriting source
mise run clean Remove build artifacts, preserving development environment/local Run records

mise run test/mise run test-rust accept Cargo package names and aliases pvisor, cli (pvisor-cli), core, control/agentctl (Core compatibility aliases), capture (Gateway) and shim (pvisor-shim). With package arguments, mise run test runs only those Rust tests. CI shards use mise run test-rust without additionally running Python tests.

Runtime-only Rust tests remain in crates/pvisor/tests/. Executable/frontend integration test files, including mixed runtime/command tests, live in crates/pvisor-cli/tests/; mixed files retain their runtime-only cases in pvisor. Native VM and environment-dependent tests keep their existing prerequisites and skip/ignore gates; compile checks do not validate real guests.

Default unittest discovery includes tests/ and benchmark/. Rust tests in pvisor-core verify shared Operation/Overlay contracts. Benchmark tests requiring /proc and Linux rootfs tools run only on Linux.

VM filesystem checks run inside the Linux guest and require root, Python and tar. From the repository run PVISOR_TEST_GUEST_FS_DIRS=/var/tmp:. mise run test-py -- tests/test_vm_filesystem.py to check guest root/workspace filesystems separately. Checks skip without directory configuration; explicitly enabled failures are errors.

For a specific Rust integration test/filter, pass nextest arguments after --, for example mise run test-rust -- pvisor-gateway -- --test llm_fixtures. nextest excludes doctests; use cargo test --doc -p <package> when needed.

CI responsibilities

Workflow Trigger and responsibility
CI Push/PR to main/develop: format, Clippy, actionlint, Python/benchmark-tool/Rust tests, documentation cases and examples
Documentation Documentation changes: bilingual build/link checks; only upstream main deploys Pages
pVisor Benchmark Runtime/build/benchmark changes: compare with PR base/previous commit and upload reports
Nightly Build Daily or manually on main: build/validate two-platform wheels and update nightly release
Publish PyPI Stable tag: check version, lockfile and main ancestry before publishing; manual runs build/validate only

Keep required status CI: any failed, canceled or skipped dependency fails it. Linux Rust tests shard into core/Gateway/pVisor; macOS runs the same package set once. The separate Linux isolation job requires user namespaces/FUSE and cannot skip isolation checks. Filesystem examples/documentation cases share its release build/isolation environment. Network/Gateway examples run separately.

The shared setup action installs the same mise tools and Python development dependencies as local development. Its native-firmware input prepares macOS native firmware tools, and static-musl installs Linux system build libraries; tool versions come from mise.toml. A reusable workflow owns the two-platform wheel matrix, including mise with the same configuration inside manylinux. PR documentation builds do not cancel Pages deployments.

VM guest startup

pvisor-guest provides the shared GuestConfig library and the pvisor-guest executable. With pvisor-vm's init-blob feature, crates/pvisor-vm/build.rs uses Rust's rust-lld to build a release Linux musl ELF for the VM architecture. Separate target/pvisor-guest/ avoids competing with the outer Cargo artifact lock. pvisor-vm embeds the ELF as /init.krun, which becomes guest PID 1.

CLI/shim inject /.pvisor-guest.json containing argv, environment, cwd, workspace mounts, limits, optional networking and shim agent arguments. The supervisor initializes guest filesystems/console I/O, mounts the workspace, configures networking, launches the workload directly and reaps children. On exit, it reports the workload exit code using libkrun's private rootfs ioctl 0x7602, then syncs/reboots. Nonzero exit fails the Attempt; normal VM shutdown without a reported code fails with 125.

See Guest init comparison for measurements and scope.

Packaging and names

The PyPI distribution uses pvisor-sandbox; the Python import package, installed CLI and runtime Rust crate use pvisor; the application crate is pvisor-cli, and companion crates use pvisor-*; environment variables use PVISOR_*. Wheel names follow pvisor_sandbox-<version>-py3-none-<platform>.whl.

Build environment

Use the Rust toolchain pinned in mise.toml, the default LLVM backend and platform linker. mise run setup installs the current architecture's guest target. Rust's bundled linker builds the guest supervisor as a static Linux musl ELF; macOS VM builds do not require Zig.

Artifact Linux Apple Silicon macOS
Host CLI Static Linux musl ELF Native Darwin executable with HVF entitlement
Embedded guest Static Linux musl ELF Static Linux musl ELF
pvisor-vm Single Rust runtime crate Single Rust runtime crate
Guest kernel Compressed and embedded at build time Compressed and embedded at build time

CARGO_TARGET_DIR selects native build output shared by build/install/smoke/examples/cases. Wheel verification uses a fresh staging directory so old dist/ packages cannot be mistaken for new artifacts. Linux CLI links musl statically and embeds the VM kernel. The mise installation and setup provide Zig, cargo-zigbuild and the Linux musl target. Linux wheels retain manylinux_2_28 for glibc Python installers.

Documentation tasks use an isolated uv environment with the same pinned Zensical as CI; no separate docs virtual environment is required.

See Release process and Reproducible examples for releases/runtime requirements.

The private runtime modules use a libkrun 1.19.3 base with selected upstream backports. See the upstream synchronization ledger for source commits, local adaptations and validation limits; the version number does not imply a complete 1.19.6 or 2.0 upgrade.

VM API boundary

pvisor_vm::api is the sole external runtime interface. It declares portable structs and trait signatures with no conditional compilation or method bodies. Private modules implement the contracts; consumers import VmConfiguration, VmRuntime, VmControl and the snapshot/RAM traits they use. Platform services use RuntimeSupport on VmPlatform.

CLI, shim, examples and the init benchmark use this interface. Low-level register/device tests and hardware probes belong inside pvisor-vm. Static kernel extraction/packing lives in crates/pvisor-vm/build_kernel.rs; its existing build environment variables remain compatible. Firmware data ABI and OS FFI remain private. Historical trace/receipt identifiers and benchmark evidence retain their original names.

Run mise run test -- pvisor-vm; macOS signs its test executables with the existing Hypervisor entitlement. Real VM tests require host HVF/KVM access; Linux VM-creation tests require /dev/kvm.