Skip to content

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.

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.

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.

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 than
asking 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.

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.

A download can show the previous version of a file for up to 5 seconds after a script writes it.

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.

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.

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.net and cdnjs.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.

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().

  • 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.