Shared Memory Graph for MCP Agents: How to Give Multiple Agents a Single Source of Truth FalkorDB published a design for a shared memory graph that lets multiple MCP agents read from and write to a single graph, arguing that multi-agent memory requires an explicit graph schema, server-side write ownership, and provenance on every update. The schema defines five node types — Agent, User, Task, Entity, and Decision — and seven relationship types, with every node carrying mandatory agent_id, written_at, and source properties and every relationship carrying created_by and created_at. FalkorDB states that each agent owns the node types it creates, with the role-to-permission mapping held on the server keyed by agent_id so an agent cannot escalate its own permissions. The previous post in this series https://www.falkordb.com/blog/agent-memory-for-long-running-agents/ covered single-agent memory: one agent, one user, one graph. That model holds until you add a second agent. Once two agents share a user, a task, or an organizational context, per-agent memory becomes the problem. Agent A writes a decision. Agent B writes a contradicting one. Neither knows what the other stored. The user sees inconsistency, and the debugging session traces back to the fact that you never defined who owns what. Shared multi-agent memory requires an explicit graph schema, server-side write ownership, and provenance on every update. Per-Agent Memory vs. Shared Memory Per-agent memory is scoped. Each agent maintains its own store, isolated from every other agent. That isolation is useful when agents are independent: no cross-contamination, no coordination overhead, clean deletes. The retrieval architecture post https://www.falkordb.com/blog/ai-agent-memory-retrieval-architecture/ covers this pattern in depth. Shared memory is a different architectural problem. Multiple agents read from and write to the same graph. A researcher agent that discovers a new entity makes it immediately available to a writer and a reviewer without any message-passing or sync protocol. That’s the upside. The downside is that without explicit write ownership, agents silently overwrite each other’s facts. | Dimension | Per-agent memory | Shared memory graph | |---|---|---| | Isolation | Complete | Explicit boundaries required | | Consistency | Trivial one writer | Requires write ownership rules | | Freshness | Stale across agents | Current on next read | | Conflict risk | None | High without provenance | | Use case | Single-agent, single-user | Multi-agent coordination | In a shared graph, the database is no longer the isolation boundary. The graph schema and the MCP server layer are. The Graph Schema A shared memory graph needs a schema that covers the full coordination surface: agents, users, tasks, entities, and decisions. Nodes - Agent : one node per agent type. Properties: agent id , role , capabilities . - User : the human in the loop. Properties: user id , created at . - Task : a unit of work. Properties: task id , status , created at , closed at plus provenance fields . - Entity : any named thing agents discover or reference. Properties: name , type , embedding plus provenance fields . - Decision : a conclusion that affects downstream work. Properties: decision id , content , confidence , agent id , written at , source , valid from , valid to , superseded by . Relationships php Agent - :ASSIGNED TO - Task User - :OWNS - Task Agent - :WROTE {created by, created at} - Decision Decision - :REFERENCES - Entity Entity - :RELATED TO - Entity Task - :PRODUCED - Decision Agent - :DISCOVERED - Entity Every node carries three mandatory provenance properties: agent id , written at , and source . Every relationship carries created by and created at . These fields are the audit trail that answers “which agent wrote this, when, and based on what.” The entity side of the schema is intentionally generic. Agents can discover new entity types without a schema migration. Specificity lives in the type property and in relationship labels. Read and Write Boundaries Every agent gets read access to the full shared graph. Write access should be narrow, and it should be enforced server-side, not by trusting a role claim from the calling agent. Each agent owns the node types it creates. A researcher writes Entity and Decision nodes. A planner writes Task nodes. A reviewer can supersede a Decision by closing the existing one and writing a replacement. Crossing those lines is how contradictions enter the graph. The role-to-permission mapping lives on the server, keyed by agent id . An agent cannot escalate its own permissions by passing a different role in the request. Write permissions by role | Agent role | Can write | Cannot write | |---|---|---| | Researcher | Entity, Decision | Task, Agent | | Writer | Decision drafts | Entity, Task | | Reviewer | Decision supersede existing | Entity, Task | | Planner | Task | Entity, Decision content | All roles have full read access via ro query . No MERGE or CREATE runs outside the MCP write tools. When a Decision node contains bad data, the server-side role map tells you immediately which agent type wrote it. That’s the debugging advantage of write ownership: you don’t trace logs across four agents. Preventing Duplicate and Contradictory Memories Two agents discovering the same entity is expected. Two agents storing it under different names is a bug. “Acme Corp,” “ACME,” and “acme.com” are the same entity. If your graph has three nodes for them, every traversal from that entity misses two-thirds of the connected facts. Resolve entities at write time. Before writing a new Entity node, run a vector similarity search against existing Entity embeddings and a name normalization pass. If a match exceeds the similarity threshold, merge into the existing node. This deduplication step is not in the minimal code example below; it belongs in a preprocessing layer before the graph write. For contradictory Decisions, close the old fact before writing the new one. A reviewer agent superseding a researcher’s decision runs this as a single chained query, so both steps succeed or neither does: // Match both nodes first, then SET and CREATE atomically MATCH d:Decision {decision id: $old id} WHERE d.valid to IS NULL MATCH a:Agent {agent id: $agent id} SET d.valid to = $now, d.superseded by = $new id CREATE d2:Decision { decision id: $new id, content: $content, agent id: $agent id, written at: $now, valid from: $now, source: $source, confidence: $confidence } CREATE a - :WROTE {created by: $agent id, created at: $now} - d2 Both MATCHes run before any write, so a missing Agent node causes the whole query to return nothing rather than leaving the old Decision closed with no replacement. FalkorDB runs each query atomically and serializes writes to a graph, so two concurrent supersedes can’t both close the same Decision. A query filtered on valid to IS NULL returns exactly one current Decision. MCP Memory Server Architecture with FalkorDB The MCP memory server is the single entry point for all graph writes. Agents call tools. The server looks up the calling agent’s role from a server-side map, checks write permissions, and executes the Cypher. No agent touches FalkorDB directly. Below is a minimal FastMCP server exposing write memory and read memory . This example shows agent id as a tool parameter for readability; in production it should come from the authenticated MCP session so agents can’t pass a peer’s ID. The role map is keyed by agent id on the server. This example omits entity deduplication, relationship creation, and supersede logic to stay readable; all three belong in a production implementation. For the production MCP server, see the FalkorDB MCP server docs https://docs.falkordb.com/genai-tools/mcpserver , which covers deployment and configuration. python from mcp.server.fastmcp import FastMCP from falkordb import FalkorDB import time db = FalkorDB host="localhost", port=6379 graph = db.select graph "shared agent memory" mcp = FastMCP "falkordb-memory" Role is looked up server-side by agent id, not supplied by the caller AGENT ROLES: dict str, str = { "agent-researcher-01": "researcher", "agent-writer-01": "writer", "agent-reviewer-01": "reviewer", "agent-planner-01": "planner", } WRITE PERMISSIONS: dict str, set str = { "researcher": {"Entity", "Decision"}, "writer": {"Decision"}, "reviewer": {"Decision"}, "planner": {"Task"}, } @mcp.tool def write memory agent id: str, node id: str, content: str, source: str, confidence: float = 1.0, - dict: """Write a Decision node to the shared memory graph with provenance. Entity and Task writes use separate tools, not shown. agent id should come from the authenticated MCP session in production. """ role = AGENT ROLES.get agent id if role is None: return {"error": f"Unknown agent id: {agent id}"} allowed = WRITE PERMISSIONS.get role, set if "Decision" not in allowed: return {"error": f"Role '{role}' cannot write Decision nodes."} now = int time.time 1000 graph.query "CREATE n:Decision {" " decision id: $node id, content: $content," " agent id: $agent id, written at: $now," " source: $source, confidence: $confidence," " valid from: $now" "} ", { "node id": node id, "content": content, "agent id": agent id, "now": now, "source": source, "confidence": confidence, }, return {"decision id": node id, "written at": now} @mcp.tool def read memory cypher: str, params: dict | None = None - list: """Query the shared memory graph. Read-only. Return scalar properties in your query e.g. RETURN d.content, d.agent id , not nodes or edges, which are not JSON-serializable. """ result = graph.ro query cypher, params or {} ro query enforces read-only at the database level return list row for row in result.result set if name == " main ": mcp.run ro query enforces read-only access at the FalkorDB level, so no keyword blocklist is needed. The role check in write memory runs before any database call. The reviewer’s supersede right is in the permissions table; the supersede tool itself is omitted here for brevity. What This Architecture Gives You A shared memory graph built on this pattern delivers four concrete properties. Immediate consistency across agents. When a researcher writes a new Entity, the writer and reviewer see it on their next read. No sync, no polling, no message bus for memory updates. One current version per fact. The chained supersede query means valid to IS NULL always returns exactly one active Decision. History is preserved for audit; it’s just not surfaced in retrieval. A traceable write trail. Every node carries agent id , written at , and source . Every WROTE relationship carries created by and created at . When an answer is wrong, you trace it to the specific write and the agent that made it. For a deeper look at how this maps to enterprise auditability requirements, see the enterprise agents context layer reference https://www.falkordb.com/reference/enterprise-ai-agents-context-layer/ . Write ownership enforced at the server. The MCP server is the only path to the graph. The role map lives on the server, keyed by the authenticated session identity. Permissions are checked before any Cypher executes. Post 6 in this series covers RBAC for agents. Try FalkorDB Cloud https://www.falkordb.com/plans/ or run it locally https://docs.falkordb.com/ get-started with one Docker command: docker run -p 6379:6379 -p 3000:3000 -it --rm falkordb/falkordb:latest Then open localhost:3000 to see the shared memory graph in FalkorDB Browser. Author - Gal is a Software and AI Engineer, leading the development of GraphRAG-SDK, a specialized toolkit for building Graph Retrieval-Augmented Generation GraphRAG systems. It integrates knowledge graphs, ontology management, and state-of-the-art LLMs to deliver accurate, efficient, and customizable RAG workflows.