Local Files

Mount storage into a sandbox with SandboxConfig.fs. Each entry defines a backend, a mount path inside the sandbox, and an acls list that controls access (see File Access). You can combine multiple backends in one sandbox, mount paths must be unique and non-overlapping.

BackendPersists beyond the sandbox?Use it for
localNo (unless snapshotted)Ephemeral scratch space. The default.
gdriveYesFiles that should land in a user's Google Drive.
gcsYesDurable object storage / shared datasets.
s3YesAmazon S3 or an S3-compatible object store.
azureYesAzure Blob Storage.
onedriveYesFiles that should land in a user's Microsoft OneDrive.
externalYesA file system you back with your own HTTP host.

This page covers the local and external backends. For the cloud backends see GCS, S3, Azure Blob, Google Drive, and OneDrive.

Local

Stores data on the sandbox's own disk. No external dependencies; discarded when the sandbox is destroyed (unless captured with a Snapshot). This is the default when you omit fs.

index.ts
import { getOrCreateSandbox } from "@hiver.sh/client";

const sandbox = await getOrCreateSandbox("my-sandbox", {
  fs: [{
    backend: "local",
    mount: "/workspace",
    acls: [{ path: "/workspace/**", access: "rw" }],
  }],
});

Mount a host directory (Docker runtime only)

During local development, set origin to mount a directory from your machine straight into the sandbox, changes are visible both ways with no copying. This is only supported with the local Docker runtime and is ignored in cloud deployments.

index.ts
fs: [{
  backend: "local",
  mount: "/workspace",
  origin: "/Users/me/projects/my-project", // host path mounted into the sandbox
  acls: [{ path: "/workspace/**", access: "rw" }],
}]

Hot reload

Because the mount is a live view of your host directory, a file watcher inside the sandbox picks up edits you make in your editor instantly, no rebuild, no re-upload. Mount your project and run the app under a reloader:

index.ts
const sandbox = await getOrCreateSandbox("dev", {
  image: "python",
  fs: [{
    backend: "local",
    mount: "/workspace",
    origin: process.cwd(), // your project, live-mounted
    acls: [{ path: "/workspace/**", access: "rw" }],
  }],
});

// uvicorn --reload watches /workspace and restarts on every save you make locally
const proc = await sandbox.execStream(
  ["uvicorn", "app:main", "--host", "0.0.0.0", "--port", "8000", "--reload"],
  { cwd: "/workspace" },
);
for await (const chunk of proc.pipes) process.stdout.write(chunk.stdout ?? "");
// edit app.py in your editor → uvicorn reloads inside the sandbox

The same works for any watcher, nodemon, watchexec, flask --debug, a test runner in --watch mode. Reach the running server through the proxy: sandbox.proxyUrl(8000).

Skill iteration

Agents often load a directory of "skills" (prompts, scripts, tool definitions) at runtime. Mount that directory with origin so you can tweak a skill on your host and have the agent pick up the new version on its next run, no image rebuild between iterations:

index.ts
const sandbox = await getOrCreateSandbox("agent-dev", {
  image: "claude",
  env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
  fs: [
    { backend: "local", mount: "/workspace", acls: [{ path: "/workspace/**", access: "rw" }] },
    {
      backend: "local",
      mount: "/home/agent/.claude/skills",
      origin: "./skills",              // edit skills locally, agent sees them live
      acls: [{ path: "/home/agent/.claude/skills/**", access: "ro" }],
    },
  ],
});

// tweak ./skills/*.md on your host, then re-run, no rebuild needed
await sandbox.exec(["claude", "-p", "Use the pdf-report skill to summarize report.pdf"], {
  cwd: "/workspace",
});

Mounting the skills directory ro means the agent can read and use the skills but can't modify them, you stay in control of the source on your host.

Multiple mounts

Combine backends by listing several entries. Each mount is independent, writing to one never touches another.

index.ts
fs: [
  { backend: "local", mount: "/workspace", acls: [{ path: "/workspace/**", access: "rw" }] },
  {
    backend: "gcs",
    mount: "/data",
    gcs_bucket: "my-data",
    gcs_service_account_json: process.env.GCS_SERVICE_ACCOUNT_JSON!,
    acls: [{ path: "/data/**", access: "ro" }], // read-only dataset from GCS
  },
]

External

Back a mount with your own storage by implementing a small HTTP host. Set backend: "external" and point host at your server, each file operation the agent performs (read, write, list, delete) becomes one call against it. Use this for a database, an internal object store, or any system without a built-in backend.

index.ts
fs: [{
  backend: "external",
  mount: "/data",
  host: "https://my-fs-host.internal", // trailing slash ignored
  acls: [{ path: "/data/**", access: "rw" }],
}]

An external host that serves fixtures from memory is the easy way to hand an agent canned files for tests and evals, see Mocking.

Hiding a mount from the agent

Set internal: true on any file system to mount it for the sandbox runtime but hide it from the agent. ACLs are ignored (the agent can't reach it at all). This is used for storage the sandbox needs but the agent must not see, for example, a remote-backed snapshot store.

Lazy, cached mounts (async)

By default a cloud-backed mount is read-through: the agent always sees the latest backend state, and every content read is a fresh synchronous round-trip — the same file re-downloads on every open, so the agent never observes a stale copy.

Set async: true to run the mount in fetch-on-read, cache-after mode:

  • Nothing is fetched up front. The mount comes up instantly; startup issues no GET. (There is no eager background pull — the mount does not walk the backend downloading objects.)
  • Metadata is synchronous, on demand. stat and directory listings hit the backend when asked (results are briefly cached), so the agent sees what exists without downloading anything.
  • Content is fetched lazily and synchronously, on the first explicit read. Opening a file for reading pays exactly one GET; the bytes land in the local buffer and every later read of that file is served locally — no repeat GET.
  • Writes are buffered and uploaded in the background (the buffer is kept, so a file you wrote reads back from the buffer without a fetch).
index.ts
{
  backend: "gcs",
  mount: "/workspace",
  async: true,
  gcs_bucket: "my-bucket",
}

The contract in one line: a GET (content download) happens only in response to an explicit read, never before.

Worked example — what hits the backend, when

Given a backend holding /workspace/report.md and /workspace/data/big.csv, on a fresh async mount:

mount                        (nothing  no GET)
ls /workspace                LIST            (metadata only)
stat /workspace/report.md    STAT            (metadata only; no content)
cat /workspace/report.md     GET report.md   (first read: one content fetch  cached)
cat /workspace/report.md     (served from local buffer  no GET)
echo hi > /workspace/note    (buffered locally; PUT note uploads in the background)
cat /workspace/note          (served from local buffer  no GET)

big.csv is never downloaded unless something actually reads it.

A symlink has no native object type in a blob store, so it is persisted as a companion object at <path>.symlink whose body is the link target. This round-trips through async without ever downloading a linked file eagerly:

ln -s /library/x.md out.md   PUT out.md.symlink  (body = "/library/x.md")
ls /workspace/output         LIST  out.md shown as a symlink   (no GET)
stat out.md                  STAT out.md.symlink (metadata only; reported as a link)
readlink out.md              GET out.md.symlink  (explicit read of the link  target)
cat out.md                   GET x.md            (reads the *target*, resolved lazily)

Trade-off

Because content is cached after the first read, an async mount is eventually consistent for content: an out-of-band change to a file already in the local buffer isn't re-fetched. Use async when the workspace is effectively owned by one sandbox at a time (the common agent case) and you want backend durability without paying download latency up front or on every repeated read. Leave it off (the default read-through) when the agent must observe out-of-band backend content changes live. It has no effect on local backends, which have no backend to sync with.


Next: Google Cloud Storage