Agno Memory
Give Agno agents long-term memory with one constructor argument. MemorySyncDb plugs into Agno’s own MemoryManager: agno’s native pipeline extracts memories after every run, they persist to MemorySync, and they’re injected back into context automatically — with real semantic recall and an async-native twin.
How it works
- Install
agno-memorysyncfrom PyPI. - Keep your sessions wherever they already live (SQLite, Postgres…).
- Point the agent’s
MemoryManagerat aMemorySyncDb— memories now live in MemorySync. - Done — agno extracts memories after each run and injects them on the next. No per-turn code.
Before you start
- A MemorySync API key. Create one in the dashboard under Settings → API Keys, inside a project.
- Python 3.10+ with
agno2.8 or newer.
Install
One package:
pip install agno-memorysync
Quickstart
Step 1 — split sessions from memories
Agno’s db= holds sessions; the MemoryManager’s db= holds memories. Keep sessions local, put memories in MemorySync:
from agno.agent import Agentfrom agno.db.sqlite import SqliteDbfrom agno.memory import MemoryManagerfrom agno_memorysync import MemorySyncDbagent = Agent(db=SqliteDb(db_file="agent.db"), # sessions: localmemory_manager=MemoryManager(db=MemorySyncDb()), # memories: MemorySyncupdate_memory_on_run=True, # extract + store after every runuser_id="customer-42", # whose memories these are)
Step 2 — run it
Tell the agent something in one run; it recalls it in the next — even after a restart:
agent.run("I prefer teal dashboards and window seats")agent.run("Which color should the new chart use?") # remembers: teal
A memory-only db — on purpose
Agno’s BaseDb interface also covers sessions, evals, knowledge, metrics and traces. 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. Pair it with any session db, as in the quickstart.
Each memory is one value in MemorySync’s state store for agno, keyed by its memory_id and 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, and deleting a memory deletes its facts too, so nothing resurfaces through recall.
Real semantic recall
Agno’s own search_user_memories offers last_n, first_n, and agentic — the last is an extra LLM round-trip that reads all memories and picks ids. search_content here ranks the user’s memories by similarity on the MemorySync side: no LLM call, best match first.
db = MemorySyncDb()memories = db.get_user_memories(user_id="customer-42",search_content="what does the user like to eat?", # ranked server-sidelimit=5,)
Async agents
For Agent.arun paths, use the async twin — the MemoryManager awaits AsyncBaseDb natively. It is the only async-native memory backend in the agno ecosystem; the sync db never blocks the event loop either, because every read is bounded by the budget.
from agno_memorysync import AsyncMemorySyncDbmanager = MemoryManager(db=AsyncMemorySyncDb())
Deletes that cannot nuke an account
| Call | What happens |
|---|---|
delete_user_memory(id, user_id=...) | Deletes that memory and the facts extracted from it; already-gone id is an idempotent no-op; a FAILED delete raises |
delete_user_memories([ids], user_id=...) | Bulk variant, same guarantees |
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 |
Writes fail open by default with loud logs and an honest None return, so post-run extraction can never turn a successful agent run into a failure.
Configuration
| Parameter | Default | Meaning |
|---|---|---|
api_key | MEMORYSYNC_API_KEY env var | API key |
default_user_id | default | Namespace when agno passes user_id=None |
recall_timeout | 1.2 | Hard read budget in seconds |
fail_open_writes | True | Extraction failures log instead of raise |
source | agno | Not used for storage: memories always live under the agno source, so every install shares them |
Quotas and plan limits
Hitting a monthly plan limit never breaks a run. On free and paid plans, over-limit writes are accepted without storing and reads return empty — the agent keeps working. Evaluation keys instead surface a truthful 429, so you find out during testing, not in production.
Troubleshooting
- Memories stored but never appear in replies — you passed a verbatim
system_message=string, which replaces agno’s built system prompt (where memories are injected). Useinstructions=for your prompt text instead. - `MemorySyncMemoryOnlyError` — you pointed a session/knowledge/eval surface at this db. Keep those on your local db (see the quickstart split); only
memory_manager=getsMemorySyncDb. - Recall returns nothing right after storing — extraction is asynchronous; give it a moment. Also confirm both runs use the same
user_id. - A run went without memories once — that’s the read budget working: a slow lookup is skipped rather than delaying the run.
- `clear_memories()` raised — deliberate: a no-argument everything-wipe is refused. Use
forget_user_memories(user_id)for an explicit per-user wipe.
Supported versions
| Surface | Requires | Verified on |
|---|---|---|
agno-memorysync 1.1.0 | agno 2.8+ (Python 3.10+) | 63 CI checks against the latest agno: the REAL MemoryManager add/replace/delete flows and a REAL Agent run (stub model, local session db) asserting MemorySync memories reach the model context — plus the memory-only scope contract, semantic search_content, one-value-per-memory idempotency, facts replaced on update and deleted with their memory, the guarded nullary clear, the AsyncBaseDb twin, the 1.2s budget, and both monthly-quota server modes |
How it compares
| Mem0 (`Mem0Tools` + cookbook) | Zep (`ZepTools` + cookbook) | Supermemory | MemorySync | |
|---|---|---|---|---|
| Integration depth | ✗ LLM tools — the model must *decide* to recall | ✗ LLM tools + a hardcoded time.sleep(10) for indexing | — nothing at all | ✓ native MemoryManager backend — automatic extraction + injection |
| Async agents | ✗ sync client blocks the event loop | ✗ sync | — | ✓ bounded-budget sync db + 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 freshness | ✗ cookbook injects a static snapshot fetched at construction | ✗ same static-dependencies pattern | — | ✓ 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 call) |
| Whole-store wipe | — | — | — | ✓ clear_memories() refuses; per-user wipe is explicit |