Metadata-Version: 2.5
Name: recuerdos-ai
Version: 0.5.0
Summary: Python client for Recuerdos AI, a self-hostable long-term memory service for AI agents.
Project-URL: Homepage, https://github.com/CoolDevGuys/recuerdos-ai
Project-URL: Documentation, https://github.com/CoolDevGuys/recuerdos-ai/blob/main/docs/sdk-python.md
Project-URL: Source, https://github.com/CoolDevGuys/recuerdos-ai
Project-URL: Changelog, https://github.com/CoolDevGuys/recuerdos-ai/blob/main/docs/CHANGELOG.md
Author: Recuerdos AI contributors
License-Expression: Apache-2.0
Keywords: agents,llm,mcp,memory,rag
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.7
Provides-Extra: dev
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.3; extra == 'langchain'
Description-Content-Type: text/markdown

# recuerdos-ai

Python client for [Recuerdos AI](https://github.com/CoolDevGuys/recuerdos-ai) —
a self-hostable long-term memory service for AI agents, over REST and MCP.

```bash
pip install recuerdos-ai
```

You need a running daemon. The fastest way:

```bash
docker run -p 7070:7070 -v recuerdos-ai-data:/data \
  -e RECUERDOS_AI_AUTH__MODE=none ghcr.io/cooldevguys/recuerdos-ai
```

## The loop

```python
from recuerdos_ai import Client

ra = Client(base_url="http://localhost:7070", api_key="ra_live_…")

# Raw text in. The service decides what is worth remembering, splits it
# into atomic memories, and supersedes anything they contradict.
ra.save("btw we moved the backend to Hetzner, fly.io got too expensive. "
        "Also always write table-driven tests in Go")

# Ask the question you actually have.
for hit in ra.search("where do we deploy?"):
    print(hit.content)   # "The backend runs on Hetzner (migrated from Fly.io …)"
```

`save` returns a job, because extraction is a model call that takes
seconds:

```python
job = ra.save("User is vegetarian now")
finished = ra.wait_for_job(job)
print(finished.memory_ids)

# Or, for scripts:
memories = ra.save_and_wait("User is vegetarian now")
```

## When you already know what to remember

`save_direct` stores one memory as given, with what it is and who it is true
for. It is cheap and synchronous, and still checked against what is stored for
the same scope: a contradiction is refused rather than stored alongside. Prefer
`save` unless you have already distilled the memory yourself.

```python
saved = ra.save_direct(
    "User forbids barrel files / index.ts re-exports",
    kind="preference",
    topic="coding",
    applies_to="me",
    origin="user_said",
    tags=["typescript", "imports"],
)
print(saved.message)
```

The fields are explained in the repository's `docs/memory-model.md`. A memory
rule that refuses the save raises `RejectedError` with its `code`. For a
`conflict`, `error.conflicts` names the stored memories: ask the user, then save
again with `replaces=` (they changed their mind) or `coexists_with=` (both hold),
or fix the stored one with `ra.update(...)`. `ra.update` also closes a goal:
`ra.update(goal_id, goal_status="achieved", origin="user_confirmed")`.

Projects are listed with `ra.projects()`, renamed with
`ra.rename_project("quill", "quill-app")` (memories follow, the old name still
finds it) and merged with `ra.merge_project("me-quill", into="quill")`.

## Session distillation

At the end of a working session, hand over what happened and keep only
what outlives it:

```python
result = ra.distill_session(transcript, session_id="s-42", client="my-agent")
print(f"{result.distilled} memories survived the session")
```

`distilled == 0` is the ordinary outcome. Most sessions produce nothing
that stays true after they end.

## Session start

```python
print(ra.profile())   # markdown, ~1500 tokens
```

Read it before the first turn. Recall answers a question; an agent that
has not asked one yet still needs to know the conventions it is expected
to follow.

## LangChain

```bash
pip install "recuerdos-ai[langchain]"
```

```python
from recuerdos_ai import Client
from recuerdos_ai.langchain import RecuerdosRetriever

retriever = RecuerdosRetriever(
    client=Client(api_key="ra_live_…"),
    limit=5,
    categories=["preference.coding", "decision"],
)
docs = retriever.invoke("how should imports be structured?")
```

See [`examples/`](examples/) for a LangGraph agent that reads its memory
at the start of a thread and writes back at the end.

## Errors

Every failure is a `RecuerdosError` subclass, keyed off the API's
stable error code rather than its message:

```python
from recuerdos_ai import NotFoundError, PermissionError_, RecuerdosError

try:
    ra.get(memory_id)
except NotFoundError:
    ...                     # gone, or someone else's — indistinguishable by design
except PermissionError_:
    ...                     # valid key, missing scope
except RecuerdosError as error:
    print(error.request_id)  # ties this to the server log line with the real cause
```

## Notes

- **Sync only.** Every call is one request to a service usually on
  localhost; the work that takes seconds already happens off the request
  path behind a job id. Inside an async framework, use
  `asyncio.to_thread`.
- **A key is a user.** There is no user parameter anywhere — two users
  means two clients.
- **`search_report()` is `search()` plus the top level of the response**:
  the relevance floor that produced the list, the server's own `took_ms`, and
  what an enabled rerank stage did — `applied`, `shadow`, `not_needed`, or
  `skipped` with a reason. A stage that misses `[rerank].timeout_ms` serves
  the pre-rerank order on purpose, and this is how a client can tell that
  apart from the feature being off.
- **Unknown response fields are ignored**, so an older SDK keeps working
  against a newer daemon.

Full reference: [docs/sdk-python.md](https://github.com/CoolDevGuys/recuerdos-ai/blob/main/docs/sdk-python.md).

Apache-2.0.
