Skip to content

SDK

Rust SDK

Rust build-time SDK and Workload IR contract.

Rust has two SDK surfaces:

  • Authoring β€” mvm-sdk: build Workload IR (the same IR the Python/TypeScript decorators emit).
  • Runtime β€” mvm-client: the MvmClient facade for driving machines (create/run/exec/stop/reconfigure), shared with the CLI, the GUI, and the fleet orchestrator.

Current:

  • Workload and app builders;
  • image, source, resources, network, entrypoint helpers;
  • Workload IR emission;
  • static decorator parsing support in mvm-sdk;
  • runtime recording types and lowering.
use mvm_sdk::*;
let workload = workload("worker")
.app(
app("worker")
.source(local_path("."))
.image(nix_packages(["python312"]))
.entrypoint(entrypoint_command(["python", "-m", "worker"]))
.resources(resources(1, 256, 512))
.build()?,
)
.build()?;
emit(&workload)?;

Rust is the right layer for tools that generate or validate Workload IR directly.

The MvmClient trait is the runtime SDK: a LocalBackend drives the host in-process, and a GatewayBackend (feature remote) drives a remote fleet over REST. Both speak the same MachineSpec intent type.

Build a MachineSpec fluently with MachineSpec::builder(name, image) β€” the two required fields are supplied up front, so build() is infallible; cpus (default 1), memory_mib (default 512), and env (accumulates) are optional.

builder itself returns a Result: image is parsed into a RootfsSource (an absolute / ./ / ~/ path or path:<p> is a local rootfs, flake:<ref>#<attr> is a flake output, anything else or oci:<ref> is a registry reference), so a declaration that names nothing is refused here rather than at boot. The spec carries the parsed value and serializes it back as that same string.

use mvm_client::{LocalBackend, MachineSpec, MvmClient};
// inside an async context:
let client = LocalBackend::new();
let spec = MachineSpec::builder("web", "nginx")?
.cpus(2)
.memory_mib(512)
.env("PORT", "8080")
.build();
let machine = client.run_machine(spec).await?;
let out = client
.exec_machine(&machine.id, vec!["nginx".into(), "-v".into()])
.await?;
println!("{}", String::from_utf8_lossy(&out.stderr));
client.stop_machine(&machine.id).await?;

The builder is equivalent to a struct literal β€” every field stays public β€” but reads far better at call sites and lets new optional fields land without churning existing callers.

Embedding it β€” studio, mvmd, and custom frontends

Section titled β€œEmbedding it β€” studio, mvmd, and custom frontends”

connect(Target) returns a Box<dyn MvmClient> and hides the transport, so one piece of UI/service code drives either this host or a remote fleet:

use mvm_client::{connect, MvmClient, Target};
// In-process β€” this host's microVMs (auto-selected VMM). No daemon required.
let local = connect(Target::Local)?; // == mvm_client::LocalBackend::new()
// Remote β€” a hosted fleet or a local sidecar over REST (feature `remote`).
let remote = connect(Target::Gateway {
base_url: "https://fleet.example.com".into(),
token: std::env::var("MVM_TOKEN")?,
})?;
for m in remote.list_machines(Default::default()).await? {
println!("{}", m.id.0);
}

The studio desktop app is this pattern: a GatewayBackend by default, or the in-process LocalBackend when built --features local with MVM_STUDIO_BACKEND=local β€” one dyn MvmClient behind its Tauri commands.

# a frontend that drives machines
mvm-client = { path = "../mvm/crates/mvm-client", features = ["remote"] }
mvm-client-local = { path = "../mvm/crates/mvm-client-local" } # optional, in-process backend

A host-side daemon that manages instances directly β€” the mvmd fleet orchestrator, or your own controller β€” instead links the mvmctl library facade for the runtime types, host shell seam, and the gated host↔guest IPC transport. default-features = false keeps it lean (no async runtime unless you opt into the transport):

# a daemon that runs the host that hosts sandboxes
mvmctl = { path = "../mvm", default-features = false, features = ["hostd-transport"] }
use mvmctl::core::{instance::InstanceStatus, pool::Role, protocol};
use mvmctl::runtime::shell; // host command-execution seam

Rule of thumb: drive sandboxes β†’ the MvmClient facade; run the host that hosts them β†’ the mvmctl facade.