Cogitator
Memory & RAG

Knowledge Graphs

Extract entities and relationships from conversations, store them in a graph, run inference rules, and enrich agent context with structured knowledge.

Overview

Knowledge graphs give agents structured memory beyond flat conversation history. Instead of treating past messages as opaque text, the knowledge graph extracts entities (people, organizations, concepts) and relationships (knows, works_at, part_of) into a queryable graph.

This enables agents to:

  • Answer questions about relationships between entities
  • Discover indirect connections through graph traversal
  • Infer new facts from existing relationships
  • Build richer context by injecting relevant graph data into prompts

Architecture

Conversation ──► LLMEntityExtractor ──► GraphAdapter (storage)

                                    GraphInferenceEngine (rules)

                                    GraphContextBuilder (retrieval)

                                        Agent Prompt

Entity Extraction

The LLMEntityExtractor uses an LLM to identify entities and relationships from text. It prompts the model for structured JSON output, then validates and filters the results.

import { LLMEntityExtractor } from '@cogitator-ai/memory';

const extractor = new LLMEntityExtractor(llmBackend, {
  minConfidence: 0.7,
  maxEntitiesPerText: 20,
  maxRelationsPerText: 30,
});

const result = await extractor.extract(
  'Alice works at Acme Corp in New York. She knows Bob from the engineering team.'
);

console.log(result.entities);
// [
//   { name: 'Alice', type: 'person', confidence: 0.95 },
//   { name: 'Acme Corp', type: 'organization', confidence: 0.95 },
//   { name: 'New York', type: 'location', confidence: 0.9 },
//   { name: 'Bob', type: 'person', confidence: 0.9 },
// ]

console.log(result.relations);
// [
//   { sourceEntity: 'Alice', targetEntity: 'Acme Corp', type: 'works_at', confidence: 0.95 },
//   { sourceEntity: 'Acme Corp', targetEntity: 'New York', type: 'located_in', confidence: 0.85 },
//   { sourceEntity: 'Alice', targetEntity: 'Bob', type: 'knows', confidence: 0.9 },
// ]

Entity Types

person, organization, location, concept, event, object

Relation Types

knows, works_at, located_in, part_of, related_to, created_by, belongs_to, associated_with, causes, precedes

Extraction Context

Provide hints to improve extraction quality:

const result = await extractor.extract(text, {
  entityTypeHints: ['person', 'organization'],
  relationTypeHints: ['works_at', 'knows'],
  existingEntities: ['Alice', 'Acme Corp'],
});

Existing entities help the LLM disambiguate references and avoid creating duplicates.

Graph Adapter

The PostgresGraphAdapter stores nodes and edges in PostgreSQL with full indexing, semantic search via pgvector, and recursive traversal using CTEs.

import { PostgresGraphAdapter } from '@cogitator-ai/memory';
import pg from 'pg';

const pool = new pg.Pool({
  connectionString: process.env.DATABASE_URL!,
});

const graph = new PostgresGraphAdapter({
  pool,
  schema: 'cogitator',
  vectorDimensions: 1536,
});

The adapter auto-creates graph_nodes and graph_edges tables on first use.

Adding Nodes and Edges

const { data: alice } = await graph.addNode({
  agentId: 'agent-1',
  type: 'person',
  name: 'Alice',
  aliases: ['alice'],
  description: 'Engineer at Acme Corp',
  properties: { role: 'backend' },
  confidence: 0.95,
  source: 'inferred',
});

const { data: acme } = await graph.addNode({
  agentId: 'agent-1',
  type: 'organization',
  name: 'Acme Corp',
  aliases: ['Acme'],
  properties: {},
  confidence: 0.95,
  source: 'inferred',
});

await graph.addEdge({
  agentId: 'agent-1',
  sourceNodeId: alice.id,
  targetNodeId: acme.id,
  type: 'works_at',
  weight: 1.0,
  bidirectional: false,
  properties: {},
  confidence: 0.95,
  source: 'inferred',
});

Querying

const { data: people } = await graph.queryNodes({
  agentId: 'agent-1',
  types: ['person'],
  namePattern: 'Ali',
  minConfidence: 0.8,
  limit: 10,
});

const { data: edges } = await graph.queryEdges({
  agentId: 'agent-1',
  types: ['works_at', 'knows'],
  minConfidence: 0.7,
});

Graph Traversal

Traverse the graph from a starting node, following edges up to a specified depth:

const { data: traversal } = await graph.traverse({
  agentId: 'agent-1',
  startNodeId: alice.id,
  maxDepth: 2,
  direction: 'both',
  edgeTypes: ['knows', 'works_at'],
  minConfidence: 0.6,
});

for (const path of traversal.paths) {
  const names = path.nodes.map((n) => n.name).join(' -> ');
  console.log(`${names} (weight: ${path.totalWeight})`);
}

Shortest Path

Find the shortest path between two nodes using recursive CTEs:

const { data: path } = await graph.findShortestPath('agent-1', alice.id, bobNodeId, 5);

if (path) {
  console.log(`Path length: ${path.length}, weight: ${path.totalWeight}`);
}

When nodes have embeddings, search by meaning:

const vector = await embeddingService.embed('backend engineering');
const { data: nodes } = await graph.searchNodesSemantic({
  agentId: 'agent-1',
  vector,
  limit: 5,
  threshold: 0.7,
  entityTypes: ['person', 'concept'],
});

Node Merging

Merge duplicate nodes, redirecting all edges to the target:

await graph.mergeNodes(primaryNodeId, [duplicateId1, duplicateId2]);

Inference Engine

The GraphInferenceEngine applies rules to discover implicit relationships. It ships with default rules and supports custom ones.

import { GraphInferenceEngine } from '@cogitator-ai/memory';

const engine = new GraphInferenceEngine(graph);

const inferred = await engine.infer('agent-1', {
  minConfidence: 0.6,
  maxInferences: 100,
});

console.log(`Found ${inferred.length} inferred relationships`);

Default Rules

RulePatternConclusionConfidence
transitive_knowsA knows B, B knows CA related_to C0.6
colleaguesA works_at X, B works_at XA associated_with B (colleague)0.8
location_hierarchyA located_in B, B located_in CA located_in C0.9
part_of_hierarchyA part_of B, B part_of CA part_of C0.85
causality_chainA causes B, B causes CA causes C (indirect)0.7

Custom Rules

engine.registerRule({
  name: 'team_membership',
  description: 'People who belong to the same team are associated',
  pattern: {
    edgeTypes: ['belongs_to', 'belongs_to'],
    minPathLength: 2,
    maxPathLength: 2,
    nodeTypeConstraints: {
      0: ['person'],
      1: ['concept'],
      2: ['person'],
    },
  },
  conclusion: {
    edgeType: 'associated_with',
    label: 'teammate',
    weightFormula: 'min',
    bidirectional: true,
  },
  confidence: 0.75,
  enabled: true,
});

Weight formulas: min, max, average, product -- applied to the weights of edges in the matched path.

Materializing Inferences

Save inferred edges back to the graph:

const { data: materialized } = await engine.materialize(inferred);
console.log(`Materialized ${materialized.length} edges`);

Materialized edges include metadata tracking the rule that created them and the supporting path.

Graph Context Builder

The GraphContextBuilder retrieves relevant graph data for a given input and formats it as structured text to inject into the agent's prompt.

import { GraphContextBuilder } from '@cogitator-ai/memory';

const contextBuilder = new GraphContextBuilder(graph, embeddingService, {
  maxNodes: 20,
  maxEdges: 50,
  maxDepth: 3,
});

const context = await contextBuilder.buildContext('agent-1', 'What does Alice do at Acme Corp?');

console.log(context.formattedContext);
// ## Knowledge Graph Context
//
// ### Entities
// - **Alice** (Person): Engineer at Acme Corp
// - **Acme Corp** (Organization)
//
// ### Relationships
// - Alice -> works at -> Acme Corp

console.log(`Nodes: ${context.nodes.length}, Edges: ${context.edges.length}`);
console.log(`Estimated tokens: ${context.tokenCount}`);

The builder performs keyword matching and semantic search on graph nodes, then traverses outward from matched nodes to gather related context. Nodes are ranked by name match, alias match, keyword overlap, access frequency, and confidence.

OptionTypeDefaultDescription
maxNodesnumber20Maximum nodes to include in context
maxEdgesnumber50Maximum edges to include in context
maxDepthnumber3Graph traversal depth from matched nodes
includeInferredbooleantrueInclude edges created by inference rules
tokensPerNodenumber30Estimated tokens per node for budget
tokensPerEdgenumber15Estimated tokens per edge for budget

On this page