Skip to main content

Graph Memory

Standard entity memory stores flat records — a list of names, types, and attributes. Graph memory upgrades this to a full knowledge graph where entities are connected by typed, directed relationships with temporal metadata. This lets agents answer questions like “Who on the frontend team works with Raj?” or “What projects depended on the billing API before it was deprecated?” — queries that require traversing relationships, not just keyword search.

Quick Start

import { Agent, InMemoryGraphStore, openai } from "@radaros/core";

const agent = new Agent({
  name: "assistant",
  model: openai("gpt-4o"),
  memory: {
    storage: new MongoDBStorage({ uri: "mongodb://localhost/radaros" }),
    graph: {
      store: new InMemoryGraphStore(),
    },
  },
});
With graph enabled, the agent automatically:
  • Extracts entities and relationships from conversations
  • Exposes graph tools (query_graph, traverse_entity, add_relationship) to the agent
  • Injects relevant subgraph context into the system prompt before each run

Neo4j Backend

For production, use Neo4j as the graph store:
import { Agent, Neo4jGraphStore, openai } from "@radaros/core";

const agent = new Agent({
  name: "assistant",
  model: openai("gpt-4o"),
  memory: {
    storage: new MongoDBStorage({ uri: "mongodb://localhost/radaros" }),
    graph: {
      store: new Neo4jGraphStore({
        uri: "bolt://localhost:7687",
        username: "neo4j",
        password: process.env.NEO4J_PASSWORD!,
        database: "radaros",
      }),
      maxContextNodes: 20,
      maxDepth: 3,
    },
  },
});
Neo4j gives you native graph traversal performance and Cypher queries under the hood, which matters when the graph grows past a few thousand nodes.

Auto-Extraction from Conversations

When graph memory is enabled, the background extraction step (after each run) identifies entities and their relationships from the conversation:
// Conversation:
// User: "Raj from the frontend team just finished the checkout redesign.
//        He's handing it off to Priya for QA testing."

// Extracted graph:
// Nodes:
//   - Raj (type: "person", attributes: { team: "frontend" })
//   - Priya (type: "person", attributes: { role: "QA" })
//   - Checkout Redesign (type: "project")
//   - Frontend Team (type: "team")
//
// Edges:
//   - Raj --[MEMBER_OF]--> Frontend Team
//   - Raj --[COMPLETED]--> Checkout Redesign
//   - Raj --[HANDED_OFF_TO]--> Priya
//   - Priya --[TESTING]--> Checkout Redesign
The extraction model determines relationship types automatically. Over time, repeated mentions reinforce existing edges and add new ones.
The agent can query the graph through auto-exposed tools or you can query it programmatically:

Agent Tools (Automatic)

When graph memory is enabled, three tools are automatically added:
// query_graph — semantic search over nodes and edges
// Agent calls: query_graph({ query: "who works on checkout" })
// → [{ name: "Raj", type: "person", relationships: [...] },
//    { name: "Priya", type: "person", relationships: [...] }]

// traverse_entity — walk outward from a specific node
// Agent calls: traverse_entity({ entity: "Raj", depth: 2 })
// → { node: "Raj", edges: [
//      { type: "MEMBER_OF", target: "Frontend Team" },
//      { type: "COMPLETED", target: "Checkout Redesign" },
//      { type: "HANDED_OFF_TO", target: "Priya" },
//    ]}

// add_relationship — manually link two entities
// Agent calls: add_relationship({
//   from: "Priya",
//   to: "Checkout Redesign",
//   type: "APPROVED",
//   attributes: { date: "2026-04-07" },
// })

Programmatic Access

const graphStore = agent.memory?.getGraphStore();

const results = await graphStore?.query({
  text: "billing API dependencies",
  maxNodes: 10,
});

const neighborhood = await graphStore?.traverse({
  entityName: "Billing API",
  depth: 2,
  edgeTypes: ["DEPENDS_ON", "USED_BY"],
});

Temporal Awareness

Every node and edge in the graph carries temporal metadata:
interface GraphNode {
  name: string;
  type: string;
  attributes: Record<string, unknown>;
  validFrom: Date;          // when this node was first observed
  invalidatedAt?: Date;     // when this node was marked obsolete
  lastMentioned: Date;      // most recent conversation reference
}

interface GraphEdge {
  from: string;
  to: string;
  type: string;
  attributes: Record<string, unknown>;
  validFrom: Date;          // when the relationship was established
  invalidatedAt?: Date;     // when the relationship ended
  confidence: number;       // 0–1, increases with repeated mentions
}
When the extraction model detects a contradiction (e.g., “Raj moved from frontend to platform team”), the old MEMBER_OF → Frontend Team edge is marked with invalidatedAt and a new MEMBER_OF → Platform Team edge is created. The old data is never deleted — see Temporal Awareness for details. By default, buildContext() only injects currently-valid nodes and edges. To include historical data:
graph: {
  store: new Neo4jGraphStore({ ... }),
  includeInvalidated: true,   // show superseded facts in context
}

Configuration

PropertyTypeDefaultDescription
storeGraphStorerequiredBackend store (InMemoryGraphStore, Neo4jGraphStore)
maxContextNodesnumber15Max nodes injected into system prompt context
maxDepthnumber2Max traversal depth for context assembly
edgeTypesstring[]allFilter which relationship types to include in context
includeInvalidatedbooleanfalseInclude temporally invalidated nodes/edges in context
extractRelationshipsbooleantrueAuto-extract relationships from conversations
minConfidencenumber0.3Minimum edge confidence to include in context
namespacestring"global""global" | "user" | custom namespace for graph isolation

Graph Store Implementations

StorePersistenceBest For
InMemoryGraphStoreNone (session only)Development, testing
Neo4jGraphStoreDurableProduction, large graphs, complex traversals
Both implement the GraphStore interface:
interface GraphStore {
  addNode(node: GraphNode): Promise<void>;
  addEdge(edge: GraphEdge): Promise<void>;
  query(opts: { text: string; maxNodes?: number }): Promise<GraphNode[]>;
  traverse(opts: { entityName: string; depth?: number; edgeTypes?: string[] }): Promise<GraphNeighborhood>;
  invalidateNode(name: string): Promise<void>;
  invalidateEdge(from: string, to: string, type: string): Promise<void>;
}

Cross-References