Metadata-Version: 2.5
Name: ag2-memorysync
Version: 1.1.0
Summary: MemorySync for AG2 (AutoGen classic): an automatic memory loop on ConversableAgent's hook system — budgeted recall injection, duplicate-proof capture of the user's turns for fact extraction, 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, the user's
turns captured for fact extraction, 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",     # tags the facts from this chat
)
memory.add_to_agent(assistant)     # that's the whole integration
```

From then on every incoming user message is enriched with recalled
context and sent to MemorySync, which extracts the durable facts it
contains — through AG2's own hook system (`process_last_received_message`
+ `process_message_before_send`). The agent's own replies are not sent:
assistant replies are not stored as memories.

## 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 — each utterance handled once; an agent's reply is never sent as the person's words (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-captured? | — | system-message mutation, last-write-wins | ✓ hook output feeds the LLM only; the ORIGINAL text is what is sent |

## Semantics worth knowing

- **The reply is never stalled and never broken.** Recall blocks at most
  `recall_timeout` (default 1.2s); sending a turn is fire-and-forget off
  the hot path. Outages and quota exhaustion degrade to "no memories
  this turn".
- **User turns are sent to fact extraction; only durable facts are
  stored.** Each user turn goes to MemorySync as plain text with
  `role: "user"` under the `ag2::<session>` session scope. The server
  extracts the durable facts it contains and stores only those, tagged
  with `session_id: "ag2::<session>"`; filler such as "ok" or "thanks"
  yields none, and the turn text itself is not stored. Deterministic
  idempotency seeds let the server recognise a retry of the same turn and
  not extract it twice.
- **Roles follow the speaker, not the hook.** A message sent by an agent
  that speaks for a person — a `UserProxyAgent`, an agent whose
  `human_input_mode` is `"ALWAYS"`, or a `"TERMINATE"`-mode agent with no
  model — is the user's turn, even when the capability is attached to
  that agent too. Every other agent's outgoing message is an assistant
  reply and is not sent. That includes a plain
  `ConversableAgent(..., llm_config=...)`: its default
  `human_input_mode="TERMINATE"` only asks a person when the chat ends,
  so its messages come from the model.
- **Attach it to both sides if you like.** A process-wide registry
  records what each attached agent just handled, so the person's words
  are sent once — not again by the receiving side — and an assistant's
  reply arriving at the person's proxy is never sent as theirs.
- Tool/function messages are never sent.
- `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 sends (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"` | Session scope: the `session_id` (`ag2::<session>`) carried by the facts from this chat |
| `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. The sent side carries only what an agent speaking for a person says, so `"sent"` belongs on the person's `UserProxyAgent`; on an assistant it sends nothing |

## Development

```bash
pip install -e . "autogen[openai]" pytest "httpx>=0.25,<1"
python -m pytest tests -q      # 31 tests through REAL ConversableAgent chats
```

The suite includes a reproduction of zep-ag2's documented multi-agent
double-store scenario (each utterance handled once), a `UserProxyAgent` +
assistant chat with the capability on both sides (the person's words are
sent once; the assistant's replies are not sent), and a chat driven from
inside `asyncio.run()` (no deadlock).

## License

MIT
