# Files

Every agent has files: its own workspace at `/workspace` by default, or the
**volumes** you mount. Files you attach to a message land there, the model works
on them with its file tools and with code, tools save their outputs there, and
you read what the agent made by path.

## Attaching files to a message

```ts
const run = await agent.run("What changed in Q3?", {
  files: ["./q3.pdf", screenshotBytes, new File([csv], "data.csv"), { path: "/workspace/notes.md" }],
});
```

```python
run = await agent.run("What changed in Q3?", files=[Path("q3.pdf"), screenshot_bytes, {"name": "data.csv", "data": csv}, {"path": "/workspace/notes.md"}])
```

A file is bytes, a `Blob` or `File`, `{name, data, contentType?}` (Python
`{"name", "data", "content_type"?}`), a local path
(the Node entry `@camelai/run/node`, or a `str`/`Path` in Python), or
`{path}` for a file already in the agent's mounts. The SDKs upload each first,
streamed, to `uploads/<run id>/<name>` in the agent's workspace, then send the
message referring to them by path, so a retried run never uploads twice.

- At most 20 files per message, each at most 256 MiB.
- Over REST, upload with `PUT /v1/agents/:id/uploads/:requestId/:name`, then
  `POST /v1/agents/:id/prompt {text, requestId, files: [{path}]}`. The prompt also
  takes small files inline, `{name, data: <base64>, contentType?}`, up to 4 MiB in
  all.

## What the model sees

The message carries a line per file, such as
`[File /workspace/uploads/r1/q3.pdf (application/pdf, 2.1 MB)]`, and the file
itself where the model can take it:

| | Shown natively | Limits |
| --- | --- | --- |
| Images (PNG, JPEG, GIF, WebP) | models with image input | 5 MiB and 8,000 px a side each |
| PDFs | Anthropic, Google, OpenAI and OpenRouter models with image input | 16 MiB and 100 pages each |
| Per model request | | 24 MiB and 100 files shown; older files past that are named only |

An image goes to the model at most 1,568 px a side: a larger one is scaled down
in the request (keeping its shape; the file itself is unchanged), as Anthropic
would scale it anyway, so a conversation with many images stays within every
provider's limits. One that cannot be scaled down is named with why instead.
Inline images (a message's or a tool result's base64) are scaled down as they
enter the transcript.

Audio is transcribed when it is attached, and the model reads the transcript
(see [Voice and audio](voice.md)). Anything else is only named, and the model reads it with its file tools. The
transcript keeps a reference to each file's content, never its bytes, so a file
changed or deleted later still reads as it was when attached.

## The agent's file tools

`read`, `write`, `edit`, `ls`, `glob` and `grep` work on mount paths
(`/workspace/notes.md`). `read` shows an image or PDF to a model that can view
it. The runtime remembers which version of each file the agent last read or wrote,
so a `write` or `edit` of a file someone else changed since then fails ("changed
since you last read it; read it again") and the model reads again. The model
never sees version numbers; the files API for applications keeps them. An application whose own tools
handle files can leave these out with `fileTools: false` (in a definition, or when
the agent is made).

Code in `js_exec` has `fs` over the same mounts:

```js
const csv = await fs.readFile("/workspace/data.csv", { encoding: "utf8" });
await fs.writeFile("/workspace/out/chart.png", png, { contentType: "image/png" });
await fs.stat("/workspace/out/chart.png");  // {path, type, size, updatedAt, contentType}
await fs.list("/workspace/out");
```

One `fs` call moves at most about 700 KiB; read larger files in windows with
`tools.read({ path, offset, encoding: "base64" })`.

## Files out

A run lists what it wrote: `run.files` (`[{path, version, size, contentType}]`,
every file written, up to 100) and, in `run.raw.presented`, the files the model
handed over with `present_file` (with a caption). Each presented file is also a
`file_presented` event as soon as it is presented, with a signed download `url`.

Read them with the agent's own access:

```ts
const { data, contentType } = await agent.files.download("/workspace/out/chart.png");
const listing = await agent.files.list({ path: "/workspace/out" });
const link = await agent.files.link("/workspace/out/report.pdf", { expiresIn: 3600 }); // for a browser or another service
await agent.files.upload("/workspace/in/config.json", JSON.stringify(config));
```
Python has the same: `agent.files.download(path)` (`.data`, `.content_type`, `.version`), `list(path=...)`,
`link(path, expires_in=...)`, `upload(path, data)`.

Tools of a definition's sources take the agent's files as `{"$file": path}`,
and their file outputs are saved to the workspace: see
[Files in tool calls](tools.md#files-in-tool-calls).

## Volumes

A volume is a shared file tree. Without `mounts`, each agent gets its own
workspace volume at `/workspace`; mount the same volume in several agents to
share files, read-only or read-write:

```ts
const docs = await agents.runtime.createVolume({ name: "shared docs" });
await agents.runtime.volume(docs.id).write("handbook.md", handbook);
const agent = await agents.upsert("support", { mounts: [{ volumeId: docs.id, path: "/docs", mode: "ro" }], … });
```

- Mounts are `{volumeId, path, mode: "ro" | "rw", subpath?, notify?}`. `notify`
  prompts the agent (about a second after, coalesced) when others change files
  under the mount. Only your own volumes can be mounted, and an agent's mounts
  are fixed when it is made: an upsert with other mounts is a 409, unless it
  sends `remount: true`, which sets them between its turns.
- `PUT /v1/agents/:id/mounts` replaces them during a turn too. Mount changes
  apply at the agent's next tool call (a call under way keeps the mounts it
  began with): a removed volume is refused, a read-only one is read-only, and
  one added or swapped in is there at its path, so a tool can swap `/bot` for
  another volume and the model goes on working in it. Tool outputs go to the
  workspace as it is then. The running turn's environment text updates at the
  next run.
- The agent's own workspace stays at `/workspace` beside the mounts you give
  (after them). `{workspace: true}` among them puts it at that position,
  `{workspace: false}` leaves it out, and a mount you give at `/workspace` takes
  its place. The first mount is where relative paths resolve, e.g.
  `[{volumeId, path: "/bot", mode: "rw"}]` has `/bot` first and `/workspace`
  second; `[{workspace: false}]` is no mounts at all.
- `{workspace: true, path: "/scratch"}` mounts the workspace elsewhere, e.g.
  beside a project volume at `/workspace`:
  `[{volumeId: project, path: "/workspace", mode: "rw"}, {workspace: true, path: "/scratch"}]`.
  The path is absolute, written normalized, and clear of the other mounts.
- Attachments are saved under `uploads/<request>/` in the agent's own
  workspace, files tools return under `tool-outputs/`, and the model keeps
  scratch files in `tmp/`, wherever the workspace is mounted (`/workspace/...`
  by default, `/scratch/...` above). Without it, they go to a read-write
  `/workspace`, else the first read-write mount (and an agent with none cannot
  take attachments or tool files).
- An agent's own workspace is deleted with the agent, whether or not it is
  still mounted; volumes it only mounted stay.
- `createVolume({ name }, { idempotencyKey })` (Python
  `create_volume(name=…, idempotency_key=…)`) makes a volume once per key, for a
  day: a retry, or another process, gets the same volume.
- `volume.write(path, data, { version })` writes only if nobody changed the file
  since that version (`0`: it must not exist), `read`, `list`, `remove`,
  `changes(since, { prefix? })`. `runtime.volumes(ids)` reads up to 50 volumes'
  `seq` (and files and bytes) in one request, to see which changed.
- `volume.snapshot()` and `volume.fork({ snapshot })` copy metadata only: a fork
  shares its source's content and diverges independently. `read`, `list` and
  `readAll` take `{ snapshot }` to read a snapshot as it was.
- A snapshot can be pinned (`snapshot({ pinned: true })`, or
  `updateSnapshot(id, { pinned })`, `PATCH /v1/volumes/:id/snapshots/:id`): it is
  kept until unpinned, counts against 10,000 pinned snapshots rather than the
  100 others, and deleting it takes unpinning first or `deleteSnapshot(id,
  { force: true })`. Snapshots take `labels`, your own string map (16 at most),
  and `snapshots({ labels })` (`?label=key:value`) lists those that have them.
  Deleting a volume deletes all its snapshots, pinned ones too.
- `volume.snapshot({ files })` makes a snapshot of the files given (path to
  text or bytes; 1,000 files and 16 MiB at most) instead of the volume, which it
  leaves untouched: for bringing in versions kept elsewhere. It is made whole or
  not at all, and its answer has each file's `sha256` (`contents`).
- `volume.restore(snapshot)` (`POST /v1/volumes/:id/restore {snapshot}`) makes
  the volume as a snapshot was, in place: files the snapshot lacks are removed
  and files that differ are written back, in one write, each a change that
  agents mounting it see. It answers `{snapshot, seq, written, removed}`; the
  snapshot stays.
- `volume.archive({ snapshot?, path?, glob? })` (`GET /v1/volumes/:id/archive`)
  streams the files as a tar.gz, as the volume or a snapshot has them at one
  `seq` (`X-Volume-Seq`), named relative to `path`: for a build that wants the
  whole tree. At most 10,000 files and 1 GiB (else 413).
- `volume.readAll({ prefix?, glob?, snapshot? })` (Python `read_all`) reads every
  matching file with its contents in one request, as the volume was at one `seq`:
  `{seq, files: [{path, size, version, contentType, sha256, text | data}]}`
  (`data` is base64, for files that are not UTF-8 text). Use it to check or
  publish what an agent wrote: nothing written meanwhile shows in it. At most
  1,000 files and 16 MiB a read (a 413 says to narrow `prefix` or `glob`). REST:
  `GET /v1/volumes/:id/files?content=true&prefix=…&snapshot=…`.
- Every file has a content type: the upload's, else sniffed from its bytes and
  name. Downloads are served so a file can never run as the runtime's origin.
- Uploads may take 15 minutes; a volume suits up to about 100,000 files.
- Deleted files, volumes and snapshots stop being stored and billed about a day
  after nothing refers to them any more. A file an agent was sent or shown stays,
  as the agent's history refers to it, until the agent is deleted.

## Signed links

`volume.link(path, options)` or `agent.files.link(path, options)` returns `{url,
expiresAt}`: a URL that downloads (`GET`) or uploads (`PUT`, with `maxBytes` and
`contentType`) that one file without a token, so a browser, a tool server or a
channel moves the bytes directly. Links last 15 minutes by default, at most 24
hours, and cannot be revoked sooner. Downloads take `Range`.

From your server, with your API key, `POST /v1/agents/:id/links {path, method?,
expiresIn?, maxBytes?, contentType?}` signs a link to a file of an agent by the
path the agent sees (`/workspace/report.pdf`), without the agent's token; `POST
/v1/volumes/:id/links` does the same by a volume's own path. Both answer `{url,
method, path, expiresAt}`, and a retry with the same `Idempotency-Key` never
returns the link again.
