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

# Storage and Files

> VirtualFS-backed filesystem tools, shared storage mounts, and run artifact handling.

## Storage Model

Orca has two storage surfaces:

* **VirtualFS-backed filesystem tools** (`read_file`, `write_file`, `list_dir`, `delete_file`) expose scoped paths under conventional directories such as `/agents`, `/pools`, `/s3`, `/data`, and `/kb`.
* **Artifact tools** (`save_artifact`, `get_artifact`, `list_artifacts`) expose raw run/session-keyed S3-compatible objects.
* **Memory Bank** persists per-profile long-lived memories under `memory/<profile>/<id>.json` when S3-compatible storage is configured. See [Memory Bank](/concepts/memory-bank).

Local Docker uses SeaweedFS. Production can use any S3-compatible backend, including AWS S3, R2, or MinIO.

***

## Environment Contract

VirtualFS is configured from explicit `VFS_*` variables, or from production defaults when `VFS_BACKEND=production` is set:

| Variable                 | Description                                                                                                                                             |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `VFS_POSTGRES_DSN`       | Optional Postgres index cache; falls back to in-memory                                                                                                  |
| `VFS_REDIS_URL`          | Optional Redis byte cache; falls back to disabled                                                                                                       |
| `VFS_S3_BUCKET`          | S3/R2 bucket for root mount contents (prod profile). When unset, the root mount comes up `unavailable`; use `--profile dev` for an all-RAM local setup. |
| `VFS_S3_ENDPOINT`        | Optional S3-compatible endpoint                                                                                                                         |
| `VFS_BACKEND=production` | Also consults generic Postgres, Redis, S3, and R2 environment variables                                                                                 |

### Mount strictness

A mount is strictly identified by its path AND its backing data source (kind + bucket + endpoint + prefix). The prod, dev, and disk profiles each register one system mount named `root` at `/`; `/agents`, `/pools`, `/s3`, `/data`, and `/kb` are directories inside that mount, not separate boot-time mounts. Runtime user mounts can still register non-root prefixes, and the dispatcher resolves paths with longest-prefix-wins. Two important consequences:

* The server never substitutes a different backend for a missing one. If the prod profile is configured for R2 and R2 is unreachable at boot, the root mount comes up `unavailable` — not silently backed by RAM.
* The client never assumes a mount is what it declares; it checks. Use `Workspace.validate()` to fail fast on bucket drift or kind mismatch.

Mounts that fail to initialize at boot do not crash the server. The server boots; their entries surface in `GET /vfs/mounts` with `status: "unavailable"` and a human-readable `reason`. Operations on those mounts return `MOUNT_UNAVAILABLE`.

### Single coherent filesystem

The VFS presents path operations as one tree:

* Addressable: every byte lives at exactly one absolute path
  (`/agents/...`, `/data/...`, `/kb/...`), regardless of whether the selected mount is RAM, R2, or
  another backend.
* Navigable: `ls /` returns top-level directories visible from the active mount table; `cd /` works; `stat /` reports a directory.
* Searchable: `find /` and `grep -r /` traverse the visible tree and apply the active session allowlist before returning entries or matches.
* Mutable across subtrees or mounts: `cp /agents/x /data/y` and `mv /a/x /b/y`
  stream bytes through the dispatcher.

Strict identity (above) and unification (here) are independent
guarantees. Strict means each mount honestly reports its data source;
unification means callers don't have to enumerate mounts to walk the
tree. Together they let an agent treat the substrate like a real
filesystem without hiding what's actually under each subtree.

With no `VFS_*` environment, the runner still boots with an in-memory VirtualFS dispatcher. That keeps `@fs`, `@pool`, and `@vfs` usable for a single runner process, but state is not shared with a separate `vfs serve` process. Configure both processes with the same VirtualFS backend settings when dashboard Files, `vfs serve`, and agent tools should see the same data.

The [dashboard Files page](/dashboard/files) is a VFS client too. From any writable directory, operators can upload files or folders with the Upload menu or by dragging them into the listing. Folder picks and dropped directories preserve relative paths, while the upload queue shows per-file progress, retry, cancel-all, and clear controls.

Raw artifacts and Memory Bank persistence are configured from the standard AWS SDK environment plus Orca-specific bucket flags:

| Variable                | Description                                        |
| ----------------------- | -------------------------------------------------- |
| `S3_BUCKET`             | Required bucket name                               |
| `AWS_REGION`            | Region; defaults to `us-east-1`                    |
| `AWS_ENDPOINT_URL_S3`   | S3-compatible endpoint for SeaweedFS, MinIO, or R2 |
| `AWS_ACCESS_KEY_ID`     | Access key                                         |
| `AWS_SECRET_ACCESS_KEY` | Secret key                                         |
| `S3_FORCE_PATH_STYLE`   | Set `true` for SeaweedFS and MinIO                 |
| `S3_CAPACITY_BYTES`     | Optional dashboard capacity denominator            |

Run-event blobs can use a separate internal bucket. This keeps operational run transcripts out of the tenant-facing artifact bucket used by artifact tools, Memory Bank, and the dashboard storage API:

| Variable                         | Description                                                                 |
| -------------------------------- | --------------------------------------------------------------------------- |
| `INTERNAL_S3_BUCKET`             | Optional bucket for `runs/<runId>/events.jsonl` and `runs/<runId>/finished` |
| `INTERNAL_AWS_REGION`            | Region; defaults to `us-east-1`                                             |
| `INTERNAL_AWS_ENDPOINT_URL_S3`   | S3-compatible endpoint                                                      |
| `INTERNAL_AWS_ACCESS_KEY_ID`     | Access key                                                                  |
| `INTERNAL_AWS_SECRET_ACCESS_KEY` | Secret key                                                                  |
| `INTERNAL_S3_FORCE_PATH_STYLE`   | Set `true` for SeaweedFS and MinIO                                          |

When raw artifact storage is not configured, artifact calls return a "not configured" tool error and the dashboard storage API reports `configured: false`. When `S3_BUCKET` is configured for the conductor, startup probes the bucket with a small list request before wiring artifact storage. Missing buckets, wrong endpoints, or invalid credentials fail startup with `artifacts.bucket_unavailable` instead of silently dropping run-event writes. When `INTERNAL_S3_BUCKET` is configured, the conductor probes that bucket too and uses it for run-event blobs. If the internal bucket is omitted, run events fall back to the primary `S3_BUCKET`, so single-bucket local and CI setups need no extra configuration.

***

## Filesystem View

Filesystem tools use absolute POSIX paths and dispatch through VirtualFS. With the default S3/R2-backed root mount, user-facing paths stay stable, while the dispatcher scopes bucket keys under the authenticated tenant's jail prefix (`tenants/{tenant}/...`). Entries returned by `ls`, `find`, `grep`, `stat`, and writes are decoded back to tenant-facing POSIX paths before callers see them.

| Path                                      | Auth tenant | Bucket key                                            |
| ----------------------------------------- | ----------- | ----------------------------------------------------- |
| `/agents/general/notes.md`                | `acme`      | `tenants/acme/agents/general/notes.md`                |
| `/pools/research/board/public/posts/x.md` | `acme`      | `tenants/acme/pools/research/board/public/posts/x.md` |

Every profile has implicit read/write/delete access to `/agents/{self}/**`.

```json theme={null}
{
  "fs": {
    "read": ["/datasets/**"],
    "write": ["/shared/{self}/**"],
    "deny": ["/datasets/private/**"]
  }
}
```

`delete` defaults to `write` when omitted. `deny` overrides all grants.

***

## File Tools

`@fs` is part of `@default`.

| Tool          | Description                                            |
| ------------- | ------------------------------------------------------ |
| `read_file`   | Reads text or base64 content from an allowed path      |
| `write_file`  | Writes text or base64 content to an allowed path       |
| `list_dir`    | Lists immediate children visible to the session policy |
| `delete_file` | Deletes an allowed file                                |

Relative paths are resolved under the agent home. For example, `write_file("notes.md")` writes `/agents/{self}/notes.md`.

When a VirtualFS dispatcher is wired, `@fs`, `@pool`, `@sandbox-transfer`, and `@vfs` share the same backing substrate. A file written with `write_file` under `/agents/{self}` is visible to VirtualFS shell commands such as `vfs_execute` running inside a [sandbox](/concepts/sandboxes).

***

## Artifact Tools

`@artifacts` is opt-in. These tools use run/session-keyed object paths:

```text theme={null}
runs/<runId>/sessions/<sessionId>/<path>
```

When there is no run ID, objects are written under `shared/sessions/<sessionId>/...`.

| Tool             | Description                                                |
| ---------------- | ---------------------------------------------------------- |
| `save_artifact`  | Saves text or base64 content under the current run/session |
| `get_artifact`   | Reads by relative `path` or absolute `key`                 |
| `list_artifacts` | Lists by prefix; defaults to the current run prefix        |

Inline tool payloads are capped at 8 MiB.

***

## Dashboard Storage API

The conductor exposes storage endpoints used by the dashboard's storage browser:

| Method   | Path                              | Description                                                                                 |
| -------- | --------------------------------- | ------------------------------------------------------------------------------------------- |
| `GET`    | `/api/storage/info`               | Bucket usage and top-level prefix breakdown                                                 |
| `GET`    | `/api/storage/objects?prefix=...` | List objects by key prefix                                                                  |
| `GET`    | `/api/storage/objects/{key}`      | Preview object content inline; `key` may contain multiple path segments                     |
| `PUT`    | `/api/storage/objects/{key}`      | Upload an object (raw body, capped at 8 MiB); `key` may contain multiple path segments      |
| `DELETE` | `/api/storage/objects/{key}`      | Delete one object — or every object under a prefix when the multi-segment key ends with `/` |

Non-UTF-8 object content is base64 encoded in `GET` responses. `DELETE` against a folder prefix walks up to 1000 entries (one List page) and removes them serially.

***

## Local SeaweedFS

The compose stack starts SeaweedFS and creates the default bucket:

```bash theme={null}
make storage-up
make storage-smoke
```

The default local endpoint is `http://localhost:8333`, bucket `agent-artifacts`, access key `agent-orc`, and secret `agent-orc-secret`.
