Thread filesystem
Each conversation with an advanced agent has its own filesystem. The code that
the agent writes reads and writes files there with the urai:fs library. The
files stay after the script ends and after the turn ends, so a later script in
the same conversation opens them again.
import { fs } from "urai:fs";
const rows = await fs.readJSON("/uploads/sales.json");await fs.writeText("/out/q3-revenue.svg", renderChart(rows));await urai.complete({ chart: "/out/q3-revenue.svg" });The script returns the path, not the SVG. The data stays in the filesystem and does not go through the model. This keeps the context small, and it removes the token cost of large results.
Use the filesystem for these tasks:
- Process a file that the user attached, such as a CSV export.
- Make a report, a chart, or a data file that the user downloads.
- Keep an intermediate result, such as downloaded pages, for the next step.
- Build a data set across several scripts, one part at a time.
Where the filesystem is available
Section titled “Where the filesystem is available”The filesystem belongs to a conversation thread. The workspace id is the thread id.
| Surface | Filesystem |
|---|---|
| An advanced agent in the Urai interface | Yes |
| An advanced agent in the chat widget | Yes. The widget shows the files on each reply. |
| An advanced agent over the Agents API | Yes. The files stay for the conversation, but the API does not return them. |
| A simple agent | No |
| The Chat Completions API | No |
| A tool that the Chat Completions API calls | No |
| A workflow step | No |
In an advanced agent, only the scripts that the agent runs with execute get
the filesystem. A library that such a script imports runs in the same execution,
so the library can also use urai:fs. The same library called in a place that
has no filesystem sees fs.available() return false.
There is no setting on the agent. Every advanced agent gets the filesystem when the Urai deployment has it turned on.
Folders
Section titled “Folders”Urai does not force a layout. The agent’s instructions tell it to use these folders, and the Urai interface expects them:
| Folder | Contents |
|---|---|
/uploads/ |
The files that the user attached. Urai puts them here. |
/out/ |
The files that the agent makes for the user. |
/work/ |
Intermediate files, such as downloaded pages and partial results. |
/canvas/<name>/ |
The files of a canvas. See Canvases. |
Give files names that a person understands later, for example
/out/q3-revenue.svg. Do not use names such as /tmp1.
Attachments
Section titled “Attachments”When the user attaches a file to a message, Urai copies the file into
/uploads/ before the agent starts. The agent gets a note with the path and the
content type:
[Attached, and saved to this thread's filesystem: /uploads/sales.csv (text/csv).Open any of them with `urai:fs` to work with the contents in code, rather thanasking for them again.]Urai sends images and PDF files to the model as content too. For all other files, the model gets only the path, and a script reads the file.
Urai makes the file name safe before it writes the file:
- It keeps only the last part of the name, with no folders.
- It replaces control characters with
_. - It removes dots at the start and at the end of the name.
- It keeps at most 120 characters.
If a different file with the same name is already in /uploads/, Urai adds a
number, for example sales-2.csv. If the file has the same name and the same
size, Urai uses the file that is already there.
Urai copies only the attachments of the newest message. If the copy fails, Urai sends the file to the model in the message, as it does for a simple agent.
What the agent sees
Section titled “What the agent sees”After each execute, the agent gets a list of the files in the thread, with
the path and the size of each file. The list comes with the result, or with the
error when the script failed. The list shows the 50 files that changed most
recently. When there are more files, a note tells the agent to use fs.glob().
The agent gets no download links.
The agent is told to return paths, not contents. In its final answer it names a file by its path and tells the user what the file contains.
What the user sees
Section titled “What the user sees”A download can show the previous version of a file for up to 5 seconds after a script writes it.
In the Urai interface
Section titled “In the Urai interface”Each message shows the files that the agent made during that message. Browse files opens a panel with all the files in the thread. From the panel, the user downloads one file, or all files as a ZIP archive.
Urai sends HTML, SVG, and XML files with a sandbox content security policy. The browser shows these files, but it does not run scripts in them.
In the chat widget
Section titled “In the chat widget”The widget shows the files that the agent made on the reply that made them:
- Images (PNG, JPEG, GIF, WebP, AVIF, BMP, and SVG) show in the message.
- Other files show as a download link with the file size.
- Files show when each tool call finishes, and they stay on the message in the history.
- A file shows again on a later reply only when that reply changed the file.
- The widget does not list files in
/uploads/,/work/, or/canvas/.
When the conversation has shown at least one file, the widget header shows Download all files. This button downloads all the files in the thread as a ZIP archive, including the files that the conversation does not list.
The widget gets each file with the visitor id header, so one visitor cannot read the files of another visitor. See Embed the widget to change how files show.
Canvases
Section titled “Canvases”A canvas is a small web page that the agent builds for the user. It opens beside
the conversation. The agent writes the canvas files with the write_file tool,
under /canvas/<name>/. A canvas opens when its index.html file exists.
Canvases open in the Urai interface. The chat widget does not open them.
The agent’s instructions tell it to make a canvas only when the user must
interact with the result. A static chart or report is a file in /out/.
A canvas runs in a sandbox:
- It loads only its own files, and pinned scripts from
cdn.jsdelivr.netandcdnjs.cloudflare.com. - It cannot call APIs, import
urai:modules, or read secrets. - It cannot use
localStorage,sessionStorage, IndexedDB, or cookies.
A script can write a data file beside the canvas with urai:fs, and the canvas
loads that file.
| Canvas limit | Value |
|---|---|
| Name | 1 to 64 characters from a-z, 0-9, -, and _. It cannot start with - or _. |
| Folder depth below the canvas | 4 |
Size of one file written with write_file |
512 KiB |
| Life of a canvas link | 1 hour. The interface makes a new link each time the user opens the canvas. |
Limits
Section titled “Limits”These are the default limits. Uploads, scripts, and canvases share them.
| Limit | Default |
|---|---|
| Total size of the thread’s files | 512 MiB |
| Size of one file | 64 MiB |
| Number of files | 20,000 |
Urai checks a limit before it writes. When a write goes over a limit, Urai
writes nothing and the file does not change. A script reads its usage with
fs.usage().
Security
Section titled “Security”- Each thread has its own files. A script cannot reach the files of another
thread or the files of the host. Paths that contain
..are refused. - Files stay. A secret that a script writes to a file stays after the execution that was allowed to read the secret. Tell the agent in its instructions never to write a secret to a file.
- Urai logs file access. For each file operation in an agent script, Urai logs the path and the number of bytes. It does not log the contents.
- Deleting a thread does not delete its files yet. A copy of a thread starts with no files.
- Read and write files from code shows how to use
urai:fs. - Reference lists every method, type, and error.