Skip to main content
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 mountPath before the first command runs.

How volumes work

  • Format: only gzip-compressed tar archives (application/gzip). Symlinks, hardlinks, device nodes, and entries containing .. are dropped on the server.
  • Size: the upload body is capped at 4 GiB; a file-granular volume has a flat 64 GiB capacity cap.
  • 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. The body is a Uint8Array or ArrayBuffer; pass a streaming body from disk by reading with fs.readFile, or build it in-memory (see the cookbook for a zero-dep tar writer).
Signature
string
required
Human-readable name. Not used for addressing — the server returns a stable volumeId.
Uint8Array | ArrayBuffer
required
The raw tar.gz bytes to upload.
string
default:"'application/gzip'"
Content-Type header sent with the upload. Leave as the default.
string
Override the API key from environment.
string
Override the API domain (e.g. api.declaw.ai).
number
Per-request timeout in milliseconds. Raise this for multi-GiB uploads.

Volumes.list()

List all volumes owned by the caller, newest first.

Volumes.get()

Fetch metadata for a single volume.
Throws NotFoundError if the volume does not exist or is owned by a different tenant.

Volumes.delete()

Delete the blob and the metadata row.

Attaching to a sandbox

Pass volumes: [...] to Sandbox.create:
VolumeAttachment[]
One or more attachments. Each is { volumeId: string, mountPath: string }. mountPath 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 volumeId can appear in many sandbox-create calls in parallel; each sandbox gets its own materialized copy on its overlay.

File-granular volumes (live mounts)

The volumes above are copy-mode: a tar.gz hydrated into the sandbox at boot, with writes private to each sandbox. A file-granular volume is different — you can edit its files directly from the SDK (no sandbox), and live-mount it into a sandbox so reads and writes go straight to the shared volume.

Create a file-granular volume

Edit files without a sandbox — Volumes.files()

files.info(path) returns a version token; pass it to write(path, data, { ifVersion }) for an optimistic compare-and-set write — a ConflictError (409) means the file changed underneath you.

Live-mount into a sandbox

Use mode: 'mount-ro' for a read-only mount — guest writes are rejected with a read-only-filesystem error. 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):

Snapshot a sandbox’s files into a volume

Capture filesystem state from a running sandbox into a new volume — the source is never modified:
snapshot captures any in-sandbox path; commit captures the mount path of a volume already attached to that sandbox. Both return a new VolumeInfo; the name arg is optional. Synthetic paths (/proc, /sys, /dev) are rejected.

Advisory locks

Coordinate writers to a shared (live-mounted) volume with advisory leases over a (volume, path) pair. acquire returns a token you must present to renew / release:
Locks are advisory — they coordinate cooperating writers; they don’t block I/O from code that ignores them.

VolumeInfo shape

Errors

See also