Skip to main content
A volume is a named, owner-owned store of files that lives outside any single sandbox. Unlike a sandbox’s own filesystem — which is ephemeral, private, and gone when the sandbox is killed — a volume persists, and you can attach it to any number of sandboxes at a mount path. Volumes carry data (datasets, model weights, an agent’s working tree); templates define the base image a sandbox boots from.

Two kinds of volume

Copy-modeFile-granular
What it isA gzip tar archive hydrated into the sandbox’s filesystem at bootA persistent, shared filesystem you can edit directly — with or without a running sandbox
Reads/writesRead-at-boot; guest writes stay private to that sandbox and never flow backLive read and write; changes are shared and durable
Attach modescopymount (read-write) or mount-ro (read-only)
Edit without a sandboxNoYes — read/write/list/delete files over the API
Best forSeeding many parallel sandboxes from the same source (fanout, CI)A shared workspace multiple sandboxes (or agents) read and write concurrently
Both are owner-scoped — you can only attach your own volumes.

Create and attach (copy-mode)

Upload a gzip tar archive once, then attach it to sandboxes at create time. On boot, the archive is materialized under the attachment’s mount_path before the first command runs.
from declaw import Sandbox, Volumes, VolumeAttachment

# Upload once (bytes, a file-like object, or a path to a file/dir)
vol = Volumes.create("training-data", data="./data")
print(vol.volume_id, vol.size_bytes)

# Attach to any number of sandboxes
sbx = Sandbox.create(
    template="python",
    volumes=[VolumeAttachment(volume_id=vol.volume_id, mount_path="/data")],
)
print(sbx.commands.run("ls /data").stdout)
The same volume can be attached to many sandboxes in parallel — each gets its own private copy, so there’s no contention.

File-granular volumes (live mounts)

A file-granular volume is a shared filesystem you can edit from the SDK with no sandbox running, and live-mount into a sandbox so reads and writes go straight to the shared volume. Create one empty (or seed it from an archive), edit its files directly, then mount it read-write.
from declaw import Sandbox, Volumes, VolumeAttachment

# Create an empty file-granular volume and edit it without a sandbox
vol = Volumes.empty("workspace")
files = Volumes.files(vol.volume_id)
files.write("/notes.txt", b"seeded from the SDK\n")
print(files.list("/"))

# Live-mount it read-write; guest writes are shared and durable
sbx = Sandbox.create(
    template="base",
    volumes=[VolumeAttachment(
        volume_id=vol.volume_id, mount_path="/work", mode="mount",
    )],
)
sbx.commands.run("echo 'from the sandbox' >> /work/notes.txt")
print(files.read("/notes.txt"))   # includes both lines
Use mode="mount-ro" for a read-only mount (guest writes are rejected). Live mounts require a file-granular volume; copy-mode volumes can only be attached with mode="copy".

Mount a sub-path

Mount just part of a volume with subpath (live-mount only — the server rejects subpath on a copy attachment):
VolumeAttachment(
    volume_id=vol.volume_id, mount_path="/data", mode="mount",
    subpath="datasets/train",   # mounts <volume>/datasets/train at /data
)

Snapshot and commit

Capture filesystem state from a running sandbox into a new volume — the source is never modified:
# Any absolute in-sandbox path -> new volume
vol = Volumes.snapshot(sbx.sandbox_id, path="/workspace/out", name="run-42")

# An already-attached volume's mount path -> new volume
checkpoint = Volumes.commit(sbx.sandbox_id, volume_id=src.volume_id, name="checkpoint")
snapshot captures any in-sandbox path; commit captures the mount path of a volume already attached to that sandbox. Both return a new volume and leave the source untouched. Synthetic paths (/proc, /sys, /dev) are rejected.

Coordinate writers with advisory locks

When several sandboxes share a live-mounted volume, coordinate writers with advisory leases over a (volume, path) pair. acquire returns a token you present to renew / release:
locks = Volumes.locks(vol.volume_id)

lease = locks.acquire("/data/model.bin", ttl_seconds=60)   # 409 if already held
token = lease["token"]
locks.renew("/data/model.bin", token, ttl_seconds=60)
print(locks.status("/data/model.bin"))                     # {"held": True, "expires_in_ms": ...}
locks.release("/data/model.bin", token)
Locks are advisory — they coordinate cooperating writers; they don’t block I/O from code that ignores them. For atomic read-modify-write, file-granular writes also support a compare-and-swap (if_version) check.

Limits

  • Format: uploads are gzip-compressed tar archives (application/gzip). Only regular files are materialized — symlinks, hardlinks, device nodes, and entries containing .. are dropped for safety.
  • Upload size: the upload body is capped at 4 GiB.
  • File-granular capacity: a flat 64 GiB per-volume cap (a hard abuse-prevention ceiling, not a per-tier quota — volumes are not tier-gated).
  • Ownership: strictly owner-scoped; you can attach only your own volumes. A volume that is still live-mounted by a sandbox cannot be deleted.

VolumeInfo model

FieldTypeDescription
volume_idstrUnique identifier
namestrHuman-readable name you set at creation
size_bytesintCurrent size of the volume’s contents
created_atstrWhen the volume was created (ISO-8601 string)
metadatadictArbitrary key-value pairs attached at creation
quota_bytesintCapacity cap for file-granular volumes (0 = unlimited)

Use cases

Upload a dataset once, then fan out many parallel sandboxes that each read the same files at boot — no per-sandbox upload step.
vol = Volumes.create("dataset", data="./big-dataset")
workers = [
    Sandbox.create(template="python",
                   volumes=[VolumeAttachment(volume_id=vol.volume_id, mount_path="/data")])
    for _ in range(10)
]
Live-mount one file-granular volume into several sandboxes so a team of agents reads and writes a common workspace. Serialize concurrent writers with advisory locks.
vol = Volumes.empty("shared-workspace")
a = Sandbox.create(volumes=[VolumeAttachment(vol.volume_id, "/work", mode="mount")])
b = Sandbox.create(volumes=[VolumeAttachment(vol.volume_id, "/work", mode="mount")])
# Both see each other's writes under /work
Capture a sandbox’s output directory into a new volume after an expensive step, so you can restore or share it later.
sbx.commands.run("python3 train.py --out /workspace/model")
artifact = Volumes.snapshot(sbx.sandbox_id, path="/workspace/model", name="model-v3")
For the full method reference, see the SDK volume guides (Python, TypeScript, Go) and the Volumes API. For runnable examples, see the Volumes cookbook.