Files in chat
Astro AI’s web chat can send files to an agent and render files from the agent as download chips. The transfer has two parts:
- The Files API moves bytes between the browser and the deployment’s persistent file store.
- The messaging API associates an uploaded file key with a user or assistant message.
The browser never puts file bytes in a chat message. The agent normally does not call the Files API either: Astro AI mounts the same file store into the agent container and sets AGENT_FILES_DIR to its location (normally /data/files).
Enable file-enabled chat
File-enabled chat needs two things: a messaging deployment with the web adapter (which provisions and mounts the file store automatically), and an agent that declares it consumes files.
First, declare a messaging agent. The file store is mounted automatically; there is no separate storage setting.
Then set supportsFiles in the config your agent sends at startup. The chat’s upload controls (the paperclip, drag-and-drop, paste, and the Files tab) appear only for agents that set this flag, so an agent that ignores uploads never shows controls that do nothing. The flag defaults to off; having the file store mounted is not enough on its own. It gates only the upload affordances; agent-produced download chips render regardless.
Node.js
Python
In local development, open the chat URL printed by ast dev (normally http://localhost:3100). Users can attach a file with the paperclip, drag and drop, or paste. The chat uploads the bytes when the message is sent and passes the resulting key with that message.
Read files attached by a user
When you use an Astro AI adapter, each call to your agent receives the current turn’s files in StreamOptions.attachments. Treat this list as the authority for the turn. Do not enumerate the whole shared directory to decide which files belong to the current user.
Each attachment contains:
Node.js
Python
path is absent only when an older sidecar or SDK did not carry the storage key. Never guess AGENT_FILES_DIR/<original filename>: API-managed uploads are stored by opaque key, not by their display name. Update the messaging and adapter packages before relying on file input.
Return a file to the user
Write the complete output as a regular file directly inside AGENT_FILES_DIR, then register it with the response hook before finishing the response. The bridge sends the file reference on the END chunk, and the chat renders a download chip.
Use a new, single-segment filename for every output. A UUID prefix prevents concurrent conversations or different users from colliding on a shared filename. Do not use subdirectories, symlinks, or names ending in .blob, .meta.json, or .tmp; those are reserved storage artifacts and are not exposed as agent outputs.
Node.js
Python
The file must exist before onFile / on_file, and the hook’s name must exactly match the basename on disk. If the sidecar cannot find that regular file when it processes the terminal chunk, it omits the download chip rather than creating a broken link.
Use the raw messaging SDK
If you manage the gRPC stream yourself, the same rules apply without the adapter conveniences.
For an inbound web attachment:
- Select attachments with type
FILE. - Use
storageKey(Node) orstorage_key(Python), notfilename, to locate the bytes. - Filesystem-backed uploads are stored at
AGENT_FILES_DIR/<storage key>.blob.
For an outbound file:
- Write a uniquely named regular file directly inside
AGENT_FILES_DIR. - Send a
FileAttachmenton the terminalContentChunk. - Set
filenameto the file’s basename. Leaveurlempty for a filesystem file.
Node.js
Python
See the Node messaging SDK or Python messaging SDK for the full message and response shapes.
Build a custom chat client
Astro AI’s built-in chat already implements this flow. A custom client must use both APIs in order:
Reserve a file key
Call POST /api/v1/deployments/{deploymentId}/files with the display metadata and exact byte size.
The response contains an opaque key and an upload descriptor:
Upload the bytes
Resolve a relative upload.url against /api/v1/deployments/{deploymentId}/files/. Absolute URLs are presigned storage URLs and must be used unchanged. Send the bytes with the returned method and headers. Include the user’s Astro AI credentials only for a same-origin Astro AI URL; never forward them to a presigned cross-origin URL.
The upload is not attachable until this request succeeds. A 413 means the declared or received file is too large; 507 means the deployment volume cannot fit it.
Ownership and storage rules
Setting supportsFiles changes what your agent ingests: it starts accepting user uploads. Treat attachment bytes, display names, and MIME types as untrusted input, and apply the same parser limits and content validation you would use for any user upload. The agent container shares the underlying volume, so use only the current turn’s attachment paths for input — do not scan or expose unrelated files.
- Files are scoped to the authenticated user, even when several users share a deployment. List, read, download, delete, and message-attachment checks all enforce that scope.
- Agent-produced files are assigned to the conversation owner when the reply is finalized. A file already assigned to another user is not transferred or attached.
- Upload limits depend on the active deployment path and storage backend. Clients should treat
413 Payload Too Largeas authoritative and prompt the user to choose a smaller file. The store also reserves free space for chat data; clients should surface507 Insufficient Storageand let the user delete files before retrying. - Agent code writes outputs directly to the shared volume, so it must bound output size and handle disk-full write errors; direct agent writes do not pass through the upload limit or capacity reservation.
- Symlinks are never adopted or served. Only regular files directly inside
AGENT_FILES_DIRcan become agent-produced downloads. - File metadata and chat history persist, so upload and download chips survive a page reload. If a user later deletes a file, its old message metadata remains but its bytes are no longer downloadable.