> ## Documentation Index
> Fetch the complete documentation index at: https://docs.declaw.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Volumes

> Attach persistent, owner-owned file stores to sandboxes — copy-mode for read-at-boot fanout, or file-granular live mounts shared read-write across sandboxes.

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](/features/templates) define the *base image* a sandbox boots from.

## Two kinds of volume

|                            | Copy-mode                                                                   | File-granular                                                                             |
| -------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| **What it is**             | A gzip tar archive hydrated into the sandbox's filesystem at boot           | A persistent, shared filesystem you can edit directly — with or without a running sandbox |
| **Reads/writes**           | Read-at-boot; guest writes stay private to that sandbox and never flow back | Live read **and** write; changes are shared and durable                                   |
| **Attach modes**           | `copy`                                                                      | `mount` (read-write) or `mount-ro` (read-only)                                            |
| **Edit without a sandbox** | No                                                                          | Yes — read/write/list/delete files over the API                                           |
| **Best for**               | Seeding 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.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    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)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    import { Sandbox, Volumes } from '@declaw/sdk';

    const vol = await Volumes.create('training-data', tarGzBytes);

    const sbx = await Sandbox.create({
      template: 'python',
      volumes: [{ volumeId: vol.volumeId, mountPath: '/data' }],
    });
    console.log((await sbx.commands.run('ls /data')).stdout);
    ```
  </Tab>
</Tabs>

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.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    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
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    import { Sandbox, Volumes } from '@declaw/sdk';

    const vol = await Volumes.create('workspace');   // empty file-granular
    const files = Volumes.files(vol.volumeId);
    await files.write('/notes.txt', new TextEncoder().encode('seeded from the SDK\n'));

    const sbx = await Sandbox.create({
      template: 'base',
      volumes: [{ volumeId: vol.volumeId, mountPath: '/work', mode: 'mount' }],
    });
    await sbx.commands.run("echo 'from the sandbox' >> /work/notes.txt");
    console.log(new TextDecoder().decode(await files.read('/notes.txt')));
    ```
  </Tab>
</Tabs>

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):

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    VolumeAttachment(
        volume_id=vol.volume_id, mount_path="/data", mode="mount",
        subpath="datasets/train",   # mounts <volume>/datasets/train at /data
    )
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    {
      volumeId: vol.volumeId, mountPath: '/data', mode: 'mount',
      subpath: 'datasets/train',   // mounts <volume>/datasets/train at /data
    }
    ```
  </Tab>
</Tabs>

## Snapshot and commit

Capture filesystem state from a running sandbox into a **new** volume — the source is never modified:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    # 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")
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const snap = await Volumes.snapshot(sbx.sandboxId, '/workspace/out', 'run-42');
    const checkpoint = await Volumes.commit(sbx.sandboxId, src.volumeId, 'checkpoint');
    ```
  </Tab>
</Tabs>

`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`:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    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)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const locks = Volumes.locks(vol.volumeId);

    const lease = await locks.acquire('/data/model.bin', 60);  // 409 if already held
    await locks.renew('/data/model.bin', lease.token, 60);
    console.log(await locks.status('/data/model.bin'));        // { held: true, expiresInMs: ... }
    await locks.release('/data/model.bin', lease.token);
    ```
  </Tab>
</Tabs>

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

| Field         | Type   | Description                                              |
| ------------- | ------ | -------------------------------------------------------- |
| `volume_id`   | `str`  | Unique identifier                                        |
| `name`        | `str`  | Human-readable name you set at creation                  |
| `size_bytes`  | `int`  | Current size of the volume's contents                    |
| `created_at`  | `str`  | When the volume was created (ISO-8601 string)            |
| `metadata`    | `dict` | Arbitrary key-value pairs attached at creation           |
| `quota_bytes` | `int`  | Capacity cap for file-granular volumes (`0` = unlimited) |

## Use cases

<AccordionGroup>
  <Accordion title="Stage data before the sandbox exists">
    Upload a dataset once, then fan out many parallel sandboxes that each read the same files at boot — no per-sandbox upload step.

    ```python theme={null}
    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)
    ]
    ```
  </Accordion>

  <Accordion title="Shared workspace for multiple agents">
    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.

    ```python theme={null}
    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
    ```
  </Accordion>

  <Accordion title="Checkpoint a result into a versioned volume">
    Capture a sandbox's output directory into a new volume after an expensive step, so you can restore or share it later.

    ```python theme={null}
    sbx.commands.run("python3 train.py --out /workspace/model")
    artifact = Volumes.snapshot(sbx.sandbox_id, path="/workspace/model", name="model-v3")
    ```
  </Accordion>
</AccordionGroup>

<Note>
  For the full method reference, see the SDK volume guides ([Python](/sdks/python/volumes), [TypeScript](/sdks/typescript/volumes), [Go](/sdks/go/volumes)) and the [Volumes API](/api-reference/volumes/create). For runnable examples, see the [Volumes cookbook](/cookbook/volumes/upload-and-attach).
</Note>
