Guides
Agent files and shared volumes in camelRun
Attach files to messages, read what a camelRun agent wrote, share volumes between agents, and sign download and upload links.
View as Markdown · Docs index for coding agents
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. For a short tour of what an agent keeps,
see AI agent memory on camelRun.
Attach files to a message
const run = await agent.run("What changed in Q3?", {
files: ["./q3.pdf", screenshotBytes, new File([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
(from @camelai/run/node in TypeScript, or a str or Path in
Python), or {path} for a file already in the agent's mounts. The SDKs upload
each file first, to uploads/<run id>/<name>, 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 sendPOST /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 | The newest 100 images, up to 20 MiB as sent, and 24 MiB of PDFs shown. Older ones 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), so a conversation with many images stays within every provider's limits. One that can't be scaled down is named, with why, instead.
Audio is transcribed when it is attached, and the model reads the transcript (see Voice and audio). Anything else is only named, and the model reads it with its file tools. The history 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 such as
/workspace/notes.md. read shows an image or PDF to a model that can view
it. camelRun 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. Pass fileTools: false (in a definition, or when
the agent is made) to leave the tools out.
Code in js_exec has fs over the same mounts:
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 in run.files ([{path, version, size, contentType}],
up to 100). The files the model handed over with present_file (with a
caption, up to 20) are in run.raw.presented, and each is also a
file_presented event as soon as it is presented, with a signed download url.
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 });
await agent.files.upload("/workspace/in/config.json", JSON.stringify(config));Tools from a definition's sources take the agent's files as {"$file": path},
and their file outputs are saved to the workspace. See
Tools.
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:
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?}, at most 16 per agent.notifyprompts the agent (about a second after, coalesced) when others change files under the mount. Only your own volumes can be mounted. An agent's mounts are set when it is made: an upsert with other mounts is a 409, unless it sendsremount: true, which sets them between its turns. PUT /v1/agents/{id}/mountsreplaces them during a turn too. The change applies 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.- The agent's own workspace stays at
/workspace, after the mounts you give.{workspace: true}among them puts it at that position,{workspace: false}leaves it out, and a mount you give at/workspacetakes its place. The first mount is where relative paths resolve.{workspace: true, path: "/scratch"}mounts the workspace elsewhere, for example beside a project volume:[{volumeId: project, path: "/workspace", mode: "rw"}, {workspace: true, path: "/scratch"}]. - Attachments are saved under
uploads/<request>/in the agent's own workspace, tool file outputs undertool-outputs/, and the model keeps scratch files intmp/. Without the workspace, they go to a read-write/workspace, else the first read-write mount; an agent with none can't take attachments or tool files. - An agent's own workspace is deleted with the agent. Volumes it only mounted stay.
createVolume({ name }, { idempotencyKey })(Pythoncreate_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 (0means it must not exist yet). Volumes also haveread,list,removeandchanges(since, { prefix? }), andruntime.volumes(ids)reads up to 50 volumes'seq(and files and bytes) in one request, to see which changed.volume.readAll({ prefix?, glob?, snapshot? })(Pythonread_all) reads every matching file with its contents in one request, as the volume was at oneseq:{seq, files: [{path, size, version, contentType, sha256, text | data}]}. Use it to check or publish what an agent wrote. At most 1,000 files and 16 MiB a read. REST:GET /v1/volumes/{id}/files?content=true&prefix=….volume.archive({ snapshot?, path?, glob? })(GET /v1/volumes/{id}/archive) streams the files as a tar.gz, at most 10,000 files and 1 GiB.- A volume suits up to about 100,000 files. Uploads may take 15 minutes.
For an agent that builds something your application publishes (a bot, a site, a report), a volume per project plus a publish step built on snapshots is the pattern: see Projects.
Snapshots
volume.snapshot()andvolume.fork({ snapshot })copy metadata only: a fork shares its source's content and diverges independently.read,listandreadAlltake{ snapshot }to read a snapshot as it was.volume.restore(snapshot)(POST /v1/volumes/{id}/restore {snapshot}) makes the volume as the snapshot was, in place, in one write that agents mounting it see. It answers{snapshot, seq, written, removed}, and the snapshot stays.- A volume keeps 100 snapshots. A pinned one (
snapshot({ pinned: true }), orupdateSnapshot(id, { pinned })) is kept until unpinned and counts against 10,000 pinned snapshots instead. Deleting it takes unpinning first, ordeleteSnapshot(id, { force: true }). - Snapshots take
labels, your own string map (16 at most), andsnapshots({ labels })(?label=key:value) lists those that have them. volume.snapshot({ files })makes a snapshot of the files you give (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. Its answer has each file'ssha256.- Deleting a volume deletes all its snapshots, pinned ones too.
Deleted files, volumes and snapshots stop being stored and billed about a day after nothing refers to them. A file an agent was sent or shown stays until the agent is deleted, since its history refers to it. Storage is billed per GB-month: see Pricing.
Signed links
agent.files.link(path, options) or volume.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 or
another service moves the bytes directly. Links last 15 minutes by default and
at most 24 hours, and can't be revoked sooner. Downloads take Range.
From your server, POST /v1/agents/{id}/links {path, method?, expiresIn?, maxBytes?, contentType?}
signs a link by the path the agent sees (/workspace/report.pdf), and
POST /v1/volumes/{id}/links by a volume's own path. Both answer
{url, method, path, expiresAt}.