Metadata-Version: 2.5
Name: agno-memorysync
Version: 1.1.0
Summary: MemorySync memory backend for Agno agents: a memory-only db for MemoryManager with real semantic recall, guarded deletes, and an async-native twin.
Project-URL: Homepage, https://memorysync.io
Project-URL: Documentation, https://docs.memorysync.io/guides/agno
Project-URL: Changelog, https://docs.memorysync.io/release-notes
Author-email: MemorySync <support@memorysync.io>
License-Expression: MIT
Keywords: agents,agno,ai,llm,memory,memorysync,phidata
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
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 :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: agno>=2.8
Requires-Dist: httpx<1,>=0.25
Description-Content-Type: text/markdown

# agno-memorysync

MemorySync memory backend for [Agno](https://github.com/agno-agi/agno) agents.

A **memory-only db** for Agno's `MemoryManager`: user memories live in
MemorySync's state store for agno (persistent, searchable) while sessions and
other agent state stay in your local db. Each memory's text is also sent to
MemorySync's fact extraction, so the facts are recallable from every
MemorySync surface. Ships a sync `MemorySyncDb` and an async-native
`AsyncMemorySyncDb` twin.

```bash
pip install agno-memorysync
```

## Quickstart

```python
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.memory import MemoryManager
from agno_memorysync import MemorySyncDb

agent = Agent(
    db=SqliteDb(db_file="agent.db"),                  # sessions: local
    memory_manager=MemoryManager(db=MemorySyncDb()),  # memories: MemorySync
    update_memory_on_run=True,   # extract + store memories after every run
    user_id="customer-42",
)
agent.run("I prefer teal dashboards and window seats")
agent.run("Which color should the new chart use?")   # remembers
```

The API key comes from the `MEMORYSYNC_API_KEY` environment variable (or
`MemorySyncDb(api_key=...)`). `add_memories_to_context` auto-enables when a
memory manager is set, so recalled memories are injected into context on
every run.

## Why this instead of the Mem0 toolkit?

Agno's ecosystem has one other memory SaaS: the `Mem0Tools` toolkit shipped
in agno core, plus a cookbook. Verified against their source:

| Behavior | Mem0 (`Mem0Tools` + cookbook) | **agno-memorysync** |
|---|---|---|
| Integration depth | LLM tools — the model must *decide* to recall | native `MemoryManager` backend — automatic extraction + injection |
| Async agents | ✗ sync client **blocks the event loop** | sync db with bounded budget + a true `AsyncBaseDb` twin |
| Missing user id | returns error **strings as tool output** | deterministic `default` namespace, never cross-user |
| Retries / re-runs | cookbook: *"comment out this line after running once"* | one value per memory id — an unchanged memory is never stored or extracted twice |
| Memory snapshot | cookbook injects a **static** snapshot fetched at construction | fresh recall every run |
| Agent scoping | `search`/`get_all` ignore `agent_id` | `agent_id` / `team_id` stored and filterable |
| Semantic search | — | **server-side similarity search** via `search_content` (agno itself has only last_n / first_n / an extra LLM round-trip) |
| Whole-store wipe | — | `clear_memories()` **refuses**; per-user wipe is explicit |

## The memory-only contract

`BaseDb` covers sessions, evals, knowledge, metrics, and traces too.
`MemorySyncDb` implements **every memory method for real** and makes every
other surface raise `MemorySyncMemoryOnlyError` with the fix in the message —
a backend that silently pretended to store sessions would lose them.

```python
Agent(
    db=SqliteDb(...),                                # sessions, evals, ...
    memory_manager=MemoryManager(db=MemorySyncDb())  # memories only
)
```

## Where memories are stored

- Each memory is one value in MemorySync's state store for agno, keyed by
  its `memory_id`, holding the `UserMemory` exactly as the manager wrote it:
  text, topics, input, feedback, agent and team ids, timestamps. A memory id
  always has exactly one current value.
- The memory's text is also sent to fact extraction. The extracted facts
  (tagged with the memory's `agent_id` / `team_id`) are recallable from
  every MemorySync surface, and they are what the MemorySync dashboard
  shows.
- Updating a memory replaces the facts extracted from its previous version.
  Deleting a memory deletes its facts too, so nothing resurfaces through
  recall.

## Semantic recall

```python
db = MemorySyncDb()
memories = db.get_user_memories(
    user_id="customer-42",
    search_content="what does the user like to eat?",  # ranked server-side
    limit=5,
)
```

Agno's built-in `search_user_memories` offers `last_n`, `first_n`, and
`agentic` (an extra LLM call that reads *all* memories). `search_content`
here ranks the user's memories by similarity on the MemorySync side — no
LLM round-trip, best match first.

## Async agents

```python
from agno_memorysync import AsyncMemorySyncDb

manager = MemoryManager(db=AsyncMemorySyncDb())
# MemoryManager awaits AsyncBaseDb natively on Agent.arun paths.
```

## Delete semantics — designed against data loss

| Call | What happens |
|---|---|
| `delete_user_memory(id, user_id=...)` | deletes that memory and the facts extracted from it; an already-gone id is an idempotent no-op; a FAILED delete raises |
| `delete_user_memories([ids], user_id=...)` | bulk variant |
| `clear_memories()` | **always raises** — a nullary everything-wipe is how accounts get destroyed |
| `forget_user_memories(user_id)` | the explicit, scoped, loud per-user wipe (memories and their facts); returns the number removed |

## Failure policy

- **Reads fail open** under a hard budget (`recall_timeout`, default 1.2 s):
  a slow or down memory service degrades to no memories, never a stalled or
  crashed turn.
- **Writes fail open by default** (`fail_open_writes=True`): post-run
  extraction never turns a successful agent run into a failure. The failure
  is logged loudly and the call returns `None` — an honest contract value.
  Set `fail_open_writes=False` to raise instead.
- **Deletes are never fail-open.** A delete that did not happen raises.

## Configuration

| Parameter | Default | Meaning |
|---|---|---|
| `api_key` | `MEMORYSYNC_API_KEY` env var | API key |
| `base_url` | `https://api.memorysync.io` | Override for staging |
| `project_id` | – | Optional `X-Project-ID` header |
| `tenant_id` | auto-discovered | Skip discovery |
| `default_user_id` | `"default"` | Namespace when agno passes `user_id=None` |
| `recall_timeout` | `1.2` | Hard read budget (seconds) |
| `fail_open_writes` | `True` | Post-run extraction failures log instead of raise |
| `source` | `"agno"` | Not used for storage: memories always live under the `agno` source, so every install shares them |

## Multimodal memories

Images flow to the model (`Agent.run(images=[Image(...)])`), the model's
understanding is extracted by `MemoryManager` as text, and the memory lands
here with its source `input` — image-derived memories work through agno's
NATIVE pipeline. (The Mem0 docs demo bypasses agno's memory system entirely
and pushes raw base64 into their cloud.)

## Tests

```bash
pip install -e . agno pytest pytest-asyncio "httpx>=0.25,<1"
python -m pytest tests -q   # 60+ checks against the real agno at latest
```

The suite drives the real `MemoryManager` and a REAL `Agent` run (stub
model, local session db) against a mock of the MemorySync state API, and
reproduces each named competitor bug as a regression test.

## License

MIT © MemorySync.
