Metadata-Version: 2.5
Name: nat-memorysync
Version: 1.0.0
Summary: MemorySync for the NVIDIA NeMo Agent Toolkit: a MemoryEditor plugin with budgeted per-fact recall, duplicate-proof verbatim persistence, bleed-proof multi-tenant scoping, and session-scoped deletes.
Project-URL: Homepage, https://docs.memorysync.io/guides/nemo-agent-toolkit
Project-URL: Documentation, https://docs.memorysync.io/guides/nemo-agent-toolkit
Project-URL: Repository, https://github.com/Rafay121/memorysync-plugins
Author-email: MemorySync <support@memorysync.io>
License-Expression: MIT
Keywords: agents,aiqtoolkit,long-term-memory,memory,memorysync,nemo-agent-toolkit,nvidia,nvidia-nat
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
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.14,>=3.11
Requires-Dist: httpx<1,>=0.25
Requires-Dist: nvidia-nat-core<2,>=1.5
Description-Content-Type: text/markdown

# nat-memorysync

MemorySync memory backend for the [NVIDIA NeMo Agent Toolkit](https://github.com/NVIDIA/NeMo-Agent-Toolkit) (`nvidia-nat`).

Registers a `memorysync_memory` client that plugs into workflow YAML as a
`memory:` section entry — usable from the toolkit's built-in `add_memory` /
`get_memory` tools, from the automatic `auto_memory_agent` wrapper, and from
any custom function that requests a memory client from the Builder.

```bash
pip install nat-memorysync
```

Requires Python 3.11+ (the toolkit's own floor). Installing this package pulls
`nvidia-nat-core`; install `nvidia-nat` (or the plugin subpackages you need)
for the full toolkit.

## Why this instead of the in-repo editors?

The toolkit ships example editors for Mem0 and Zep. Both have sharp edges we
designed against:

| Behavior | Mem0 (in-repo) | Zep (in-repo) | **nat-memorysync** |
|---|---|---|---|
| `search()` without `user_id` | bare `KeyError` | n/a (thread-scoped) | `ValueError` naming the kwarg |
| Multi-user isolation with no conversation id | per-call `user_id` | **all users share `"default_zep_thread"`** | rows always keyed by item `user_id` — bleed impossible |
| Your `metadata` dict after `add_items` | **mutated** (keys popped out) | untouched | untouched (copy-first, tested) |
| Search result shape | items, **scores discarded** | **one joined text blob** | one `MemoryItem` per fact, `similarity_score` populated |
| `remove_items()` with no kwargs | **silent no-op** | deletes current thread | raises — refuses to guess |
| Delete blast radius | whole user | whole thread | session-scoped by default; whole user requires explicit `scope="user"` |
| Slow/down memory backend | blocks the turn | blocks the turn | 1.2 s recall budget, fail-open both directions |
| Retried writes | duplicated | duplicated | deterministic idempotency seeds — retries converge on one row |

## Wiring mode 1 — explicit memory tools

The agent decides when to store and when to recall:

```yaml
memory:
  saas_memory:
    _type: memorysync_memory        # key comes from MEMORYSYNC_API_KEY env var

functions:
  add_memory:
    _type: add_memory
    memory: saas_memory
    description: Save any user preference or fact for later conversations.
  get_memory:
    _type: get_memory
    memory: saas_memory
    description: Recall previously saved user preferences and facts.

workflow:
  _type: react_agent
  tool_names: [add_memory, get_memory]
  llm_name: my_llm
```

## Wiring mode 2 — automatic memory (`auto_memory_agent`)

No tools, no prompt changes — every turn is stored and every prompt is
enriched automatically (requires `nvidia-nat-langchain`):

```yaml
memory:
  saas_memory:
    _type: memorysync_memory

workflow:
  _type: auto_memory_agent
  augmented_fn: my_actual_workflow
  memory: saas_memory
```

`search` runs inside a hard 1.2 s budget here, so automatic memory can never
stall a turn.

## Builder API (Python)

```python
from nat.builder.workflow_builder import WorkflowBuilder
from nat_memorysync import MemorySyncMemoryConfig

async with WorkflowBuilder() as builder:
    await builder.add_memory_client("saas_memory", MemorySyncMemoryConfig())
    editor = await builder.get_memory_client("saas_memory")

    from nat.memory.models import MemoryItem
    await editor.add_items([
        MemoryItem(
            conversation=[{"role": "user", "content": "I prefer teal dashboards"}],
            user_id="customer-1",
            metadata={"plan": "pro"},
        )
    ])
    items = await editor.search("dashboard preferences", top_k=5, user_id="customer-1")
    for it in items:
        print(it.similarity_score, it.memory)
```

## Configuration

All fields are optional except the API key (env var or config field):

| YAML field | Default | Purpose |
|---|---|---|
| `api_key` | `MEMORYSYNC_API_KEY` env var | API key — keep it in the env var so YAML stays credential-free |
| `base_url` | `https://api.memorysync.io` | Override for self-hosted / staging |
| `project_id` | – | Optional `X-Project-ID` header |
| `top_k` | `5` | Default memories per search |
| `recall_timeout` | `1.2` | Hard recall budget (seconds); slow backend degrades to no memories |
| `min_query_chars` | `8` | Skip recall for shorter queries |
| `source` | `nat` | Source label on stored turns |

`MemoryBaseConfig` + `RetryMixin` knobs (`num_retries`,
`retry_on_status_codes`, …) work too — retries are safe because every write
carries a deterministic idempotency seed.

## Editor semantics

- **`add_items(items)`** — each `MemoryItem.conversation` is stored through
  MemorySync's extraction pipeline (facts, dedup, decay), scoped to that
  item's `user_id`. `metadata` keys ride along; `metadata.ignore_roles`
  filters roles out (e.g. `["assistant"]` stores only user turns). Items
  whose extraction fails are logged and skipped — a partial batch never
  raises mid-turn.
- **`search(query, top_k=..., user_id=...)`** — semantic recall, one
  `MemoryItem` per fact with `similarity_score`. `user_id` is required
  (loud `ValueError`, not a `KeyError`).
- **`remove_items(user_id=...)`** — deletes this adapter's session rows for
  the user. Add `memory_id="..."` for one row, or `scope="user"` to wipe the
  user's entire memory (explicit opt-in). No kwargs → `ValueError`.

Session scope comes from the toolkit's `Context.get().conversation_id`
ContextVar when set (`nat::<conversation_id>`), else `nat::default` — but
rows are always additionally keyed by `user_id`, so an unset conversation id
can never mix users.

## Tests

```bash
pip install -e . nvidia-nat-core langchain-core pytest pytest-asyncio "httpx>=0.25,<1"
pytest tests -q   # 26 tests
```

The suite exercises the real `WorkflowBuilder`, NVIDIA's real
`add_memory`/`get_memory` tool functions driving this editor end to end, plus
named regression tests for every competitor bug in the table above.

## License

MIT © MemorySync.
