Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.maximem.ai/llms.txt

Use this file to discover all available pages before exploring further.

Plug Synap into NVIDIA NeMo Agent Toolkit (NAT) as a first-class MemoryEditor. NAT workflows that declare a memory backend in YAML — or instantiate one programmatically — now get persistent, semantically searchable, per-user memory.

Overview

This guide shows how to add Synap to a NAT workflow to build pipelines that:
  • Persist MemoryItem objects across workflow executions
  • Retrieve memories via semantic search inside any NAT step
  • Be declared in NAT’s YAML config without writing additional integration code
The Synap NAT integration ships three exports — the editor itself, a registration decorator, and a one-shot factory.
ExportPurpose
SynapMemoryEditorImplements nat.memory.interfaces.MemoryEditor for NAT workflows
@register_memoryDecorator that registers the editor under a YAML-referenceable name
synap_memory_clientFactory that builds a ready-to-use SynapMemoryEditor from config

Setup

Install the package alongside NAT:
pip install maximem-synap-nemo-agent-toolkit nat
Configure your API key. Generate one from the Synap Dashboard.
.env
SYNAP_API_KEY=synap_your_key_here
Initialize the SDK once at application startup:
from maximem_synap import MaximemSynapSDK

sdk = MaximemSynapSDK()
await sdk.initialize()
Alternatively, use the synap_memory_client factory below to skip the SDK setup — it initializes Synap internally. See SDK Initialization for the full lifecycle and configuration options.

Basic integration

The smallest useful integration constructs a SynapMemoryEditor and uses it directly:
from nat.memory.models import MemoryItem
from synap_nemo_agent_toolkit import SynapMemoryEditor

editor = SynapMemoryEditor(
    sdk=sdk,
    customer_id="acme",    # optional — required for B2B instances
    mode="accurate",       # "fast" or "accurate"
)

# Store memories — user_id travels on the item, not the editor
await editor.add_items([
    MemoryItem(user_id="alice", memory="Prefers concise bullet-point summaries", tags=["preference"]),
    MemoryItem(user_id="alice", memory="Working on Q3 roadmap planning", tags=["project"]),
])

# Search memories
results = await editor.search("communication preferences", top_k=5, user_id="alice")
for item in results:
    print(item.memory, item.score)
Notice that user_id is supplied per item and per query — a single SynapMemoryEditor instance serves all users in the workflow. customer_id is set once at construction.

Core concepts

MemoryEditor interface

SynapMemoryEditor implements the full MemoryEditor protocol. NAT workflows that accept a MemoryEditor work without modification:
editor = SynapMemoryEditor(sdk=sdk, customer_id="acme", mode="accurate")
MethodBehavior
add_items(items)Batch-ingest MemoryItem objects into Synap
search(query, top_k, user_id)Semantic search; returns scored MemoryItem list
update_items(items)Update existing memories by ID
get_items(user_id, limit)Retrieve all memories for a user
The two retrieval modes trade latency against comprehensiveness:
fastaccurate
Latency50-100ms200-500ms
SearchVector similarityVector + graph + re-ranking
Best forReal-time chatMulti-entity queries
Reads degrade gracefullysearch and get_items return empty results and log an error on Synap outages. Writes surface failuresadd_items and update_items raise SynapIntegrationError so workflows know if persistence failed.

Registration for YAML configs

NAT lets you declare memory backends in YAML. Use @register_memory to make SynapMemoryEditor resolvable by name:
from synap_nemo_agent_toolkit import register_memory, SynapMemoryEditor

@register_memory("synap")
class _RegisteredSynap(SynapMemoryEditor):
    pass
After registration, reference it in any NAT workflow config:
memory:
  type: synap
  config:
    api_key: ${SYNAP_API_KEY}
    mode: accurate
    customer_id: acme
NAT resolves type: synap to the registered class and instantiates it with the config block.

Factory function

For programmatic setups outside YAML, synap_memory_client builds a ready-to-use editor and initializes the SDK internally — no separate lifecycle to manage:
import os
from synap_nemo_agent_toolkit import synap_memory_client

editor = synap_memory_client(
    api_key=os.environ["SYNAP_API_KEY"],
    customer_id="acme",
    mode="accurate",
)
Use this when you want a single function call to produce a configured editor — especially useful in scripts and notebooks.

Complete example: NAT workflow with persistent memory

The pattern below sets up a workflow with Synap-backed memory at startup, ingests a batch of memories, and runs a recall query:
from nat.memory.models import MemoryItem
from synap_nemo_agent_toolkit import SynapMemoryEditor


async def setup_memory(sdk, customer_id: str | None = None) -> SynapMemoryEditor:
    editor = SynapMemoryEditor(sdk=sdk, customer_id=customer_id, mode="accurate")

    # Seed the editor with starting memories for known users
    await editor.add_items([
        MemoryItem(user_id="alice", memory="Lead engineer on Project Phoenix", tags=["role"]),
        MemoryItem(user_id="alice", memory="Prefers email for async updates", tags=["preference"]),
        MemoryItem(user_id="bob", memory="QA lead, focuses on flaky tests", tags=["role"]),
    ])

    return editor


async def recall(editor: SynapMemoryEditor, user_id: str, query: str) -> list[str]:
    results = await editor.search(query, top_k=5, user_id=user_id)
    return [r.memory for r in results]


# Usage
editor = await setup_memory(sdk, customer_id="acme")
alice_prefs = await recall(editor, user_id="alice", query="how does alice prefer to be contacted?")
# → ["Prefers email for async updates", ...]
Three things to notice in this pattern:
  1. One editor, many users. user_id travels on each MemoryItem and each search call, so the editor is shared.
  2. customer_id is the tenant boundary. All users sharing an editor are inside the same customer_id — build a separate editor per tenant for multi-tenant services.
  3. Mode is fixed at construction. Set mode="fast" for low-latency NAT steps; "accurate" for higher-recall lookups.

Advanced patterns

Multi-tenant scoping

SynapMemoryEditor takes customer_id at construction; user_id is supplied per call. customer_id is required on B2B Synap instances and ignored on single-tenant ones. See Memory Scopes.
# Single-tenant
editor = SynapMemoryEditor(sdk=sdk)

# Multi-tenant — one editor per customer
editor_acme = SynapMemoryEditor(sdk=sdk, customer_id="acme")
editor_initech = SynapMemoryEditor(sdk=sdk, customer_id="initech")

Choosing between SDK-managed and factory-managed lifecycles

  • Use SynapMemoryEditor(sdk=...) when your application owns the SDK lifecycle (recommended for production — you control init/shutdown).
  • Use synap_memory_client(api_key=...) for scripts, notebooks, or YAML-driven workflows where you’d rather not manage the SDK explicitly.

Failure semantics

The integration follows the Synap-wide contract:
  • search and get_items degrade gracefully — return empty lists and log an error if Synap is unreachable.
  • add_items and update_items surface failures — raise SynapIntegrationError so the workflow and caller know persistence failed.
This is by design: read failures shouldn’t break a workflow step mid-flight, but silent write failures would corrupt the memory pool.

Next steps

Semantic Kernel

Plugin for Microsoft Semantic Kernel.

Pydantic AI

Type-safe deps and tools for Pydantic AI.

Memory Scopes

How user_id and customer_id interact across reads and writes.

Ingestion

Direct ingestion API for pipelines that need finer control than add_items.