There are three paths a file takes to land in the workspace file store. Each writes a row into workspace_files with a different context.
Drag-drop and the upload picker
The Files module accepts drag-drop into the gallery and a "Browse" picker. Both go through the same upload route:
- The client requests a presigned upload URL from
/api/files/upload. - The browser PUTs the file directly to object storage.
- On success, the client confirms the upload back to the API, which inserts a
workspace_filesrow withcontext = 'workspace'.
The collision rule kicks in here: re-uploading a file with an existing originalName (in the workspace context) fails until the prior row is soft-deleted or renamed.
The same flow drives chat attachments — the Assistant composer's drag-drop calls a chat-scoped variant that writes the row with context = 'chat' and additionally inserts into assistantChatBagFiles so the conversation can project the bag into the Sandbox.
The Files API
For programmatic uploads, the same presigned-URL flow is exposed through the REST API:
# 1. Request a presigned PUT URL.
curl -X POST \
"https://your-actana.example.com/api/files/upload" \
-H "Authorization: Bearer ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"originalName": "report.pdf",
"contentType": "application/pdf",
"size": 102934,
"context": "workspace"
}'
# Response: { key, uploadUrl, headers }
# 2. PUT the bytes directly to object storage with the returned URL.
curl -X PUT "${uploadUrl}" \
-H "${headers}" \
--data-binary @report.pdf
# 3. Confirm — server inserts the workspace_files row.
curl -X POST \
"https://your-actana.example.com/api/files/upload/confirm" \
-H "Authorization: Bearer ${API_KEY}" \
-d '{ "key": "${key}" }'This is the right path for batch imports, sync jobs, or any system that produces files server-side.
Sandbox-generated assets
Files produced inside a Sandbox run land in the file store via writeback rather than an upload. The sandbox holds an upload OTP for the run; at the end of the run it POSTs assets to:
${agentUrl}/results— for Assistant chat sessions. The asset row goes intoassistantChatBagFileswithsource: 'sandbox', scoped to the conversation.${agentUrl}/assets/writeback— for Agents block workflow runs. Anything in the sandbox's/root/workspace/assets/directory is collected and surfaced on the block'sfilesoutput. Pipe that output into a Save File block to persist the assets into the workspace Files module (context = 'workspace').
Without a Save File downstream, the writeback assets stay in the chat bag or workflow bag — they are not durable workspace files.
The upload OTP has a longer TTL (60 minutes) than the boot OTPs (10 minutes) so the sandbox can still write back assets after a long-running model call.
File size + type policy
There is no built-in MIME-type allow-list — the upload route accepts any contentType. Per-deployment size caps live on the storage backend (object-storage policy + reverse proxy client_max_body_size). For very large files, prefer the presigned-URL flow over routing bytes through the application.
Source
apps/actana/app/api/files/upload/route.ts— presigned URL + confirmapps/actana/app/api/files/serve/[key]/route.ts— authenticated readapps/actana/lib/uploads/— utilities + storage adaptersapps/actana/lib/assistant-sandbox/file-bag/— chat-bag projection