Fleet
Host daemon: admission, reclaim and metering
One daemon owns each machine: it checks room before any VM starts, reclaims memory and meters every VM.
flashhostd is the FlashVMM host daemon: one process per machine that sees every FlashVM on it, runs the fit check every create passes, reclaims memory under pressure, keeps sleeping VMs as movable files and meters what every VM uses, over a strict resource-CRUD REST API (OpenAPI 3) on a local socket or pipe, an optional mutual-TLS listener, the flash host CLI and the flash_host MCP tool.
One codebase serves Linux, macOS and Windows with one contract. It needs no registration step: it finds VMs the way flash ls does, across every user's registry, pool spares and sleeping VMs included. It knows nothing of how pools work and asks flashpoold over its API (see pools). A fleet controller drives many hosts through exactly this API.
Key facts
| Runs on | Linux · macOS · Windows |
|---|---|
| Interfaces | CLI flash host · MCP flash_host · REST host API, 14 resources and 30 operations |
| Address | Linux /run/flashhostd.sock · macOS beside the registry · Windows \\.\pipe\flashhostd · optional mutual-TLS listener |
| Admission modes | report (no policy, the default) · enforce |
| Policy defaults | memory_headroom_pct 10 · memory_high_pct 90 · memory_low_pct 80 · history_s 300 · sample_ms 1000 · margin_pct 20 · sleep_on_pressure false |
| Reservations | held until the VM is seen or 120 s pass; up to 4096 in all, 64 per unprivileged user |
| Sleeping-VM transfer | 4 MiB chunks, base64, in order |
How it works
Ask, used, predicted
For every VM the daemon keeps three figures. Ask is what it asked for: cpu_millis (its cap, or all its vCPUs when uncapped), mem_mib, disk_bps, disk_iops. Used is what the host measures: the VMM process's resident memory and CPU, and the guest's disk throughput. Predicted is the p95 of recent use plus margin_pct, once the VM has history_s of history; until then, the full ask. GET /v1/host sums these against capacity and reports free room, pressure and VM counts by state. A VM is counted only when its socket is owned by its registry's owner and answered by a flashvm process.
Admission: the fit check every create passes
flash run, restore, clone, fork, rewind, pool spares and POST /v1/host/flashvms all reserve room with POST /v1/host/admissions before a VM exists. The check holds the host lock from decision to reservation and is idempotent per id. A VM fits when, on each dimension:
- memory promised plus the ask stays within capacity ×
memory_overcommit; - CPU promised plus the ask stays within capacity ×
cpu_overcommit(CPU is time-shared, so it limits only through overcommit); - memory predicted plus the ask stays within capacity less
memory_headroom_pct; - and the host is not under memory pressure.
Under enforce a VM that does not fit is refused with the dimension, rule, need and free room in plain words. Under report it is admitted and the reply's would_refuse says why it would have been refused, so a policy can be tuned on live traffic before it enforces. A reservation that flash run made is released at once if the run fails before its VM is up.
Reclaim under pressure
With a policy in place, when real host memory reaches memory_high_pct the daemon frees room one step at a time, re-reading memory after each, until it is back at memory_low_pct:
- balloon the running VM with the most idle guest memory, ranked by host-observed use;
- drop an idle pool spare through its pool (the pool keeps its target and rebuilds it later);
- with
sleep_on_pressure, sleep the least busy VM in use.
Nothing is ever killed to make room. While pressure lasts, new VMs are refused under enforce and pools do not refill, and every step is an event.
Sleeping VMs as movable files
A sleeping VM is its memory and device state in files, costing no memory or CPU. /v1/host/sleep-images serves each one as a set of files read and written in 4 MiB chunks. An incoming VM is begun with its record, platform (arch and OS) and the guest bundle's fingerprint, the SHA-256 of each bundle file, and must match this host. Room is reserved for it; once every file is complete, PATCH {"state": "ready"} installs it as a VM sleeping here, ready for flash wake. A move in progress marks the source copy, so it cannot be woken or moved twice. This is the mechanism the controller's moves use.
Events and metering
GET /v1/host/events is a cursor-based long poll (since, wait_s up to 60) over created, deleted, state, refused, policy and reclaim's pressure, reclaimed, spare_dropped, slept and pressure_cleared. GET /v1/host/usage is per-VM metering kept per UTC hour, so a date range bills only its own hours: cpu_seconds, mem_mib_seconds, asked_mem_mib_seconds, disk read and write bytes, network in and out bytes, running and sleeping seconds, with totals. It is stored in the daemon's state directory, readable by the daemon only, and an ordinary user sees only their own VMs.
Who may do what
Every read, and reserving room for one's own VMs, is open to anyone who can reach the socket (mode 0666 by default, so every flash run is counted). Changing the host (policy writes, creating and removing VMs through the host, every pool write) needs root, the daemon's own user, or membership in the socket's operator group. The network listener serves only /v1/host resources, and every caller must present a certificate signed by its CA. See security model.
Interfaces
CLI
flash host vms
flash host policy set --admission enforce --memory-overcommit 1.5 --cpu-overcommit 4 --sleep-on-pressure true
flash host admissions
flash host vms create --vcpus 2 --mem-mib 1024 --entry "python3 job.py"
flash host sleeping get job1 --out ./job1
flash host sleeping put job1 --from ./job1
flash host events --since 0 --follow
flash host usage --from 2026-11-01 --json
flash host listen --addr 0.0.0.0:7443 --cert host1.pem --key host1.key --ca ca.pem
MCP
flash_host(method: "GET", path: "/v1/host")
flash_host(method: "PUT", path: "/v1/host/policy", body: {"admission": "enforce", "memory_headroom_pct": 10})
flash_host(method: "GET", path: "/v1/host/events?since=0&wait_s=30")
REST
GET /v1/host
GET /v1/host/flashvms
POST /v1/host/flashvms {"shape": {"vcpus": 2, "mem_mib": 1024}, "entry": "python3 job.py"}
PUT /v1/host/policy {"admission": "enforce", "memory_overcommit": 1.5}
POST /v1/host/admissions {"id": "job1", "vcpus": 2, "mem_mib": 1024}
GET /v1/host/sleep-images/{vm_id}/files/{file}?offset=0
PATCH /v1/host/sleep-images/{vm_id} {"state": "ready"}
GET /v1/host/usage?from=1761955200
GET /v1/host/events?since=0&wait_s=30
Contract an agent must know: a create that does not fit under enforce returns 409 host_full with refusal: {dimension, rule, need, free, message}; a write without operator rights returns 403 forbidden; reservations past the cap return 429 too_many_reservations; GET /v1/host/policy with no policy returns 404 no_policy; another move's mark on a sleeping VM returns 409 being_moved; past 16 open event waits a wait returns 503 busy.
Guarantees
- Every VM is counted. Plain, pooled and sleeping VMs from every registry, with no registration step.
- Room before boot. Every create reserves through one locked, idempotent fit check.
- Refusals explain themselves. The dimension, rule, need and free room come back with every refusal.
- Reclaim never kills. Balloon, shed a spare, then sleep, rechecked after each step.
- Billing by the hour. Host-measured use per VM per UTC hour, readable by owner and operator.