Fork warm CI machines.

SporeVM is a small aarch64 virtual machine monitor for forkable Linux microVM checkpoints. Start a runtime once, warm it, save it, fork cheap child spores, and resume them on compatible aarch64 hosts.

Target shape: a CI scheduler places 10,000+ shards; each host pulls the selected spore, verifies its chunks, and resumes locally.

# install with mise
$ mise use -g github:sporevm/sporevm@latest

# start a VM and save it when the process receives USR1
$ spore run --image docker.io/library/alpine:3.20 --save live.spore --save-on USR1 \
    'i=0; while true; do echo "tick $i"; i=$((i + 1)); sleep 1; done' &
$ run=$!

# let it warm, trigger the save, then wait for completion
$ sleep 2
$ kill -USR1 "$run"
$ wait "$run"
tick 0
tick 1
spore run: saved spore at live.spore

# fork the saved spore and attach the children together
$ spore fork live.spore --count 4 --out children
$ spore fanout children --for 3s
[000000] tick 2
[000001] tick 2
[000002] tick 2
[000003] tick 2

What makes spore different.

SporeVM is the machine-state primitive below the scheduler: save the warmed VM, fork the artifact, then let hosts verify and resume their assigned child.

Same surface
Linux KVM/aarch64 and Apple Silicon HVF use the same board, CLI, and API shape. Restores stay inside compatible host classes.
Cheap forks
Children share verified memory chunks, rootfs CAS, and sealed disk layers. Same-host forks can use CoW RAM backing; writes become child-local state.
Fleet fan-out
Schedulers place shards; SporeVM verifies and resumes the chosen child locally. spore fanout handles local orchestration, and fleet adapters can inject the same child identity.
Library surface
libspore exposes the product API directly. Zig, the C ABI, and the Go binding share the same result contracts instead of reimplementing CLI behavior.

Spore format

A spore is a checkpoint plus a content-addressed bill of materials.

A local spore directory holds the manifest, verified memory chunks, writable disk layers, and rootfs identity needed to resume. A bundle is the distribution form: it carries the selected memory, writable disk, and rootfs bytes so another host can verify and materialize one child before boot.

manifestmachine contract, rootfs identity, child metadata
memoryBLAKE3-addressed chunks shared across forks
rootfscache-bound in a spore; exact/CAS bytes in a bundle
disksealed writable layers over the immutable rootfs
bundleportable child selection with digest-verified materialization
docs/spore-format.md
<spore>/
|-- manifest.json
|-- chunks/<blake3-hex>
|-- ram.backing
|-- ram.backing.proof
|-- disklayers/blake3/<hex>.json
`-- diskobjects/blake3/<hex>.cluster

<bundle>/
|-- manifest.json
|-- chunkpack.index.json
|-- chunkpacks/000000.pack
|-- rootfs/blake3/<hex>.ext4
|-- disklayers/blake3/<hex>.json
`-- diskobjects/blake3/<hex>.cluster

Benchmarks

Cold start is table stakes. Fast fork is the benchmark.

Sandbox benchmarks measure fresh environments reaching first command. SporeVM publishes that too, then tracks the path that matters for warm CI: fork child spores, verify the selected state, and resume.

Benchmark summary loads when this section enters view.

Integration surfaces

Use SporeVM from the layer you own.

SporeVM is the machine-state primitive. The CLI is the local tool, libspore is the embedding surface, and fleet adapters place compatible hosts without making every child a control-plane object.

  • sporeCLI. Local runs, signal save, fork/fan-out, bundle pack/pull, explicit networking, and named lifecycle.
  • libsporeZig, C ABI, and Go API. Schedulers call the same product operations as the CLI without shelling out or parsing human output.
  • k8sKubernetes as adapter cell. A node agent owns cache, slot admission, and fan-out on compatible hosts; Kubernetes should not model one object per child.
  • limitsCore contract stays explicit. Restores require compatible aarch64 HVF/KVM host classes and no hardened public-cloud multi-tenant isolation claim.