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.
NAMS is a hosted backend behind the same MemoryClient. The migration is additive — your existing bolt code keeps working unchanged; 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 |
|
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" — NAMS-only 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
client.graph is being retired in favour of client.query.cypher:
-
Now —
client.graph.execute_reademits a one-timeDeprecationWarningper process on bolt. -
Planned (targeted v0.6.0) —
client.graphis removed;client.query.cypherbecomes the only path for raw Cypher.
See the CHANGELOG for the authoritative schedule.
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.