---
title: Add your files
nav: Your files
group: Reference
order: 2.5
---
# Add your files

Keep the original PDF, spreadsheet or photo so you and your agents can find it again.

1. **Open Knowledge** in the app, open the folder it belongs in, and choose
   **Add file**, or drop a file on the page.
2. **Say where it came from**, pick who can see it, and choose **Save file**.
3. **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.

<!-- fold: 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.

```sh
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:

```sh
node mainmind-source.mjs pull content/campaigns --to .
```

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](/updates).
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.

```sh
node mainmind-source.mjs preview ./working-folder --workspace alder-and-ash --scope core --reason "Retain this working context" --output ./copy-preview.json
node mainmind-source.mjs copy ./copy-preview.json
node mainmind-source.mjs copy-status IMPORT_ID
node mainmind-source.mjs recover-copy records/source-docs/INVENTORY.md --output ./recovered-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.

<!-- /fold -->
