Metadata-Version: 2.5
Name: ag2-memorysync
Version: 1.0.2
Summary: MemorySync for AG2 (AutoGen classic): an automatic memory loop on ConversableAgent's hook system — budgeted recall injection, duplicate-proof both-side capture, and a deadlock-free sync bridge.
Project-URL: Homepage, https://docs.memorysync.io/guides/ag2
Project-URL: Documentation, https://docs.memorysync.io/guides/ag2
Project-URL: Repository, https://github.com/memorysyncio/memorysync-plugins
Author-email: MemorySync <support@memorysync.io>
License-Expression: MIT
Keywords: ag2,agents,autogen,conversableagent,long-term-memory,memory,memorysync
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
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
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.25
Description-Content-Type: text/markdown

# ag2-memorysync

[MemorySync](https://memorysync.io) for [AG2](https://github.com/ag2ai/ag2-classic)
(the classic AutoGen `ConversableAgent` framework, `pip install autogen`):
an automatic memory loop — recall injected before every reply, both sides
persisted, zero extra code per turn.

```bash
pip install ag2-memorysync
```

## Quick start

```python
from autogen import ConversableAgent
from ag2_memorysync import MemorySyncCapability

assistant = ConversableAgent("assistant", llm_config=...)

memory = MemorySyncCapability(
    api_key="ms_...",              # or MEMORYSYNC_API_KEY
    user_id="customer-42",         # required — who these memories belong to
    session_id="support-chat",     # scopes the transcript
)
memory.add_to_agent(assistant)     # that's the whole integration
```

From then on every incoming user message is persisted and enriched with
recalled context, and every outgoing reply is persisted — through AG2's
own hook system (`process_last_received_message` +
`process_message_before_send`).

## Why this one

| | Mem0 | Zep (`zep-ag2`) | **MemorySync** |
| --- | --- | --- | --- |
| AG2 adapter exists | ✗ docs show an AutoGen-0.2 recipe with placeholder model names | ✓ | ✓ |
| Multi-agent duplication | — | ✗ **documented bug**: two attached agents store every utterance twice with conflicting roles | ✓ cross-hook dedup registry + idempotency seeds — one utterance, one row (by test) |
| Sync→async bridge | — | per-call event-loop spin; documented deadlock caveat under asyncio | ✓ one persistent background loop; never touches the caller's loop (asyncio-driven chats pass, by test) |
| Recall latency budget | — | ✗ none | ✓ hard 1.2s default — the reply is never late |
| Framework pin | — | ✗ `ag2<1` — breaks on the v1 rewrite | ✓ **zero framework dependency** (duck-typed attach; works with whichever classic distribution you installed) |
| Injected context re-stored? | — | system-message mutation, last-write-wins | ✓ hook output feeds the LLM only; the ORIGINAL text is what persists |

## Semantics worth knowing

- **The reply is never stalled and never broken.** Recall blocks at most
  `recall_timeout` (default 1.2s); persistence is fire-and-forget off
  the hot path. Outages and quota exhaustion degrade to "no memories
  this turn".
- Turns persist under the `ag2::<session>` session scope with
  deterministic idempotency seeds — retries and multi-agent echoes
  converge on one write. Each turn is distilled server-side into
  durable facts; no raw transcript rows land in the user's memory.
- **Roles follow the speaker, not the hook.** A message sent by an agent
  that speaks for a person — a `UserProxyAgent`, or any agent whose
  `human_input_mode` is not `"NEVER"` — is persisted as a human turn even
  when the capability is attached to that agent too; every other agent's
  outgoing message is an ai turn. Only human turns become facts, so this
  is what makes "attach it to both sides" store the person's words as
  memory (1.0.2; 1.0.1 labelled them `ai` when the proxy carried the
  capability, and they left no memory).
- Tool/function messages are never persisted.
- `register_memory_tools(memory, caller=..., executor=...)` adds
  `search_memory` + `save_memory` tools (the caller needs an
  `llm_config`, as usual for AG2 tools).
- `memory.flush()` waits for in-flight writes (shutdown/tests);
  `memory.close()` flushes and releases the HTTP client.

## Configuration

| Parameter | Default | Meaning |
| --- | --- | --- |
| `user_id` | — (required) | End user the memories belong to |
| `session_id` | `"default"` | Transcript scope |
| `top_k` | `5` | Memories considered per turn |
| `recall_timeout` | `1.2` | Hard recall budget, 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 |

## Development

```bash
pip install -e . "autogen[openai]" pytest
python -m pytest tests -q      # 25 tests through REAL ConversableAgent chats
```

The suite includes a reproduction of zep-ag2's documented multi-agent
double-store scenario (we store once), a `UserProxyAgent` + assistant chat
with the capability on both sides (the person's words persist as a human
turn), and a chat driven from inside `asyncio.run()` (no deadlock).

## License

MIT
