Cross-Agent Memory Sharing

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)

session_id (and optionally user_identifier=)

Conversation, Message, ReasoningTrace, ReasoningStep, ToolCall. Each agent sees only the sessions it wrote to. One agent’s chat history is invisible to another by default.

Entity namespace (shared)

Global (within a backend / workspace)

Entity (POLE+O), Preference, Fact, Relationship between entities. Any agent on the same backend reads what any other agent wrote.

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 MemoryClient pointed at the same backend. Each owns its session IDs but reads the shared entity graph.

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 Person/Organization entities, writes them to long-term memory.

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 + client.query.cypher(…​)

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 ingestadd_entity() checks for embedding-similar and fuzzy-name matches before creating a new node. Above the auto_merge_threshold (default 0.95), the new write merges into the existing node and the name is added as an alias.

  • SAME_AS flagged pairs — between flag_threshold (0.85) and the auto-merge threshold, the new entity is created but linked via a SAME_AS relationship 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