Cross-Agent Memory Sharing
- The capability in one sentence
- The two namespaces
- Multi-tenant scoping with
user_identifier - Cross-language sharing: same contract, different SDKs
- A typical multi-agent architecture
- Worked example: ingestion + chat + analytics
- When agents collide: avoiding write conflicts
- Reasoning trace isolation
- Why this matters for AI engineering
- See Also
How multiple agents — written in different languages, using different frameworks, deployed as separate services — read and write the same memory graph.
Conversations are private. Knowledge is shared. That’s the design contract that makes Neo4j Agent Memory natural for multi-agent systems.
The capability in one sentence
If two agents point at the same backend (NAMS API key, or the same Neo4j cluster), an entity created by Agent A is queryable by Agent B as soon as the write returns — with no copy step, no message bus, no cache invalidation.
The two namespaces
Every memory operation lives in one of two namespaces:
| Namespace | Scoped by | What’s in it |
|---|---|---|
Conversation namespace (private) |
|
|
Entity namespace (shared) |
Global (within a backend / workspace) |
|
This split is the load-bearing design choice. It mirrors how human teams work: individual notebooks (conversations) plus a shared wiki (knowledge).
Multi-tenant scoping with user_identifier
For SaaS-style deployments where one backend serves many end-users, every memory call accepts a user_identifier= kwarg:
# Agent code, identical for every end-user
async def handle_request(user_id: str, message: str):
async with MemoryClient() as memory:
await memory.short_term.add_message(
session_id=f"chat-{user_id}",
role="user",
content=message,
user_identifier=user_id, # scopes the write
)
# Subsequent reads with the same user_identifier only see this user's data
prefs = await memory.long_term.search_preferences(
"communication style",
user_identifier=user_id,
)
user_identifier is stored as a property on every node and used as a WHERE filter on every read. See Multi-tenant memory for the full pattern.
Cross-language sharing: same contract, different SDKs
The Python SDK (neo4j-agent-memory) and the TypeScript SDK (@neo4j-labs/agent-memory) both implement the same NAMS REST contract. So an agent in either language can read what an agent in the other wrote.
Example: a Python ingestion agent and a TypeScript chat agent sharing entities:
# ingestion-agent (Python) — runs on a cron, scrapes podcasts
async with MemoryClient() as memory:
entity, _ = await memory.long_term.add_entity(
"Alice Johnson", "PERSON",
description="Former CTO of Acme, speaker on Lenny's podcast Ep. 42",
)
// chat-agent (TypeScript) — runs on Cloudflare Workers
const memory = new MemoryClient();
const matches = await memory.longTerm.searchEntities("Alice Johnson", { topK: 3 });
// matches[0].description includes the entity the Python agent created.
Both SDKs write nodes with the same labels, property names, and relationship types. The graph doesn’t know which language produced it.
A typical multi-agent architecture
| Layer | Responsibility |
|---|---|
Backend (NAMS or Neo4j) |
Single source of truth for the memory graph. NAMS is the natural choice when you don’t want to operate Neo4j; self-hosted Bolt is the choice when you need write-Cypher or geospatial features. |
Agents (any language, any framework) |
Each agent is a separate process or service. Each holds its own |
Orchestrator (optional) |
A coordinating agent that reads all sub-agents' reasoning traces, queries the shared entity graph, and synthesizes a response. Can be a workflow engine (LangGraph, Mastra) or a simple supervisor agent. |
When agents are independent services (containerized, on different runtimes), NAMS removes the "who runs Neo4j?" question — every service just needs an API key.
Worked example: ingestion + chat + analytics
Three agents that never talk to each other directly but cooperate via shared memory:
| Agent | Language | Framework | What it does |
|---|---|---|---|
Ingestion |
Python |
PydanticAI |
Cron job. Reads podcast transcripts, extracts |
Chat |
TypeScript |
Vercel AI SDK on Cloudflare Workers |
User-facing chat. On every turn, reads relevant entities and preferences from the shared graph, persists the conversation in its own session namespace. |
Analytics |
Python |
Plain script + |
Nightly. Reads all entities, computes co-mention graphs, surfaces trends. |
Each agent is its own deployment. None imports the others. They cooperate solely through reads and writes against the shared backend.
When agents collide: avoiding write conflicts
Because the entity namespace is shared, two agents can try to create the same entity at the same time. Neo4j Agent Memory handles this with:
-
Entity deduplication on ingest —
add_entity()checks for embedding-similar and fuzzy-name matches before creating a new node. Above theauto_merge_threshold(default 0.95), the new write merges into the existing node and the name is added as an alias. -
SAME_ASflagged pairs — betweenflag_threshold(0.85) and the auto-merge threshold, the new entity is created but linked via aSAME_ASrelationship for human review. -
Server-side dedup on NAMS — the hosted backend performs dedup transparently.
See Handle duplicates for the configuration knobs.
Reasoning trace isolation
While entities are shared, reasoning traces are per-session by design:
# Agent A starts a trace
trace_a = await memory.reasoning.start_trace(
session_id="agent-a-session-1",
task="Plan a trip",
)
# Agent B in the same process — its traces don't show up in A's session
trace_b = await memory.reasoning.start_trace(
session_id="agent-b-session-1",
task="Book a flight",
)
# A's get_session_traces returns only A's traces
await memory.reasoning.get_session_traces("agent-a-session-1")
But if an orchestrator wants cross-agent visibility for audit, it can query Cypher (Bolt) or the read-only Cypher endpoint (NAMS Platinum):
// All traces created in the last hour, across all sessions and agents
MATCH (t:ReasoningTrace)
WHERE t.created_at > datetime() - duration('PT1H')
RETURN t.session_id, t.task, t.outcome, t.success
ORDER BY t.created_at DESC
See Audit reasoning with TOUCHED edges for the full audit pattern.
Why this matters for AI engineering
The hardest problem in multi-agent systems is not coordination — it’s coherence. Two agents that "know" different things will give the user contradictory answers. A shared memory graph keeps every agent’s worldview in sync without bolting on a separate state management layer.
The architecture also degrades gracefully: a single-agent prototype written against MemoryClient becomes a multi-agent system by adding more agents that point at the same backend. No refactor.
See Also
-
Understanding the Three Memory Types — what conversations, entities, and traces are
-
Bolt vs NAMS — which backend to pick
-
Multi-tenant memory —
user_identifier=patterns -
Use NAMS — the hosted-service operational guide
-
NAMS Quickstart — end-to-end deployment
-
NAMS REST API — the wire-level contract both SDKs implement