> ## Documentation Index
> Fetch the complete documentation index at: https://docs.memory.anonalabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Chat with your documents

> Upload a file, wait for extraction, ask. Uploads land in the same space as everything else you recorded.

[← All use cases](https://docs.memory.anonalabs.com/use-cases/overview.md)

[document\_qa.py](https://github.com/anonalabs/Anona-Memory-SDK/blob/main/examples/document_qa.py)

## The scenario

Half of what your agent should know is in files — a policy, a spec, a handbook.
You do not want a second retrieval system for them, and you do not want to
pre-chunk anything.

Uploads land in the same space as recorded memories, so one `retrieve` answers
from both.

## Step 1 — Upload

```python theme={null}
job = client.upload_file(space_id=space, file="refund-policy.txt")
```

`file` takes a path, raw bytes, or a file object. PDF, DOCX, PPTX, XLSX, HTML,
TXT/MD/CSV, images, MP3/WAV audio and MP4/MOV/WEBM/MKV video are all read into
text.

## Step 2 — Wait for it to be searchable

Ingestion is asynchronous. The upload returns job ids; `completed` means that
file is searchable.

```python theme={null}
for job_id in job["job_ids"]:
    while client.get_job(space_id=space, job_id=job_id)["status"] not in ("completed", "failed"):
        time.sleep(5)
```

In production use [webhooks](https://docs.memory.anonalabs.com/use-cases/events-not-polling.md) instead of a loop.

## Step 3 — Ask, and see which file answered

```python theme={null}
rows = client.retrieve(
    space_id=space,
    query="How long do I have to return hardware?",
    prefer_observations=False,
)
for row in rows:
    if row.get("document_id"):
        print(row["content"], "from", row["document_id"])
```

By default an answer is often a **note** — a synthesis built from several
facts, possibly from several files — and a note has no single `document_id`
(it is `null`; its `source_ids` name the facts behind it). The facts are what
carry the file, so ask for them with `prefer_observations=False` and keep the
rows that have one.

## Step 4 — The document stays a first-class object

```python theme={null}
for doc in client.list_documents(space_id=space):
    print(doc["document_id"], "->", doc["memory_count"], "memories")
```

Deleting a document removes every memory extracted from it in one call — which
is what makes "remove that outdated policy" a single operation rather than a
hunt.

## Evals

1. Upload one file you know the answers in. Ask three questions from it.
2. Ask something the file does not cover. Empty is correct.
3. Check `document_id` resolves on every fact you would act on (with
   `prefer_observations=False` — a note never carries one).
4. Delete the document and re-ask. Everything from it should be gone.

## Guardrails

* **A question asked seconds after upload may legitimately find nothing.** Wait
  for `completed`; do not treat the gap as a bug.
* **Per-file limits apply** — 25 MB generally, 64 MB for video, with request
  totals above that. One oversized file rejects the whole request.
* **Uploads take scope too.** Pass `tags` on upload if later reads will be
  scoped, or the document is invisible to every scoped query.

## The script

Complete and runnable:
[`document_qa.py`](https://github.com/anonalabs/Anona-Memory-SDK/blob/main/examples/document_qa.py).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.