
This blog is written by AI for SEO
Shared Memory for Multi-Agent Systems: One Graph to Query
A planner agent breaks a ticket into tasks, a research agent gathers customer context, and a coding agent writes the pull request. Ten minutes later, the support agent answers a customer ticket using a pricing tier the research agent corrected an hour ago. It hallucinated the old tier because it checked a separate Redis key, while the research agent dumped its raw output into an isolated Pinecone namespace.
Per-agent vector stores, Redis scratchpads, and summary-passing pipelines are not shared memory. They pass notes across a wall. You lose the identity of who discovered a fact, you overwrite newer data with older summaries, and you force your application layer to reconcile conflicting claims.
This guide shows you how to implement shared memory for multi-agent systems using a single graph in HelixDB. You will store memory nodes with attached embeddings and epoch timestamps, link them to the agents that authored them, and connect them to customer entities using typed edges. You will supersede stale knowledge without dropping history and query semantic memory scoped directly to an entity subgraph.
Step 1: Audit how your agents share memory today (and where it breaks)
Most multi-agent systems start with one of three memory patterns. The first is the shared scratchpad, usually backed by Redis or an in-memory dictionary. The planner dumps a JSON blob, the worker agents append keys, and each agent reads the entire blob into its context window. It breaks the moment your workflow runs past a few steps. Context windows fill with irrelevant chatter, concurrent writes overwrite entire sub-objects, and nobody knows which agent produced which conclusion.
The second is isolated vector collections. The researcher gets its own collection, the coder gets its own, and the support agent gets its own. When the support agent needs context, you broadcast queries across three collections, collect thirty chunks, and run a reranker. That wastes latency and destroys lineage. The support agent retrieves a document saying Acme Corp is on an Enterprise plan, but can't tell whether the researcher verified that fact ten minutes ago or the planner assumed it yesterday.
The third is message passing, where agents hand summaries to the next agent in a pipeline. Each step is a game of telephone. Nuance disappears, corrections can't travel backward to earlier agents, and any agent running out of order works with stale context. In AI agent memory architecture, isolated vectors and string passing fail under collaborative workloads every time. Multi-agent systems need a shared store where writes are attributed to specific agents, tied to concrete domain entities, and queried by both graph structure and semantic similarity.
Step 2: Model memories, agents and entities as one graph
Skip the separate key-value stores and vector indexes. Model your entire system state as a property graph. Every agent, entity, and memory becomes a node, and the relationships between them set the boundaries for your vector searches.
Start with three core node labels: Agent, Entity, and Memory. An Agent node stores attributes like agent_id, role (for example, researcher or planner), and model. An Entity node represents the target domain object, such as a customer, a repository, or a ticket. A Memory node stores the discrete assertion: content (the raw text), embedding (a float32 vector), created_at (epoch timestamp in milliseconds), and valid (a boolean flag).
Connect these nodes with explicit edge types:
WROTE: Directs from anAgentnode to aMemorynode. This gives you instant provenance. You can ask what the researcher wrote without touching anything the planner wrote.ABOUT: Directs from aMemorynode to anEntitynode. This ties abstract semantic chunks to a concrete entity ID.SUPERSEDES: Directs from a newerMemorynode to an olderMemorynode that it invalidates or refines.
This topology separates provenance from content. When the research agent discovers Acme Corp upgraded their contract, it doesn't overwrite the old memory record. It inserts a new Memory node, draws a SUPERSEDES edge to the old record, and marks the old record as invalid. If an operator audits why an agent made a decision, the trail is right there in the graph. The edges preserve history while your queries filter down to current truth.
Step 3: Set up HelixDB with helix init and helix start dev
HelixDB is an open-source graph-vector database built in Rust that combines graph traversal, vector search, and BM25 full-text search in one engine. It stores vectors directly as top-level properties on nodes and edges, backed by the embedded key-value engine SlateDB, which persists data to S3-compatible object storage while using memory and local disk for caching.
Install the Helix CLI to manage your local development environment. Run:
curl -fsSL https://raw.githubusercontent.com/HelixDB/helix-db/main/install.sh | bashInitialize a clean workspace for your multi-agent memory project:
helix init agent-memory
cd agent-memoryStart the local development server:
helix start devThis spins up HelixDB listening on http://localhost:8080. HelixDB processes JSON queries sent to POST /v2/query from native SDKs in TypeScript, Python, Go, and Rust. There is no custom syntax to compile ahead of time. Your application code builds queries via the SDK, which serializes them into JSON that the database parses and executes directly.
Install the official TypeScript SDK in your agent application:
npm install @helix-db/helix-dbYou now have an engine that writes graph structures and vector indexes simultaneously. You don't need separate Neo4j and Pinecone containers.
Step 4: Write memories so graph and vector land in one transaction
When an agent learns a new fact, you can't risk a partial write where the text hits a vector database but the relationship edge to the customer entity fails. HelixDB executes graph writes and vector writes together in atomic transactions. If an operation fails, the entire transaction rolls back.
In your agent worker, import the SDK builders from @helix-db/helix-db. Here is how the research agent writes a new memory linked to both itself and a customer entity:
import { writeBatch, NodeRef } from "@helix-db/helix-db";
async function recordAgentMemory(params: {
agentNodeId: string;
entityNodeId: string;
content: string;
embedding: number[];
}) {
const now = Date.now();
const tx = writeBatch()
.addNode({
label: "Memory",
properties: {
content: params.content,
embedding: params.embedding,
created_at: now,
valid: true
}
}, "newMemory")
.addEdge({
label: "WROTE",
from: NodeRef.byId(params.agentNodeId),
to: NodeRef.byAlias("newMemory"),
properties: { timestamp: now }
})
.addEdge({
label: "ABOUT",
from: NodeRef.byAlias("newMemory"),
to: NodeRef.byId(params.entityNodeId),
properties: { timestamp: now }
});
const result = await fetch("http://localhost:8080/v2/query", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(tx.toJSON())
});
if (!result.ok) {
throw new Error(`Write failed with status ${result.status}`);
}
return await result.json();
}This single call creates the Memory node, indexes its 1536-dimensional or 768-dimensional float32 vector, attaches the created_at timestamp for efficient time-range indexing, and creates the two structural edges. You write no glue code to sync a vector ID back to an external graph record. Read more about this approach in how to give AI agents persistent memory.
Step 5: Supersede instead of overwrite
Standard vector stores force you to delete or overwrite old embeddings when facts change. Say an agent discovers that Acme Corp reduced its team size from 50 to 20 seats. Overwriting the chunk means you lose when that change occurred, which agent reported it, and what the previous assumption was. Deleting it also corrupts the reasoning logs of any agents that acted on the older fact earlier in the run.
Use explicit supersession. When an agent emits a correction, execute a transaction that creates the new Memory node, sets valid: false on the old Memory node, and creates a SUPERSEDES edge pointing from the new node to the old node.
import { writeBatch, NodeRef } from "@helix-db/helix-db";
async function supersedeMemory(params: {
agentNodeId: string;
entityNodeId: string;
oldMemoryNodeId: string;
newContent: string;
newEmbedding: number[];
}) {
const now = Date.now();
const tx = writeBatch()
.addNode({
label: "Memory",
properties: {
content: params.newContent,
embedding: params.newEmbedding,
created_at: now,
valid: true
}
}, "updatedMemory")
.updateNode(params.oldMemoryNodeId, {
valid: false,
invalidated_at: now
})
.addEdge({
label: "SUPERSEDES",
from: NodeRef.byAlias("updatedMemory"),
to: NodeRef.byId(params.oldMemoryNodeId),
properties: { timestamp: now }
})
.addEdge({
label: "WROTE",
from: NodeRef.byId(params.agentNodeId),
to: NodeRef.byAlias("updatedMemory"),
properties: { timestamp: now }
})
.addEdge({
label: "ABOUT",
from: NodeRef.byAlias("updatedMemory"),
to: NodeRef.byId(params.entityNodeId),
properties: { timestamp: now }
});
return await fetch("http://localhost:8080/v2/query", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(tx.toJSON())
});
}Your read queries now just filter for valid: true. If you need to investigate why an agent acted poorly three days ago, traverse the SUPERSEDES chain backward and re-evaluate the exact worldview available at that timestamp.
Step 6: Retrieve by entity subgraph plus prefiltered vector search, optionally by writing agent
Pure vector search over millions of chunks is ungrounded. If your support agent asks "What are Acme Corp's billing limits?", a global top-k vector search will pull chunks from Acme Corp, Apex Corp, and generic billing documentation. The model has to sift through distractors from completely different entities.
HelixDB handles vector pre-filtering natively. Instead of ranking every vector in the database, it traverses the graph to find candidate nodes connected to a specific entity, filters them by properties like valid: true, and runs the approximate nearest neighbor search only across that filtered candidate pool. You can learn more about this mechanic in vector search on graph edges.
Say you want to know what the research agent specifically learned about Acme Corp. Start the traversal from the Acme Entity node, follow incoming ABOUT edges to Memory nodes, filter by valid: true, traverse the incoming WROTE edge to verify the agent's role is researcher, and rank the resulting memories against the query embedding.
import { g, Predicate, VectorDistanceMetric } from "@helix-db/helix-db";
const query = g.fromNode("entity-acme-uuid")
.inE("ABOUT")
.outV()
.filter(Predicate.property("valid").equals(true))
.filter(
Predicate.subgraph(
g.inE("WROTE").outV().filter(Predicate.property("role").equals("researcher"))
)
)
.vectorSearch({
property: "embedding",
vector: queryEmbedding,
metric: VectorDistanceMetric.Cosine,
limit: 5
});HelixDB caps unrestricted vector search at 800 effective results, and candidate streams over 1,000,000 unique entities trigger a query error rather than silent truncation. Pruning the search boundary via the graph first means your vector search runs over a small, highly relevant set of vectors. You get zero entity bleed and strict role provenance.
Step 7: Handle concurrency, tenancy and visibility honestly
When ten agents execute tasks in parallel, concurrency issues will show up. Many backend engineers assume that an engine written in Rust eliminates runtime races. Rust eliminates low-level data races in memory. It doesn't eliminate logical concurrency bugs, deadlocks, or conflicting transactions in your application logic.
In HelixDB Cloud, mutations are serialized through a single writer using serializable snapshot-isolation ACID transactions, while reader nodes scale horizontally backed by object storage. Graph and vector writes commit or roll back together, so an agent never leaves a Memory node without its WROTE and ABOUT edges. What transactions do not decide is your application logic: if two agents each write a fact that supersedes the same Memory node, both writes can be valid, and choosing which one stands is a reconciliation job for your workflow, covered below.
Multi-tenancy needs strict boundaries too. HelixDB provides tenant partitioning, which isolates tenant data on shared infrastructure. If your agents manage memory across different enterprise customers, partition your indexes by tenant ID at the database level. If you need fine-grained role-based visibility inside a single tenant (for instance, hiding executive compensation notes from a standard support agent), model those boundaries as labels or properties and filter on them in your query predicates. That filtering is enforced by your application code, not by the database: tenant partitioning is not per-user access control.
What to do next: troubleshooting contradictions and duplicate memories
Once your agents share a single graph, watch for two common problems: memory explosion and un-superseded contradictions.
Memory explosion happens when iterative agents write incremental observations on every loop. A coding agent might write "File opened", "Syntax parsed", and "Tests failed" as separate Memory nodes. Fix this by enforcing memory tiering. Keep raw execution steps in temporary scratchpad objects or short-lived memory nodes with an epoch expiration, and only write durable Memory nodes for final synthesized findings. You can use HelixDB's efficient time-range indexing over the created_at property to prune or archive memories older than thirty days.
Un-superseded contradictions occur when an agent writes a conflicting fact without identifying the node it replaces. For example, Agent B records that a customer is on an annual contract, while an existing valid node from Agent A says the customer is on monthly billing. When you detect semantic duplicates with high cosine similarity but opposing facts, run a reconciliation task. Have a supervisor agent query the Entity node for all valid memories, identify opposing claims, write a consolidating Memory node, and mark both earlier nodes superseded. Keep the shared graph tidy, and your agent workflows will stay coherent across thousands of operational steps.
Conclusion
Duct-taping a Redis scratchpad to an isolated vector store gives you disjointed chunks, missing authors, and silent data loss. Multi-agent systems need one memory layer where vectors, graph relationships, and audit trails live in the same engine.
HelixDB replaces that tangled stack with an open-source, Rust-built graph-vector database that runs graph traversals and prefiltered vector searches in a single query. Star HelixDB on GitHub, pull the Docker container or run helix start dev, and stop letting your agents overwrite each other's work.