boxkite

Capability

Independent storage volumes

A volume is independent, dynamically-provisioned block storage with its own lifecycle — create it once, mount it into any number of sandboxes you create afterward, and it survives past any single sandbox session being destroyed. This is a different thing from the workspace/uploads/outputs storage every sandbox already gets automatically: those are per-session and synced to object storage; a volume is a standing resource you explicitly create, mount, and eventually delete.

Opt-in, and the underlying provisioner isn't wired to a live cluster yet

This capability is off by default (BOXKITE_VOLUMES_ENABLED=false) — every /v1/volumes route 404s until an operator explicitly opts in. The create/provision/delete dispatch logic is implemented and covered by tests, but real Kubernetes PersistentVolumeClaim provisioning (K8sVolumeProvisioner.provision) does not yet run against a live cluster — same status as the declarative builder's own KanikoJobBuildRunner.run_build.

Read docs/EXTERNAL-STORAGE-MOUNTING-DESIGN.md's Volume addendum before relying on this. Don't confuse it with that same document's FUSE object-storage mounting section — that's a different, unrelated (and much less finished) capability for mounting an existing S3/GCS/Azure bucket.

Create a volume

size_gb is required. The build is always asynchronous — the call below returns immediately with a queued volume, not a ready one.

create_volume.py
volume = client.create_volume(label="model-weights", size_gb=20)
print(volume["id"], volume["status"])  # "vol_...", "queued"

Poll status, then mount it into a sandbox

status progresses queued creatingready (or failed at any stage). Once ready, pass a {volume_id: mount_path} mapping into sandbox creation. A volume that isn't owned by your account or isn't yet ready404s the sandbox-create call rather than silently omitting the mount — you can never mistakenly believe a volume is attached when it isn't. mount_path must be an absolute path outside the sandbox's existing typed roots (/workspace, /mnt/*, /tmp).

use_volume.py
volume = client.get_volume(volume["id"])
if volume["status"] == "ready":
    sandbox = client.create_sandbox(
        label="training-job",
        volume_mounts={volume["id"]: "/data"},
    )

List and delete

Deleting a volume removes the control-plane's bookkeeping row and its underlying PersistentVolumeClaim — it does not retroactively unmount it from any already-running sandbox session; Kubernetes itself keeps a bound PVC alive until the pod using it is gone.

manage_volumes.py
client.list_volumes()
client.delete_volume(volume["id"])