AG2 Memory
An automatic memory loop for AG2’s classic ConversableAgent framework: one attach call persists both sides of every conversation and injects recalled context before every reply — duplicate-proof in multi-agent chats, deadlock-free under asyncio, zero framework dependencies. Install ag2-memorysync from PyPI.
One attach call
pip install ag2-memorysync
AG2-classic has no memory protocol — ConversableAgent.register_hook is its only per-turn seam, and every hook must be synchronous. The capability registers two hooks: incoming messages are persisted and returned with a recalled-context prefix (which feeds the LLM only — the stored transcript keeps the original text), and outgoing replies are persisted on send.
| Mem0 | Zep (`zep-ag2`) | Supermemory | MemorySync | |
|---|---|---|---|---|
| AG2 adapter exists | ✗ docs show an AutoGen-0.2 recipe with placeholder model names | ✓ (5 weeks old) | ✗ nothing | ✓ |
| Multi-agent duplication | — | ✗ documented bug: every utterance stored twice with conflicting roles | — | ✓ dedup registry + seeds — one utterance, one row, with a reproduction test |
| Sync→async bridge | — | ✗ per-call loop spin; documented deadlock caveat under asyncio | — | ✓ one persistent bridge loop; asyncio-driven chats pass, by test |
| Recall latency budget | — | ✗ none | — | ✓ hard 1.2s default |
| Framework coupling | — | ✗ pins ag2<1 — breaks on the v1 rewrite | — | ✓ zero framework dependencies (duck-typed attach) |
| Extra LLM calls per turn | Teachability needs an analyzer-LLM call every turn | none | — | none |
The automatic loop, precisely
| Hook | Fires | What the capability does |
|---|---|---|
process_last_received_message | inside generate_reply, before the LLM | Persists the ORIGINAL incoming text (fire-and-forget), recalls context under the budget, returns the text with a context prefix — AG2 hands the prefixed text to the LLM but never writes it back to the stored conversation |
process_message_before_send | on send/a_send, after the reply is generated | Persists the outgoing reply (fire-and-forget), returns the message untouched |
Tool and function messages are never persisted. Turns store verbatim under the ag2::<session> transcript scope with deterministic idempotency seeds — separate history, same shared user memories as every other MemorySync surface.
The deadlock-free bridge
All async work rides one persistent background event loop per process. Hooks submit coroutines with run_coroutine_threadsafe and block — bounded by the recall budget — on a plain future. The caller’s thread and event loop are never touched, so a chat driven from inside asyncio.run(...) completes (a test proves it). Persistence is fire-and-forget off the hot path; memory.flush() waits for in-flight writes at shutdown, and memory.close() flushes and releases the HTTP client.
Memory as agent tools
from ag2_memorysync import register_memory_tools# caller decides to use tools; executor runs them (AG2’s standard split)register_memory_tools(memory, caller=assistant, executor=user_proxy)
Both tools are synchronous — they ride the same bridge, so they work in plain initiate_chat without event-loop concerns. The caller agent needs an llm_config, as usual for AG2 tool registration.
Configuration
| Parameter | Default | Meaning |
|---|---|---|
user_id | — (required) | End user the memories belong to |
session_id | default | Transcript scope: ag2::<session> |
top_k | 5 | Memories considered per turn |
recall_timeout | 1.2 | Hard recall budget in seconds |
min_prompt_chars | 8 | Skip recall for trivial messages |
context_template | built-in | {context} placeholder; brace-safe .replace rendering |
capture | both | received / sent to capture one side only |
Supported versions
| Surface | Requires | Verified on |
|---|---|---|
ag2-memorysync 1.0.0 | The classic ConversableAgent framework (pip install autogen, Python 3.10+). The rewritten pip install ag2 v1 has no hook system yet — a future adapter target. | 22 CI checks against the latest ag2-classic: REAL two-agent initiate_chat conversations (both-side capture with seeds, context reaching the LLM but never the transcript), the zep-ag2 double-store reproduction (one row), a chat driven from inside asyncio.run() (no deadlock), the recall budget vs a slow backend, and both monthly-quota server modes |