# Filesystem

Each VM has its own filesystem that the agent works in. Guest `fs` calls never touch the host disk, and it persists automatically across sleep/wake with no setup. See [Persistence](/agentos/docs/persistence) for the details.

## Mounts

Back a guest path with external storage by adding it to the `mounts` config. Each mount takes a `path` and an optional `readOnly` flag, and the guest only ever sees the mounted subtree, never the wider host.

### Host directory

Project a real host directory into the filesystem, Docker-style. The guest sees only the mounted subtree, never the wider host filesystem. Path-escape attempts (symlinks, `..`, path aliasing) are confined to the mount root.

examples/filesystem/mount-host-dir.ts:

```ts
import { agentOS, setup } from "@rivet-dev/agentos";
import pi from "@agentos-software/pi";

const vm = agentOS({
  software: [pi],
  mounts: [
    {
      path: "/mnt/code",
      plugin: { id: "host_dir", config: { hostPath: "/path/to/repo" } },
      readOnly: true,
    },
  ],
});

export const registry = setup({ use: { vm } });
registry.start();
```

### S3

Mount an S3 bucket with the built-in `s3` plugin. Pass an optional `prefix` to scope storage to a key path within the bucket, useful for sharing one bucket across multiple agents.

The backend is a block store, not a one-object-per-file mapping: file contents are split into fixed-size chunks (4 MB by default) stored as individual S3 objects, with a separate metadata layer mapping each file to its chunks. This keeps large files, partial reads and writes, and snapshots efficient without rewriting whole objects.

examples/filesystem/mount-s3.ts:

```ts
import { agentOS, setup } from "@rivet-dev/agentos";
import pi from "@agentos-software/pi";

const vm = agentOS({
  software: [pi],
  mounts: [
    {
      path: "/mnt/data",
      plugin: {
        id: "s3",
        config: {
          bucket: "my-bucket",
          prefix: "agent-data/",
          region: "us-east-1",
        },
      },
    },
  ],
});

export const registry = setup({ use: { vm } });
registry.start();
```

The `s3` plugin config also accepts `credentials` (`{ accessKeyId, secretAccessKey }`) and a custom `endpoint` for S3-compatible providers.

### Google Drive

Mount a Google Drive folder with the built-in `google_drive` plugin.

examples/filesystem/mount-google-drive.ts:

```ts
import { agentOS, setup } from "@rivet-dev/agentos";
import pi from "@agentos-software/pi";

const vm = agentOS({
  software: [pi],
  mounts: [
    {
      path: "/mnt/drive",
      plugin: {
        id: "google_drive",
        config: {
          credentials: {
            clientEmail: process.env.GOOGLE_DRIVE_CLIENT_EMAIL!,
            privateKey: process.env.GOOGLE_DRIVE_PRIVATE_KEY!,
          },
          folderId: process.env.GOOGLE_DRIVE_FOLDER_ID!,
        },
      },
    },
  ],
});

export const registry = setup({ use: { vm } });
registry.start();
```

### In-memory

Use the built-in `memory` plugin for an ephemeral mounted directory in the RivetKit `agentOS()` actor.

examples/filesystem/server.ts:

```ts
import { agentOS, setup } from "@rivet-dev/agentos";
import pi from "@agentos-software/pi";

const vm = agentOS({
  software: [pi],
  mounts: [
    {
      path: "/home/agentos/scratch",
      plugin: { id: "memory", config: {} },
    },
  ],
});

export const registry = setup({ use: { vm } });
registry.start();
```

### Dynamic mount

Use `mountFs()` with a serializable, sidecar-owned plugin descriptor. The same descriptor works through Core and RivetKit; the actor persists dynamic descriptors in SQLite and replays them on wake. `mountFs()` resolves only once the mount is visible to guest code, and `unmountFs(path)` removes it.

examples/filesystem/mount-custom-vfs.ts:

```ts
import { AgentOs } from "@rivet-dev/agentos";

const vm = await AgentOs.create({ defaultSoftware: false });
await vm.filesystem.mount({
	path: "/home/agentos/scratch",
	plugin: { id: "memory", config: {} },
});
await vm.filesystem.writeFile("/home/agentos/scratch/hello.txt", "hello");
```

The actor's durable root is handled separately: the sidecar connects directly to Rivet's actor SQLite UDS, so root filesystem reads and writes never pass through JavaScript. `listMounts()` returns live sanitized metadata without plugin configuration.

## File operations

These operations are primarily what the agent uses inside the VM, and are also available from the client to seed inputs and read results. For large or read-only inputs (a repo, a dataset), a read-only [host mount](#mounts) is faster than copying files in. Programs that need stdin or live output use exec instead (see the [Direct VM API](/agentos/docs/core)).

### Read and write

examples/filesystem/operations.ts:

```ts
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";

const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });
const agent = client.vm.getOrCreate("my-agent");

// Write a file (string or Uint8Array)
await agent.filesystem.writeFile("/home/agentos/hello.txt", "Hello, world!");

// Read a file (returns Uint8Array)
const content = await agent.filesystem.readFile("/home/agentos/hello.txt");
console.log(new TextDecoder().decode(content));
```

### Batch read and write

examples/filesystem/operations.ts:

```ts
// Batch write (creates parent directories automatically)
const writeResults = await agent.filesystem.writeFiles([
  { path: "/home/agentos/src/index.ts", content: "console.log('hello');" },
  { path: "/home/agentos/src/utils.ts", content: "export function add(a: number, b: number) { return a + b; }" },
]);

// Batch read
const readResults = await agent.filesystem.readFiles([
  "/home/agentos/src/index.ts",
  "/home/agentos/src/utils.ts",
]);
for (const result of readResults) {
  console.log(result.path, new TextDecoder().decode(result.content ?? new Uint8Array()));
}
```

### Directories

examples/filesystem/operations.ts:

```ts
// Create a directory
await agent.filesystem.mkdir("/home/agentos/projects");

// List directory contents
const entries = await agent.filesystem.readdir("/home/agentos/projects");

// Recursive listing (entries carry path, type, and size)
const tree = await agent.filesystem.readdirRecursive("/home/agentos");
for (const entry of tree) {
  const name = entry.path.split("/").pop() ?? entry.path;
  console.log(entry.type, entry.path, name);
}
```

### File metadata

examples/filesystem/operations.ts:

```ts
// Check if a path exists
const fileExists = await agent.filesystem.exists("/home/agentos/hello.txt");

// Get file metadata
const info = await agent.filesystem.stat("/home/agentos/hello.txt");
console.log(info.size, info.isDirectory, info.mtimeMs);
```

### Move and delete

examples/filesystem/operations.ts:

```ts
// Move/rename
await agent.filesystem.move("/home/agentos/old.txt", "/home/agentos/new.txt");

// Delete a file
await agent.filesystem.remove("/home/agentos/new.txt");

// Delete a directory recursively
await agent.filesystem.remove("/home/agentos/temp", { recursive: true });
```

## Permissions

Filesystem access is governed by the VM permission policy. The filesystem scope is granted by default; restrict it by path, for example to deny a sensitive directory:

```ts
const vm = agentOS({
  permissions: {
    fs: {
      default: "allow",
      rules: [{ mode: "deny", operations: ["*"], paths: ["/home/agentos/secrets/**"] }],
    },
  },
});
```

See [Permissions](/agentos/docs/permissions) for the full configuration.

## Sandboxes

For heavier workloads, run a full Linux [external sandbox](/agentos/docs/sandboxes) alongside the VM and mount its filesystem into agentOS. The agent then reads and writes the sandbox's files through the same `fs` APIs while the sandbox handles execution.

## Default layout

With no `mounts` configured, every VM boots an Alpine-based root filesystem with the standard POSIX directories:

- `/home/agentos`: the agent's home directory (`$HOME`) and default working directory (`pwd`) when spawned, where it reads and writes (mounts land under it, e.g. `/home/agentos/data`).
- `/bin`, `/sbin`, `/usr`: installed commands (common POSIX utilities by default, plus any [software](/agentos/docs/software) you add).
- `/etc`, `/lib`, `/opt`, `/root`, `/run`, `/srv`, `/tmp`, `/var`, `/mnt`: standard system paths.

It is backed by the VM's own filesystem and persisted across sleep/wake. Nothing comes from or touches the host disk.
