battery is a MicroVM Warm Pool Manager for flintlock. It manages pools of pre-provisioned, pre-booted microVMs so consumers can claim an already-running VM instantly, instead of waiting for flintlock to provision one on demand. It's part of the liquidmetal-dev family of projects, alongside flintlock and guest-agent.
Status: early-stage / pre-alpha. The gRPC API surface is defined, but the pool reconciliation, replenishment, and lease logic is not yet implemented.
battery is made up of three binaries:
-
poolmgrd(cmd/poolmgrd) — the central pool manager daemon. It serves the gRPC API defined underapi/proto/poolmgr/v1alpha1:PoolAdmin— create, update, delete, and list pool definitions.Lease— claim a VM from a pool, heartbeat it, and release it back.Events— a server-streaming subscription for pool/VM/lease lifecycle events.
It's designed to reconcile a fleet of flintlock hosts against each pool's desired state, replenishing VMs using a per-pool strategy, and, once implemented, persist state (e.g., in an embedded SQLite database).
-
poolmgr-hostagent(cmd/poolmgr-hostagent) — a sidecar that runs alongside each flintlock host. Flintlock's guest-agent is only reachable over a host-local vsock socket, so this sidecar proxiesexec/pingcalls to the in-VM guest-agent onpoolmgrd's behalf, exposed via theHostagentgRPC service. -
poolmgrctl(cmd/poolmgrctl) — a CLI client for the pool manager's gRPC API. It connects topoolmgrdto manage pool definitions (CRUD), claim and release leases, list leases, and subscribe to pool/VM/lease lifecycle events. It holds no server-side state of its own.
See docs/design/2026-09-05-microvm-warm-pool-manager-design.md
for the full design rationale and decisions, and
docs/runbooks/e2e-manual-verification.md for the
manual end-to-end verification runbook against a real flintlockd + Firecracker VM.
- Go 1.25+
- mise (recommended) to install pinned tool versions from
mise.toml— buf, golangci-lint,protoc-gen-go, andprotoc-gen-go-grpc.
poolmgrctl is a CLI client that connects to poolmgrd's gRPC API. It can be used to manage
pools and leases. By default, it connects to 127.0.0.1:9090 — change this with --addr.
Other connection flags include --insecure (disable TLS), --ca-file, --cert-file, and
--key-file (for mTLS):
poolmgrctl pool list --addr 127.0.0.1:9090 --insecure
poolmgrctl lease claim --pool web --namespace default --addr 127.0.0.1:9090 --insecure
poolmgrctl events tail --pool web --namespace default --addr 127.0.0.1:9090 --insecurego build ./...
go vet ./...
go test ./...
golangci-lint runThe gRPC API is defined in api/proto using buf. After editing a
.proto file, regenerate the Go code with:
./hack/generate-proto.shReleases are cut by pushing a semver tag matching v*.*.* (e.g. v0.1.0)
to main. This triggers .github/workflows/release.yml, which:
- builds
poolmgrdandpoolmgrctlvia GoReleaserpoolmgrdis built forlinux/amd64andlinux/arm64and published as a multi-arch container image toghcr.io/liquidmetal-dev/poolmgrd:<version>(and:latest) — note the image tag drops the leadingvfrom the git tag (e.g. taggingv0.1.0producesghcr.io/liquidmetal-dev/poolmgrd:0.1.0), unlike the GitHub release itself, which keeps itpoolmgrctlis built as a CLI binary forlinux/amd64,linux/arm64, anddarwin/amd64/darwin/arm64and made available as archives in the GitHub release. No container image is published forpoolmgrctl— it is an operator-run client tool
- creates a GitHub release with a changelog grouped by commit type
- pushes
api/proto(PoolAdmin/Lease/Events/types) to the Buf Schema Registry atbuf.build/liquidmetal-dev/battery, creating the BSR module on first push if it doesn't exist yet, and labels the push with both the release tag and the movingmainlabel —mainis whatbuf breaking(below) compares PRs against, so every release advances it
One-time setup: a BUF_TOKEN repository secret (a Buf Schema Registry
API token with write access to liquidmetal-dev/battery) must exist
before the first tag is pushed. Until the first successful buf push,
the buf breaking check in CI (.github/workflows/ci.yml) has nothing
to compare against — that specific "no baseline yet" failure is treated
as a pass with a warning, but any other buf breaking failure (a real
incompatible schema change) still fails the job normally, before and
after the first release.
If buf push fails after GoReleaser has already published successfully,
the proto schema push can be re-run manually without re-cutting the
release: buf push --create --create-visibility public --label <tag> --label main from a checkout of that tag.
git tag v0.1.0
git push origin v0.1.0Apache License 2.0 — see LICENSE for details.
Thanks to @phoban01 for the original idea of using warm pools with flintlock, based on his work building a GitLab executor that used flintlock warm pools.