> ## 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.

# Python SDK

> The official Python client for Anona Memory, with sync and async methods for every core operation.

```bash theme={null}
pip install anona
```

Requires Python 3.10 or newer.

```python theme={null}
from anona import AnonaClient

client = AnonaClient(api_key="anona_live_YOUR_KEY")

client.record(space_id="customer-support-bot", content="Alice is a senior engineer at Google")

results = client.retrieve(space_id="customer-support-bot", query="Where does Alice work?")
print(results)
```

## Client initialization

```python theme={null}
client = AnonaClient(
    api_key="anona_live_YOUR_KEY",
    base_url="https://api.anonalabs.com",  # optional, this is the default
    timeout=120.0,                         # optional, seconds, per attempt
    max_retries=2,                         # optional
    retry_max_wait=60.0,                   # optional, seconds
)
```

| Argument | Default | What it does |
| - | - | - |
| `api_key` | required | Your key. Not read from the environment — pass it. |
| `base_url` | `https://api.anonalabs.com` | The API to call. |
| `timeout` | `120.0` | Seconds for one attempt. High on purpose: `reason` at `depth="thorough"` can run well past a minute while the server is still working, and a short timeout kills a call that was going to succeed. |
| `max_retries` | `2` | Retries *per call*, on the statuses below. `0` turns retrying off. |
| `retry_max_wait` | `60.0` | Ceiling on one wait. A `429` names the seconds to wait and the rate-limit window is a whole minute, so the default is high enough to actually sit the window out. |

`timeout` is per attempt, not per call: with `max_retries=2` a call can take up
to three timeouts plus its backoffs.

What gets retried is **not** every call — see
[What the SDKs already retry](https://docs.memory.anonalabs.com/api-reference/errors.md#what-the-sdks-already-retry).
The short version: a `429` on every method, a `5xx` only on `GET`, `PUT` and
`DELETE`, and never on a multipart upload. `retrieve` and `reason` are `POST`
requests, so this client does not retry them on a `5xx`.

The client does not read an environment variable automatically. Pass `api_key`
explicitly:

```python theme={null}
import os

client = AnonaClient(api_key=os.environ["ANONA_API_KEY"])
```

Use it as a context manager so connections close cleanly:

```python theme={null}
with AnonaClient(api_key="anona_live_YOUR_KEY") as client:
    client.record(space_id="customer-support-bot", content="...")
```

## Methods

| Method | Returns | Reference |
| - | - | - |
| `record(space_id, content, metadata=None, tags=None, background=False, user_id=None, agent_id=None, session_id=None, timestamp=None, context=None, *, route=None, fallback_space_id=None, max_targets=None)` | `dict` | [Memories](https://docs.memory.anonalabs.com/api-reference/memories.md) |
| `record_batch(space_id, items, user_id=None, agent_id=None, session_id=None, *, route=None, fallback_space_id=None, max_targets=None)` | `dict` | [Bulk ingest](https://docs.memory.anonalabs.com/api-reference/memories.md#bulk-ingest) |
| `retrieve(space_id, query, limit=10, mode="accurate", user_id=None, agent_id=None, session_id=None, as_of=None, query_timestamp=None, occurred_after=None, occurred_before=None, top_k=None, memory_type=None, tags=None, tags_match=None, tag_groups=None, prefer_observations=None, min_score=None, member_id=None, *, route=None, fallback_space_id=None, max_targets=None)` | `RetrieveResults` (a `list[dict]`) | [Retrieve](https://docs.memory.anonalabs.com/api-reference/retrieve.md) |
| `get_context(space_id, query, limit=10, max_tokens=None, block_order=None, …)` | `str` | [Prompt-ready context](https://docs.memory.anonalabs.com/api-reference/retrieve.md#prompt-ready-context) |
| `retrieve_receipt(space_id, query, limit=10, mode="accurate", receipt_detail="basic", …)` | `RetrieveWithReceipt` | [Context receipts](https://docs.memory.anonalabs.com/api-reference/context-receipts.md) |
| `get_receipt(request_id)` | `dict` | [Context receipts](https://docs.memory.anonalabs.com/api-reference/context-receipts.md) |
| `explain(request_id, memory_id)` | `dict` | [Context receipts](https://docs.memory.anonalabs.com/api-reference/context-receipts.md) |
| `reason(space_id, query, user_id=None, agent_id=None, session_id=None, model=None, depth=None, tag_groups=None)` | `str` or `None` | [Reason](https://docs.memory.anonalabs.com/api-reference/reason.md) |
| `reason_receipt(space_id, query, user_id=None, agent_id=None, session_id=None, model=None, depth=None, tag_groups=None)` | `ReasonWithReceipt` | [Reason](https://docs.memory.anonalabs.com/api-reference/reason.md) |
| `get_user_profile(space_id, user_id, *, limit=None, offset=None, memory_type=None, format=None, context_max_tokens=None)` | `dict` | [User profiles](https://docs.memory.anonalabs.com/api-reference/user-profiles.md) |
| `ask_about_user(space_id, user_id, query, *, model=None)` | `dict` | [User profiles](https://docs.memory.anonalabs.com/api-reference/user-profiles.md) |
| `list_spaces()` · `list_space_types()` | `list[dict]` | [Spaces](https://docs.memory.anonalabs.com/api-reference/spaces.md) |
| `create_space(name, description=None, *, space_type=None)` · `delete_space(space_id)` | `dict` / `None` | [Spaces](https://docs.memory.anonalabs.com/api-reference/spaces.md) |
| `get_job(space_id, job_id)` | `dict` | [Job status](https://docs.memory.anonalabs.com/api-reference/memories.md#job-status) |
| `upload_file(space_id, file, *, filename=None, strategy=None, tags=None, user_id=None, agent_id=None, session_id=None)` | `dict` | [Documents](https://docs.memory.anonalabs.com/api-reference/documents.md) |
| `list_documents(space_id, *, limit=100, offset=0)` | `list[dict]` | [Documents](https://docs.memory.anonalabs.com/api-reference/documents.md) |
| `delete_document(space_id, document_id)` | `None` | [Documents](https://docs.memory.anonalabs.com/api-reference/documents.md) |
| `delete_memory(space_id, memory_id)` | `None` | [Memories](https://docs.memory.anonalabs.com/api-reference/memories.md) |
| `get_graph(space_id, *, limit=500, min_count=1)` | `dict` | [Graph](https://docs.memory.anonalabs.com/api-reference/graph.md) |
| `list_entities(space_id, *, limit=100, offset=0)` · `get_entity(space_id, entity_id)` | `list[dict]` / `dict` | [Graph](https://docs.memory.anonalabs.com/api-reference/graph.md) |
| `create_webhook` · `list_webhooks` · `update_webhook` · `delete_webhook` · `list_webhook_deliveries` | — | [Webhooks](https://docs.memory.anonalabs.com/api-reference/webhooks.md) |
| `get_chat_settings` · `set_chat_settings` · `reset_chat_settings` | `dict` | [Chat defaults](https://docs.memory.anonalabs.com/api-reference/chat.md#space-defaults) |
| `get_extraction_settings` · `set_extraction_settings` · `reset_extraction_settings` | `dict` | [Extraction settings](https://docs.memory.anonalabs.com/api-reference/extraction-settings.md) |
| `get_space(space_id)` | `dict` | [Spaces](https://docs.memory.anonalabs.com/api-reference/spaces.md) |
| `list_memories(space_id, *, limit=50, offset=0, q=None, memory_type=None, state=None, prefer_observations=None, user_id=None, agent_id=None, session_id=None, member_id=None)` | `dict` | [Memories](https://docs.memory.anonalabs.com/api-reference/memories.md) |
| `get_memory_history(space_id, memory_id)` · `update_memory(space_id, memory_id, *, text=None, …)` | `dict` | [Memories](https://docs.memory.anonalabs.com/api-reference/memories.md) |
| `get_document(space_id, document_id)` | `dict` | [Documents](https://docs.memory.anonalabs.com/api-reference/documents.md) |
| `cancel_job(space_id, job_id)` | `dict` | [Job status](https://docs.memory.anonalabs.com/api-reference/memories.md#job-status) |
| `get_usage()` | `dict` | [Usage](https://docs.memory.anonalabs.com/api-reference/usage.md) |
| `get_space_profile(space_id)` · `set_space_profile(space_id, *, mission, disposition)` — both keyword-only and both required, either may be `None`, since the PUT is a full replace · `reset_space_profile(space_id)` | `dict` / `None` | [Memory models](https://docs.memory.anonalabs.com/api-reference/models.md) |
| `list_memory_models(space_id, *, limit=50, offset=0, tags=None)` · `create_memory_model(space_id, *, name, query, model_id=None, tags=None, max_tokens=None, trigger=None)` | `dict` — create answers `202` with `{model_id, job_id, status}`, not the model | [Memory models](https://docs.memory.anonalabs.com/api-reference/models.md) |
| `get_memory_model(space_id, model_id)` · `update_memory_model(space_id, model_id, *, name=None, …)` · `delete_memory_model(space_id, model_id)` | `dict` / `None` | [Memory models](https://docs.memory.anonalabs.com/api-reference/models.md) |
| `refresh_memory_model(space_id, model_id)` · `clear_memory_model(space_id, model_id)` · `get_memory_model_history(space_id, model_id)` | `dict` | [Memory models](https://docs.memory.anonalabs.com/api-reference/models.md) |
| `list_catalog_models()` | `dict` | [Model selection](https://docs.memory.anonalabs.com/api-reference/model-selection.md) |
| `get_reason_settings(space_id)` · `set_reason_settings(space_id, *, model, depth)` · `reset_reason_settings(space_id)` | `dict` / `None` | [Model selection](https://docs.memory.anonalabs.com/api-reference/model-selection.md) |
| `list_rules(space_id)` · `create_rule(space_id, *, name, content, priority=0, is_active=True, tags=None)` · `update_rule(space_id, rule_id, *, name=None, content=None, priority=None, is_active=None, tags=None)` · `delete_rule(space_id, rule_id)` | `list[dict]` / `dict` / `None` | [Rules](https://docs.memory.anonalabs.com/api-reference/spaces.md#rules) |

`get_context` and `retrieve_receipt` take the same filters as `retrieve` —
scope, temporal, tag, `memory_type`, `top_k`, `prefer_observations`,
`min_score`, `member_id` — which is what the `…` stands for above.

Every method also has an `async_`-prefixed counterpart — see
[Async support](#async-support).

> [!NOTE]
>
> `top_k` is an **alias for `limit`**, not a candidate bound: the API takes it in
> place of `limit` and it wins when both are sent. The docstring on `retrieve`
> describes it as bounding the candidates considered before ranking, which the API
> does not do — ignore the docstring and read
> [the retrieve parameters](https://docs.memory.anonalabs.com/api-reference/retrieve.md#parameters).

### Parameters the REST API has and this client does not

The API reference documents the wire, and these fields have no keyword argument
here. Reach for `httpx` (or the equivalent) for the few that you need; nothing
is lost by a plain request, since the client adds no behaviour beyond auth,
retrying and error translation.

| Endpoint | REST-only fields |
| - | - |
| `POST /v1/retrieve` | `expand_query`, `include_source`, `source_max_tokens`, and the connector provenance filters `source` / `repo` / `pull_request` / `author` / `merged_by`. (`format`, `context_max_tokens` and `block_order` are not missing — `get_context` is the method that sends them, and `retrieve_receipt` sends `receipt` / `receipt_detail`.) |
| `GET /v1/spaces/{space_id}/memories` | `tag_groups`, and the same provenance filters. |
| `GET /v1/spaces/{space_id}/documents` | `q`, `source`. |
| `POST /v1/spaces/{space_id}/users/{user_id}/ask` | `depth`. `ask_about_user` takes `model` only, so the depth falls through to the space's reason default. |
| Every endpoint | `org_id`. An API key already belongs to one organization, so there is nothing for the client to choose — the field exists to let a caller *assert* which organization it meant and get `400 org_mismatch` instead of being served the key's. |

### record

```python theme={null}
memory = client.record(
    space_id="customer-support-bot",
    content="Alice works at Google and specializes in distributed systems",
    user_id="alice_123",
    tags=["work"],
    metadata={"source": "crm"},
)
print(memory["memory_id"])
```

Pass `background=True` to queue the write instead of waiting. The call then returns a
job rather than a stored memory; poll it with `get_job`.

> [!WARNING]
>
> **`background=True` is several times faster, and should be your default.** A blocking
> write waits for a language model to extract facts from the text before it returns.
> Measured against production, about 3-12 seconds for a short memory (longer content takes
> longer) versus 1-3 seconds queued. Use a blocking
> write only when you search for the memory immediately afterwards.
>
> ```python theme={null}
> job = client.record(
>     space_id="customer-support-bot",
>     content="Alice is a senior engineer at Google",
>     background=True,
> )
> client.get_job(space_id="customer-support-bot", job_id=job["job_id"])
> ```

`user_id`, `agent_id` and `session_id` scope the memory inside the space, so only a
`retrieve` carrying the same scope sees it. That is how one space serves many end
users. See [Scoping](https://docs.memory.anonalabs.com/api-reference/retrieve.md#scoping-within-a-space).

`timestamp` is when the event happened, not when you are recording it. Set it when
importing history so a memory about last June is dated last June. See
[Searching over time](https://docs.memory.anonalabs.com/api-reference/retrieve.md#searching-over-time).

> [!WARNING]
>
> A user id passed in `metadata` is stored but isolates nothing: `metadata` is returned
> with results, never filtered on. Use the `user_id` argument.

### retrieve

```python theme={null}
results = client.retrieve(
    space_id="customer-support-bot",
    query="Where does Alice work?",
    limit=10,
    user_id="alice_123",   # only Alice's memories
)
for r in results:
    print(r["content"], r["relevance_score"])
    # A `note` names the facts it was synthesized from. Empty on a `fact`,
    # which is the evidence itself.
    for source_id in r["source_ids"]:
        print("  built from", source_id)
```

`source_ids` is always present, so it is safe to iterate without a guard. Because a
note is derived, its own `metadata` is empty and its `document_id` is `None`: those
belong to the facts behind it. Pass `prefer_observations=False` to get those facts
back alongside their note, each carrying the metadata you wrote it with.

`as_of` restricts the search to memories **recorded** at or before an instant, so you
get what the space knew then. `query_timestamp` moves the "now" that recency scoring
and relative dates in the query are measured against, and never removes a result. See
[Searching over time](https://docs.memory.anonalabs.com/api-reference/retrieve.md#searching-over-time).

Returns the `results` list directly, and an empty list when nothing matches. A scoped
search is strict: it never returns another user's memories, nor memories stored without
a scope.
`mode="fast"` skips the neural rerank pass for lower latency, at some cost to relevance
quality. See [Latency modes](https://docs.memory.anonalabs.com/api-reference/retrieve.md#latency-modes).

### retrieve\_receipt, get\_receipt and explain

`retrieve` returns a plain list, which has nowhere to carry the id of the
receipt for that search. `retrieve_receipt` is the same search and returns
both.

```python theme={null}
res = client.retrieve_receipt(
    space_id="customer-support-bot",
    query="who is on call for billing?",
    receipt_detail="full",
)
for row in res.memories:
    print(row["content"])

receipt = client.get_receipt(res.receipt_id)
for cut in receipt["excluded"]:
    print(cut["reason"], cut["detail"])
```

The result iterates and indexes like the list `retrieve` returns, so
`len(res)`, `res[0]` and `for row in res` all work and swapping one method for
the other rarely means changing the code around it.

`receipt_detail="full"` also asks the search to account for its own cuts, so
the receipt can explain memories that were ranked and dropped before `limit` or
a relevance floor ever applied. It can cost latency on the first call for a
given query. Leave it `"basic"` for production traffic.

`explain` answers the question the receipt cannot: why one specific memory is
not in your results.

```python theme={null}
verdict = client.explain(res.receipt_id, "mem_abc123")
verdict["outcome"]   # "included" | "excluded" | "not_retrieved"
verdict["stage"]     # "retrieval" | "fusion" | "rerank", when excluded
verdict["arms"]      # {"semantic": 812, "keyword": None, ...}
```

`not_retrieved` is the one to act on: nothing matched the memory at all, so a
bigger `limit` or a lower floor will not bring it back, and the query wording
or the scope you searched is what to change. `arms` shows which kind of
matching found it, and how well: found by `keyword` but not `semantic` usually
means your query shares words with the memory but not meaning.

Every search builds a receipt whether or not you asked for one, so
`get_receipt` and `explain` also work on a request id you pulled from your own
logs. See [Context receipts](https://docs.memory.anonalabs.com/api-reference/context-receipts.md).

### reason

```python theme={null}
answer = client.reason(
    space_id="customer-support-bot",
    query="What have we learned about Alice's career?",
)
print(answer)
```

Returns the synthesized answer string, or `None`.

### get\_user\_profile and ask\_about\_user

Everything a space has learned about one end user, and a question answered from
that user's memories only. Both need `user_id` to be the same value your writes
are scoped with.

```python theme={null}
profile = client.get_user_profile("customer-support-bot", "alice_123")
print(profile["memory_count"], profile["last_active"])

# Prompt-ready instead of a list
block = client.get_user_profile(
    "customer-support-bot", "alice_123", format="block", context_max_tokens=500
)
print(block["context"])

answer = client.ask_about_user(
    "customer-support-bot", "alice_123", "How does she prefer to be contacted?"
)
print(answer["insights"], answer["model"])
```

`ask_about_user` returns the whole response rather than the answer string, so
`model` is available: that is the model which actually answered, and what the
credits on the call were charged at. It differs from what you asked for whenever
you asked for nothing.

A `user_id` nobody has recorded under is not an error: it comes back with
`memory_count` of `0` and an empty `memories` list, because a user is a scope tag
created by the first write naming it rather than a resource you register. An
unknown *space* is still a 404.

`memory_count` can also go **down** between two reads, since consolidation folds
several raw facts into one note and the default view counts the note. Read it as
how many distinct things are currently known about this user, not as an
ingestion counter. See [User profiles](https://docs.memory.anonalabs.com/api-reference/user-profiles.md).

### list\_spaces

```python theme={null}
spaces = client.list_spaces()
for s in spaces:
    print(s["space_id"], s["name"])
```

### upload\_file

Uploads a file into a space so retrieval can draw on its content. `file` may be a path,
`bytes`, or a binary file-like object. Ingestion is asynchronous and returns
`{"job_ids": [...]}`; poll each with `get_job`. Files over 25 MB are rejected
client-side, before upload.

```python theme={null}
job = client.upload_file(
    space_id="customer-support-bot",
    file="handbook.pdf",
    tags=["hr", "policy"],
)
print(job["job_ids"])
```

Supported formats are PDF, DOCX, DOC, PPTX, PPT, XLSX, XLS, HTML, TXT, Markdown,
CSV, JPEG/PNG images, MP3/WAV audio and MP4/MOV/WEBM/MKV video. Images, audio
and video are read into text. See [Documents](https://docs.memory.anonalabs.com/api-reference/documents.md).

### list\_documents and delete\_document

```python theme={null}
docs = client.list_documents(space_id="customer-support-bot")
for d in docs:
    print(d["document_id"], d["memory_count"])

client.delete_document(space_id="customer-support-bot", document_id="file_9c2f1a")
```

## Async support

Every method has an `async_` counterpart, backed by a separate `httpx.AsyncClient`.

```python theme={null}
import asyncio
from anona import AnonaClient

async def main():
    client = AnonaClient(api_key="anona_live_YOUR_KEY")

    await client.async_record(space_id="customer-support-bot", content="Alice works at Google")
    results = await client.async_retrieve(
        space_id="customer-support-bot", query="Where does Alice work?"
    )
    print(results)

    await client.aclose()

asyncio.run(main())
```

Use `async with AnonaClient(...) as client:` for automatic cleanup.

## Error handling

```python theme={null}
from anona import AnonaClient, AnonaError

client = AnonaClient(api_key="anona_live_YOUR_KEY")

try:
    results = client.retrieve(space_id="customer-support-bot", query="Where does Alice work?")
except AnonaError as e:
    print(e.status_code)  # int
    print(e.detail)       # parsed error body (dict), or raw text
```

See the [error reference](https://docs.memory.anonalabs.com/api-reference/errors.md) for every code and which are safe to
retry.

## Framework adapters

If you already build on an agent framework, you do not need to call `record` and
`retrieve` yourself. Each adapter wires recall and storage into that framework's own
memory seam, and scopes memories per end user.

| Framework | Install | Guide |
| - | - | - |
| LangChain / LangGraph | `pip install 'anona[langchain]'` | [LangChain](https://docs.memory.anonalabs.com/integrations/langchain.md) |
| CrewAI | `pip install 'anona[crewai]'` | [CrewAI](https://docs.memory.anonalabs.com/integrations/crewai.md) |
| LlamaIndex | `pip install 'anona[llamaindex]'` | [LlamaIndex](https://docs.memory.anonalabs.com/integrations/llamaindex.md) |
| Google ADK | `pip install 'anona[adk]'` | [Google ADK](https://docs.memory.anonalabs.com/integrations/google-adk.md) |
| Microsoft Agent Framework | `pip install 'anona[msagent]'` | [Agent Framework](https://docs.memory.anonalabs.com/integrations/microsoft-agent-framework.md) |
| AWS Strands | `pip install 'anona[strands]'` | [Strands](https://docs.memory.anonalabs.com/integrations/strands.md) |

A complete runnable script for each one is on the [Examples](https://docs.memory.anonalabs.com/integrations/examples.md)
page.

All six share one `MemoryBridge`, which resolves scope and owns the failure contract:

```python theme={null}
from anona.integrations import MemoryBridge

bridge = MemoryBridge(
    api_key="anona_live_YOUR_KEY",
    space_id="customer-support-bot",
    user_id="customer-42",   # optional scope: this user's memories only
)
```

> [!NOTE]
>
> **Memory failures never raise into your agent.** A failed recall or store is logged
> and the agent runs on without memory, rather than taking your application down. This
> is deliberate and applies to every adapter. See each guide's *Failure behaviour*
> section for what the agent sees instead.

## MCP server

The SDK also ships an MCP server for connecting Anona Memory to Claude Code, Claude
Desktop, and other MCP clients. See the [MCP guide](https://docs.memory.anonalabs.com/mcp-integration.md).

## Next steps

> **[Framework adapters](https://docs.memory.anonalabs.com/integrations/langchain.md)**
>
> LangChain, CrewAI, LlamaIndex, ADK, Agent Framework, Strands.

> **[MCP](https://docs.memory.anonalabs.com/mcp-integration.md)**
>
> Connect Claude Code and other MCP clients.

> **[API overview](https://docs.memory.anonalabs.com/api-reference/overview.md)**
>
> The full REST surface.


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