Skip to main content
from declaw import Sandbox, Volumes, VolumeAttachment
A volume is a tenant-owned blob (gzip-compressed tar archive) that lives in Declaw’s object store. You upload a volume once with Volumes.create(...) and attach it to any number of sandboxes at create time via Sandbox.create(volumes=[...]). On boot, Declaw streams the blob from object storage and materializes its regular-file entries under the attachment’s mount_path before the first command runs. Volumes are the right fit when you want to:
  • Re-use the same dataset, model weights, or reference code across many short-lived sandboxes without re-uploading bytes each time
  • Stage data before the sandbox exists (CI pipelines, fanout workloads)
  • Let multiple parallel sandboxes read the same files without a per-sandbox upload step

Phase 1 contract

  • Format: only gzip-compressed tar archives (application/gzip). Any file-type metadata in the tar is honored; symlinks, hardlinks, device nodes, and entries containing .. are dropped for safety.
  • Size: upload body is capped at 4 GiB.
  • Semantics: read-at-boot. A volume is materialized into each sandbox’s overlay filesystem when it attaches. Writes inside the sandbox are private to that sandbox and never flow back to the volume.
  • Ownership: a volume is strictly owner-scoped. You can attach only your own volumes.

Volumes.create()

Upload a tar.gz and register it.
# From a bytes object
with open("dataset.tar.gz", "rb") as f:
    vol = Volumes.create(name="training-set-v1", data=f)

# From a filesystem path — directory or file is tarred in-memory
vol = Volumes.create(name="training-set-v1", data="/path/to/dir")

# From raw bytes already in memory
vol = Volumes.create(name="training-set-v1", data=raw_bytes)

print(vol.volume_id, vol.size_bytes)
name
str
required
Human-readable name. Not used for addressing — the server returns a stable volume_id.
data
bytes | BinaryIO | Iterable[bytes] | str | PathLike
required
The blob body. bytes, a file-like object open in binary mode, and iterables of byte chunks are streamed as-is. A path-like pointing at a file or directory is tarred and gzipped in-memory (convenience for small trees; pre-build the archive for large ones).
content_type
str
default:"'application/gzip'"
Content-Type header sent with the upload. Leave as default for Phase 1.
api_key
str | None
Override the API key from environment.
domain
str | None
Override the API domain (e.g. api.declaw.ai).
request_timeout
float | None
Per-request timeout in seconds. Bump this for multi-GiB uploads (default httpx timeout is 30s).
Returns: a Volume with volume_id, owner_id, name, blob_key, size_bytes, content_type, metadata, and created_at.

Volumes.list()

List all volumes owned by the caller, newest first.
for vol in Volumes.list():
    print(vol.volume_id, vol.name, vol.size_bytes)

Volumes.get()

Fetch metadata for a single volume.
vol = Volumes.get("vol-abc123")
Raises NotFoundException if the volume does not exist or is owned by a different tenant.

Volumes.delete()

Delete the blob and the metadata row.
Volumes.delete("vol-abc123")
Idempotent on a 404 — callers that don’t care about “already gone” can ignore the exception.

Attaching to a sandbox

Pass volumes=[...] to Sandbox.create():
from declaw import Sandbox, VolumeAttachment

vol = Volumes.create(name="dataset", data="/path/to/dir")

sbx = Sandbox.create(
    template="python",
    timeout=600,
    volumes=[VolumeAttachment(volume_id=vol.volume_id, mount_path="/data")],
)

# The files are already visible by the time the first command runs
print(sbx.commands.run("ls -la /data").stdout)
volumes
list[VolumeAttachment] | list[dict]
One or more attachments. Each is either a VolumeAttachment(volume_id, mount_path) dataclass or a plain {"volume_id": ..., "mount_path": ...} dict. mount_path must be an absolute path and must not target a system directory (/, /etc, /usr, /proc, /sys, /dev, /bin, /sbin, /lib, /lib64, /var, /run, /boot).
The same volume_id can appear in many sandbox-create calls in parallel; each sandbox gets its own materialized copy on its overlay.

Async

Every call has an awaitable mirror:
from declaw import AsyncSandbox, AsyncVolumes, VolumeAttachment

vol = await AsyncVolumes.create(name="dataset", data=open("dataset.tar.gz", "rb"))
sbx = await AsyncSandbox.create(
    template="python",
    volumes=[VolumeAttachment(volume_id=vol.volume_id, mount_path="/data")],
)

What Volume looks like

@dataclass
class Volume:
    volume_id: str        # "vol-..."
    owner_id: str
    name: str             # human-readable, supplied at create
    blob_key: str         # object-store path, for reference
    size_bytes: int
    content_type: str     # "application/gzip"
    created_at: str
    metadata: dict[str, str]

    def attach(self, mount_path: str) -> VolumeAttachment:
        ...

Errors

SituationExceptionHTTP
Volume not found or not owned by callerNotFoundException404
mount_path is a system directory or relativeInvalidArgumentException400
Referenced volume_id doesn’t belong to caller at attach timeAuthenticationException403
Upload body exceeds 4 GiBInvalidArgumentException413