Agent-readable docs index: /llms.txt. Full docs in one file: /llms-full.txt. Download /docs.zip to grep all markdown files locally.

Mounts

Preview: the mounts API is new. Endpoints, request shape, and SDK method names may change before GA.
Mount remote filesystems directly into your sandbox so your code reads and writes them like local files — no SDK calls, no chunked downloads, no boilerplate. Perfect for pulling in datasets, model weights, or a customer's bucket without staging anything to disk first.
typescript
import { Sandbox } from "@opencomputer/sdk"; const sandbox = await Sandbox.create(); await sandbox.mounts.add({ path: "/mnt/data", remote: "s3:my-bucket/datasets", backend: "s3", creds: { access_key_id: process.env.AWS_ACCESS_KEY_ID!, secret_access_key: process.env.AWS_SECRET_ACCESS_KEY!, region: "us-east-1", }, }); // Now anything in the sandbox can read s3://my-bucket/datasets as /mnt/data: await sandbox.exec.run("ls /mnt/data"); await sandbox.exec.run("head /mnt/data/train.csv");

How it works

Mounts use rclone mount under the hood — a single binary that speaks ~40 backends (S3, GCS, Azure Blob, SFTP, WebDAV, Dropbox, and more). When you call mounts.add():
  1. The control plane templates an rclone config from your backend + creds.
  2. The config is written to a tmpfs file inside the VM (mode 0600 — the sandbox user can't read it).
  3. rclone mount is spawned as a background daemon, exposing the remote at the path you specified.
  4. Every process in the sandbox can now read/write that path.
Credentials live only inside the VM's tmpfs for the lifetime of the mount. The worker keeps no copy.

Supported Backends

backend selects how creds are templated. The keys you pass map directly to rclone config fields for that backend type.
BackendCommon keys
s3access_key_id, secret_access_key, region, provider (default AWS), endpoint
gcsservice_account_credentials (JSON string) or service_account_file
azureblobaccount, key or sas_url
sftphost, user, pass or key_file, port
webdavurl, vendor, user, pass
dropboxtoken
For anything not in this list, or for advanced tuning, pass rcloneConfig directly with a raw rclone config string — see Custom Config below.

Examples

S3 (AWS)

typescript
await sandbox.mounts.add({ path: "/mnt/data", remote: "s3:my-bucket", backend: "s3", creds: { access_key_id: process.env.AWS_ACCESS_KEY_ID!, secret_access_key: process.env.AWS_SECRET_ACCESS_KEY!, region: "us-east-1", }, });

S3-compatible (MinIO, R2, Tigris, etc.)

Override the provider and point at the endpoint:
typescript
await sandbox.mounts.add({ path: "/mnt/r2", remote: "r2:my-bucket", backend: "s3", creds: { provider: "Cloudflare", access_key_id: "...", secret_access_key: "...", endpoint: "https://<accountid>.r2.cloudflarestorage.com", region: "auto", }, });

Google Cloud Storage

typescript
import fs from "node:fs"; await sandbox.mounts.add({ path: "/mnt/gcs", remote: "gcs:my-bucket", backend: "gcs", creds: { service_account_credentials: fs.readFileSync("./sa-key.json", "utf8"), }, });

SFTP

typescript
await sandbox.mounts.add({ path: "/mnt/remote", remote: "ssh:/home/data", backend: "sftp", creds: { host: "data.example.com", user: "agent", pass: process.env.SSH_PASSWORD!, // or key_file }, });

Custom Config

For backends not in the typed list — or to tune things like --vfs-cache-mode or --dir-cache-time — pass an rclone config string directly. The section name in the config must match the part of remote before the colon.
typescript
await sandbox.mounts.add({ path: "/mnt/box", remote: "box:Reports", rcloneConfig: ` [box] type = box token = {"access_token":"...","refresh_token":"...","expiry":"..."} `.trim(), mountOptions: ["--dir-cache-time", "1m"], });

Read-only by default

Mounts are read-only unless you explicitly opt into read-write:
typescript
await sandbox.mounts.add({ path: "/mnt/writable", remote: "s3:scratch", backend: "s3", creds: {...}, readOnly: false, // opts into RW; uses --vfs-cache-mode writes });
Object-store FUSE mounts have well-known write footguns — concurrent writers to the same key can produce surprising results, and small-write workloads amplify request counts (and your bill). Prefer object-store SDKs for write-heavy workloads; use mounts for read-heavy access and append-style artifacts.

Listing and removing

typescript
const mounts = await sandbox.mounts.list(); // [{ path: "/mnt/data", remote: "s3:my-bucket", backend: "s3", readOnly: true }] await sandbox.mounts.remove("/mnt/data");

Hibernate behavior

Mounts survive hibernate/wake transparently. The VM snapshot captures the live FUSE mount and the rclone daemon process; when the sandbox wakes, both are restored as-is. No re-mount call needed — the mount is exactly where you left it, with the same credentials, serving the same path.
typescript
await sandbox.mounts.add({ path: "/mnt/data", remote: "s3:my-bucket", backend: "s3", creds }); await sandbox.hibernate(); await sandbox.wake(); await sandbox.exec.run("ls /mnt/data"); // still works, no re-mount needed
When you want a mount gone, call remove() explicitly:
typescript
await sandbox.mounts.remove("/mnt/data");
That triggers an actual fusermount3 -u in the VM and drops the registry entry. No magic teardown on hibernate.

Stale credentials

If the credentials behind a mount get rotated or revoked while the sandbox is hibernated, the in-VM rclone daemon — restored intact from the snapshot — is still holding the old keys and will start hitting auth errors on the next request. Fix: mounts.remove(path) then mounts.add(...) again with fresh credentials.

CLI

bash
oc mounts add sb-abc123 \ --path /mnt/data \ --remote s3:my-bucket \ --backend s3 \ --cred access_key_id=AKIA... \ --cred secret_access_key=... \ --cred region=us-east-1 oc mounts list sb-abc123 oc mounts rm sb-abc123 /mnt/data
For raw rclone configs, point --config-file at a local file:
bash
oc mounts add sb-abc123 --path /mnt/box --remote box:Reports --config-file ./rclone.conf

Troubleshooting

sandbox image is missing rclone and/or fusermount3 — the sandbox is running on an older base image. Recreate the sandbox from the latest default template, or build an image off the current Dockerfile.default.
Mount succeeds but ls returns empty — usually a creds / permission issue on the remote side. SSH in (oc shell) and run rclone ls <remote>: --config /run/oc-agent/mounts/<id>.conf to see the real error. Or ls -la the mount point — FUSE errors surface as filesystem errors.
fuse: bad mount point ... permission denied — make sure the target path isn't already a non-empty directory you don't own. The mount call creates the path with sandbox:sandbox ownership; pre-existing roots can conflict.
CLI equivalent: oc mounts. Full reference: TypeScript SDK · Python SDK.

Bring your own FUSE

Advanced — most users want the rclone path above. Reach for this only if you already have a FUSE-ready filesystem.
Under the hood, mounts.add() picks a driver — the daemon that actually establishes the FUSE mount. Everything above uses the default rclone driver. If you already have a FUSE-ready filesystem (your own VFS, gcsfuse, s3fs, …) and don't want rclone as a middle layer, use the command driver: you hand us the daemon's argv and we run it as the sandbox user, inject your env/secrets, wait for the mount to come live, and tear it down on remove — the same lifecycle as an rclone mount.
typescript
await sandbox.mounts.add({ path: "/mnt/data", driver: "command", command: ["my-vfs-fuse", "--bucket", "gs://my-bucket", "{mountpoint}"], secrets: { MY_VFS_TOKEN: process.env.MY_VFS_TOKEN! }, });
In Python this is a separate method, mounts.add_command(...).

The {mountpoint} token

{mountpoint} (or {path}) in your argv is replaced with path before the command runs, so you don't repeat the path twice. The call above runs:
bash
my-vfs-fuse --bucket gs://my-bucket /mnt/data
You can also hardcode the path — the token is just a convenience.

env and secrets

  • env — plain environment variables for the daemon. Recorded and returned by list().
  • secrets — credentials. Injected into the daemon's process environment (never the command line, so they don't leak via ps), never written to disk on the worker, and never returned by list().
For daemons that need a credentials file (e.g. a service-account JSON), write it first with the filesystem API and point the command at it.
readOnly is advisory for this driver — your command must honor it (we also export OC_MOUNT_READONLY=1). Unlike rclone, the platform can't enforce read-only on an arbitrary daemon.

Requirements

Your daemon must:
  • be present in the sandbox — bake it into your image, or install/download it at runtime before mounting;
  • mount at the mountpoint and keep running (a foreground daemon is fine — we background it; a self-daemonizing one works too);
  • exit when its mount is unmounted (standard libfuse behavior) so remove cleans up.
The platform handles the rest: /dev/fuse access, creating the mountpoint, and -o allow_other support (user_allow_other is set in /etc/fuse.conf). If the mount doesn't come up within the timeout, the call fails with the daemon's own log tail so you can see why (a bad flag, missing binary, auth error, etc.).

Example: gcsfuse

gcsfuse is Google's own GCS-to-FUSE adapter. Install it in your image (or at runtime), write your service-account key with the filesystem API, and mount:
typescript
await sandbox.files.write("/run/gcp-sa.json", process.env.GCP_SA_JSON!); await sandbox.mounts.add({ path: "/mnt/bucket", driver: "command", command: [ "gcsfuse", "--foreground", "--implicit-dirs", "-o", "allow_other", "--key-file", "/run/gcp-sa.json", "my-bucket", "{mountpoint}", ], }); await sandbox.exec.run("ls /mnt/bucket"); // GCS objects, read like local files
For a public bucket, skip the key and pass --anonymous-access:
typescript
command: ["gcsfuse", "--foreground", "--anonymous-access", "-o", "allow_other", "gcp-public-data-landsat", "{mountpoint}"],

Example: bindfs

A minimal local FUSE — mirror one directory onto another path:
typescript
await sandbox.exec.run("apt-get update && apt-get install -y bindfs"); await sandbox.mounts.add({ path: "/mnt/mirror", driver: "command", command: ["bindfs", "-f", "-o", "allow_other", "/home/sandbox/data", "{mountpoint}"], readOnly: false, }); // reads/writes under /mnt/mirror now pass through to /home/sandbox/data

CLI

Pass --command once per argv element; --env/--secret take key=value:
bash
oc mounts add sb-abc123 \ --path /mnt/mirror \ --command bindfs --command -f --command -o --command allow_other \ --command /home/sandbox/data --command '{mountpoint}' \ --secret MY_VFS_TOKEN=...
If the mount doesn't come up, the error includes the daemon's log tail — usually a missing binary, a bad flag, or an auth failure. Reproduce by hand with oc shell and run the same command.