Keep the original PDF, spreadsheet or photo so you and your agents can find it again.
Open Knowledge in the app, open the folder it belongs in, and choose Add file, or drop a file on the page.
Say where it came from, pick who can see it, and choose Save file.
Find it later in that folder, after its pages, or by searching. Choose Open to read it.
PDFs, spreadsheets, Word files, photos (JPG, PNG, WEBP, GIF), videos (MP4, MOV), audio (M4A, MP3) and text files up to 64 MB work. Photos, videos and audio play in the browser, and you can skip through a video.
More detail
This part describes the source-file release contract. Availability depends on the tools advertised by your connected Mainmind deployment. A tool listing is not proof that a file was saved: retain the returned saved receipt.
Mainmind accepts PDF, XLSX, CSV, DOCX, PNG, JPG, JPEG, WEBP, GIF, MP4, MOV, M4A and MP3 files, plus UTF-8 Markdown, text, HTML, JSON, YAML and common script files, up to 64 MiB each. Each file's opening bytes must match its extension. SVG is not accepted, because an SVG can carry script.
Context is extracted only from files up to 10 MiB. A larger file is saved and can be opened, but its extraction state is too-large and its contents are not searchable. New originals use private object storage; small source records remain in canonical Git. A plain checkout therefore does not contain every original. Legacy files already stored in Git remain readable.
From the browser
In Knowledge, choose Add file, or drop a file onto the page. Choose its document type and access scope, describe where it came from, then choose Save file. Saving preserves the original; extraction, search readiness and human review are separate outcomes.
Find what you saved
Open a folder in Knowledge, or search across the space. Select a file to preview it, then choose Open to read the full document and its source. The breadcrumbs take you back through the folders. More options holds type filters and other details.
Folders follow the paths already saved in this space. Adding a file keeps its original and creates a source record; the folder you are browsing does not change where that record is saved. A folder that holds only files appears like any other folder, to people who can see those files. A folder shows its first 200 files and says how many it holds; search finds the rest.
From an agent
For files up to 256 KiB decoded, call save_source_file with filename, content_base64 and an idempotency_key. Supply a description and the intended access scope. Reuse the same key when retrying the same save after interruption. Do not invent encoded bytes from a screenshot preview or a summary. A save needs no task. To attach an open task explicitly, supply its run_id and private control_key; the key is verified and never stored with the source.
For larger files, use source_file_upload: begin declares the filename, size, SHA-256 and idempotency key; append sends numbered chunks of at most 196608 bytes decoded; finalize confirms complete storage. The total ceiling is 64 MiB. Use its status action to continue an upload. source_file_status also accepts an upload ID or the original idempotency key after an uncertain save. Never start a second operation just because the first response was lost.
Use read_source_context with record_path, optional query, cursor and max_chars for bounded cited context. Check coverage and extraction state before relying on an excerpt. Read the original resource when unchanged bytes are needed; it carries bytes inline for files up to 10 MiB. Fetch a larger original over HTTP or with the helper's fetch command. Search finds source records; do not assume every file's contents are indexed merely because the original was saved.
A host must expose the actual file bytes or an HTTP transfer mechanism. If it cannot, hand the upload to the browser instead. An attachment in an AI chat is not yet a Mainmind file.
From a checkout
Request source_file_upload with action authorize for the scoped source-file capability and endpoint. Set its returned MAINMIND_SOURCE_URL and MAINMIND_SOURCE_TOKEN privately in the shell. These are separate from the checkout's Git credential; do not print or commit the token.
Download the plain JavaScript helper from the connected Mainmind deployment's HTTPS /api/source-files/cli endpoint to mainmind-source.mjs. Inspect the saved helper before executing it. It uses Node and needs no executable bit.
Terminal
node mainmind-source.mjs save ./invoice.xlsx --reason "Reconcile the August invoice"
node mainmind-source.mjs status UPLOAD_ID
node mainmind-source.mjs context records/source-docs/RECORD.md --query "invoice totals"
node mainmind-source.mjs fetch records/source-docs/RECORD.md --output ./original.xlsx
node mainmind-source.mjs export records/source-docs/RECORD.md --output ./source-export
node mainmind-source.mjs verify ./source-export
node mainmind-source.mjs restore ./source-export
The helper chunks uploads and keeps retry identity in the local user cache. Repeat the same save command after an interruption. Set MAINMIND_SOURCE_STATE_DIR to choose another cache directory. It emits JSON results on standard output and progress on standard error. Downloads verify the fingerprint and refuse to overwrite existing files.
Exports include only selected authorized records and their originals, with a hash inventory and each record's source commit. They are not a complete backup. verify works offline without credentials; it checks local consistency, not a signed proof of authenticity. restore first verifies the export, then saves its originals as new records in the workspace selected by the source capability. It preserves each exported access scope; old exports without scope require explicit --scope. An explicit scope override remains subject to the workspace's access rules. Restore returns the old-to-new record receipts. It does not restore historical paths, roles, or complete space history.
Checkout delivery is where agents receive the proactive reminder to retain relevant originals and useful generated files within the task's authority, record why, and cite the saved reference. Mainmind does not automatically save every chat attachment or scan the user's disk.
Move large files out of Git
The owner can move large photos, videos, audio, PDFs and spreadsheets out of the space's history into saved files, so the space gets lighter. Each keeps its folder and its name, and older versions stay in history. Text, scripts and Markdown stay where they are.
Ask your agent to call move_files_to_storage. With no mode it previews: what would move and where, which Markdown pages link to each file, what stays and why. It changes nothing, and returns a preview_token. Then mode: apply with that token moves up to 40 files or 64 MB at a time. Give apply the same prefix, min_bytes and outside_folders as the preview; if they differ, it refuses. The token is a check that apply uses the same options as the preview, not proof that a preview ran. Call apply again until nothing is left. One apply runs at a time.
Files in the space's knowledge folder can move. A top-level folder beside it, such as content/, moves only when you name it: pass outside_folders: ["content", "outputs"] to both preview and apply. Each name is one exact top-level folder: no wildcards, no dot-folders, and never tools/. Files from those folders keep their folder as it is in the repository.
Before a file is kept, Mainmind checks that its bytes are exactly the ones in the space's history, and runs the same checks as any saved file. The files leave in one change, together with their saved-file pages, so nothing is half moved. A retry repeats nothing that already happened. If a selected file changed since the preview, nothing moves: preview again.
Links keep working. Here, a page means a Markdown page. The same change updates every link to a moved file, and every image a page shows from one, to link to the file's saved-file page instead. An image becomes a link, with its alt text as the link text. Code is never changed, but a link written inside code still keeps its file where it is. A file stays where it is when a page refers to it in a way that can't be updated in that change: for example a page that needs a Decision to change, a page missing the details Mainmind needs to change it safely, a video player, a cover: field, or a GitHub address. The preview and the result name that page. Fix the page, then apply with retry_refused: true. A file also stays when Mainmind Git won't take the change: a name or folder it doesn't allow, or pages too large to update in one change. Other pages, such as .html files in a named folder, are not updated.
Each file's bytes are read from the space's GitHub repository, and every byte is checked against the file as Mainmind Git holds it, so the GitHub copy doesn't have to be up to date. A space made in Mainmind needs Copy to GitHub turned on. A file added since the copy last caught up stays where it is until GitHub has it; the preview checks and lists those files. If the copy has changes Mainmind didn't make, it has stopped following your space: copy anything you want to keep from GitHub into your space first, then set the GitHub branch back to your space's latest version and it catches up. A new change on GitHub that undoes them is not enough, because GitHub would still hold a history Mainmind did not write. Only the owner can move files, from their own connection. A PDF or spreadsheet saved under records/source-docs/files/ keeps its record page, which now points at the saved file.
Get a folder back into a checkout
A script that reads media from a checkout can pull a folder back first. Run it at the checkout's root:
Every saved file whose folder is that folder, or inside it, is written back at its path in the checkout under --to, after its fingerprint is checked: inside the space's knowledge folder, or at its exact old path for a file moved from a named folder beside it. A file already there with the same bytes is left alone. A different one is kept unless you add --replace yes. pull writes only into real folders: it refuses a folder or file that is a link. It needs the same MAINMIND_SOURCE_URL and MAINMIND_SOURCE_TOKEN as fetch.
Copy a folder
Folder copying was announced in What's new. Use it only when the connected deployment's helper advertises these commands. It retains selected current files, without Git history or ongoing synchronization. It needs no source repository after the copy.
First create a local preview. This step uploads nothing and needs no credential. Choose an existing destination space and review the listed paths, sizes and exclusions before copying. Keep the preview outside the selected folder.
The last three commands use the source capability described above. The copy refuses a different destination or files changed since preview. Repeat it with the same preview after interruption. Status lists remaining originals and any uncertain save; do not start another copy to bypass an uncertain result.
A copy contains at most 100 files and 100 MiB, with the 64 MiB per-file ceiling. Links, unsafe or colliding paths, credentials, known local caches and unsupported files are excluded or refused. Text is checked for credential patterns; this is not a guarantee that arbitrary documents contain no private information. Review the selection yourself. Excluded files can leave references unresolved.
Keep the returned inventory reference. On another host, recover-copy restores unchanged originals at their relative paths in a new folder and verifies their fingerprints. Existing files are never overwritten. Scripts and instructions remain retained evidence; copying them neither executes them nor changes the space's authority. Extraction and search readiness remain separate from storage.
From an HTTP client
Use the source-only capability issued by source_file_upload with action authorize as Authorization: Bearer ... on its returned endpoint. POST JSON with action: begin, append or finalize using the same fields as the MCP upload operation. GET that endpoint with upload_id or idempotency_key for status. Keep the original key when retrying an interrupted request.
GET /api/source-context?workspace=WORKSPACE&record=RECORD_PATH for context and GET /api/source-file?workspace=WORKSPACE&record=RECORD_PATH for original bytes, with the same source capability. These endpoints verify its workspace and live scope. The original endpoint answers one Range: bytes= request with 206 Partial Content, so audio and video can seek. Obtain a new capability through the original authorized connection when it expires; a Git credential or deployment bearer cannot substitute for it.