Migrate to NAMS
How to port an existing direct-bolt project to the hosted Neo4j Agent Memory Service (NAMS) backend, including the full NotSupportedError reference table.
v0.4 introduces NAMS as an alternative backend behind the same MemoryClient. The migration is additive — your existing v0.3.x code keeps working unchanged on bolt; switching to NAMS usually means one config change.
TL;DR
export MEMORY_API_KEY=nams_xxxxxxxxxxxxxxxx
If your code uses MemoryClient() without explicit settings, that’s the whole migration. The _resolve_backend validator picks up MEMORY_API_KEY and switches to NAMS automatically.
If you have explicit MemorySettings(neo4j=Neo4jConfig(…)), swap it:
# Before (bolt)
settings = MemorySettings(neo4j=Neo4jConfig(password=SecretStr("...")))
# After (NAMS)
settings = MemorySettings(
backend="nams",
nams=NamsConfig(api_key=SecretStr("nams_...")),
)
Step-by-step
2. Provision an API key
Get one at memory.neo4jlabs.com. Keys start with nams_.
3. Decide on env-vs-explicit config
Two equally valid patterns:
-
Env-driven (recommended for apps that deploy via env vars):
bash export MEMORY_API_KEY=nams_xxx # optional: export MEMORY_ENDPOINT=https://your-private-deployment/v1 -
Explicit (recommended for libraries embedding
neo4j-agent-memory):python settings = MemorySettings( backend="nams", nams=NamsConfig(api_key=SecretStr("nams_xxx")), )
4. Audit your code for bolt-only calls
Search your codebase for any of these patterns — they need replacement or guards:
| Pattern | Replace with |
|---|---|
|
|
|
Use the appropriate higher-level method ( |
|
Remove; NAMS scopes per-conversation via |
|
Remove; NAMS commits synchronously. |
|
Remove; NAMS handles deduplication server-side. |
|
Cannot port; consider keeping that pipeline on bolt. |
|
Not available on NAMS; keep on bolt or pre-geocode at ingest. |
|
Removed by NAMS (server handles dedup automatically). |
|
Use |
5. Migrate raw Cypher calls
client.graph.execute_read is deprecated on bolt and raises NotSupportedError on NAMS. The portable replacement is client.query.cypher:
# Before (deprecated on bolt, raises on NAMS):
results = await client.graph.execute_read(
"MATCH (e:Entity) RETURN e.name AS name LIMIT 10"
)
# After (works on both backends):
results = await client.query.cypher(
"MATCH (e:Entity) RETURN e.name AS name LIMIT 10"
)
Both validate read-only client-side. Writes (CREATE, MERGE, DELETE, SET, REMOVE, etc.) raise ValueError.
client.graph.execute_write is bolt-only and stays without deprecation — there is no NAMS analog. Use higher-level methods or keep the relevant codepath on bolt.
Full NotSupportedError reference
What raises on each backend, with workarounds:
backend="nams" — bolt-only features raise
| Accessor / Method | Behavior on NAMS | Workaround |
|---|---|---|
|
Raises immediately |
|
|
Property access raises first |
Use higher-level methods ( |
|
Method call raises |
Pass |
|
Method call raises |
Remove the buffered queue — NAMS commits synchronously. |
|
Method call raises |
NAMS deduplicates on |
|
Method call raises |
Server-managed; not exposed via API yet. |
|
Method call raises |
Server-managed. |
|
Method call raises |
Not supported — NAMS owns its schema. |
|
Method call raises |
Server-managed schema; no client-side setup needed. |
|
Method call raises |
Not exposed by NAMS. Pre-geocode at ingest. |
|
Method call raises |
Same — no NAMS analog. |
|
Method call raises |
NAMS handles dedup server-side; the surface isn’t exposed. |
|
Method call raises |
Provenance is server-managed; query via |
|
Not on |
Use Platinum |
|
Not on |
NAMS extracts server-side. Just call |
|
Not on |
Bolt schema migration; not applicable on NAMS. |
|
Not on |
NAMS embeds server-side. |
|
Not on |
Bolt schema-edge mechanism; no NAMS equivalent. |
|
Not on |
Bolt schema migration. |
|
Method call raises |
|
|
Method call raises |
|
|
Method call raises |
No NAMS equivalent — geospatial features are bolt-only. |
backend="bolt" — Platinum-tier methods raise
These methods exist on NamsLongTermMemory / NamsShortTermMemory but not on the bolt classes:
| Method | Workaround on bolt |
|---|---|
|
Store feedback yourself as a relationship: |
|
Derive from |
|
Use |
|
Use the bolt |
|
Use the bolt |
|
Bolt creates conversations implicitly on first |
|
|
Behavior differences (subtle but important)
Deduplication
-
Bolt: client-side deduplication runs on every
add_entity(configurable viaDeduplicationConfig).add_entityreturns(Entity, DeduplicationResult)— the tuple carries info about whether a merge happened. -
NAMS: dedup is server-side and opaque to the client.
add_entityreturns justEntity. Existing code that unpacks the tuple (entity, dedup = await client.long_term.add_entity(…)) will break on NAMS — the result isn’t iterable as a 2-tuple. Migrate to: +python result = await client.long_term.add_entity(…) if isinstance(result, tuple): entity, dedup = result # bolt else: entity = result # NAMS
Embeddings and vector indexes
-
Bolt: you configure an embedder (OpenAI, sentence-transformers, etc.) on
MemorySettings.embedding; the client embeds text before storage; vector indexes are created atconnect()sized to the embedder’s dimensions. -
NAMS: server-managed. The
MemorySettings.embeddingconfig emits a singleUserWarningatconnect()time and is ignored.
Entity resolution
-
Bolt: configurable via
ResolutionConfig(exact / fuzzy / semantic / composite). -
NAMS: server-managed;
MemorySettings.resolutionis ignored with a warning.
Deprecation timeline
-
v0.4 (this release) —
client.graph.execute_reademits one-timeDeprecationWarningper process on bolt. -
v0.5 (planned) — More NAMS Platinum surface (additional tools, batched operations).
-
v0.6 (planned) — Remove
client.graphentirely.client.query.cypherbecomes the only path for raw Cypher.
Need help?
-
Bolt vs NAMS — when to choose which.
-
Use NAMS — full config reference.
-
File an issue if you hit a
NotSupportedErroryou think should work.