knowledge-graph

Installation
SKILL.md

Knowledge Graph

Overview

Build and query persistent knowledge graphs of entities, relationships, and observations across agent sessions. Enables agents to accumulate structured knowledge over time — tracking what they've learned about projects, people, codebases, and domains.

When to Invoke

Skill({ skill: 'knowledge-graph' }) when:

  • Agent needs to remember structured facts across sessions
  • Building a model of a codebase, project, or domain from observations
  • Tracking relationships between people, systems, or concepts
  • Implementing long-term memory that survives context resets

Option 1: MCP Memory Server (Recommended for Claude Agents)

The official MCP memory server provides a persistent knowledge graph accessible via tool calls.

Setup

npx -y @modelcontextprotocol/server-memory

Claude Desktop / settings.json config:

{
  "mcpServers": {
    "memory": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-memory"],
      "env": {
        "MEMORY_FILE_PATH": ".claude/context/memory/knowledge-graph.json"
      }
    }
  }
}

MCP Memory Server Tools

Tool Purpose
create_entities Add one or more entities with type and observations
create_relations Add directed relations between entities
add_observations Append new observations to existing entities
delete_entities Remove entities (and their relations)
delete_observations Remove specific observations
delete_relations Remove specific relations
read_graph Return the full graph
search_nodes Search entities by query string
open_nodes Retrieve specific entities by name

Usage Pattern

// Create entities
mcp__memory__create_entities({
  entities: [
    {
      name: 'authentication-service',
      entityType: 'service',
      observations: [
        'Handles JWT authentication and refresh token rotation',
        'Located at src/auth/',
        'Uses Redis for token storage',
        'Rate-limited to 100 req/min per IP',
      ],
    },
    {
      name: 'Alice Chen',
      entityType: 'person',
      observations: ['Senior engineer, owns the auth service', 'On-call for auth incidents'],
    },
  ],
});

// Create relations
mcp__memory__create_relations({
  relations: [
    {
      from: 'Alice Chen',
      to: 'authentication-service',
      relationType: 'owns',
    },
  ],
});

// Add new observations as you learn more
mcp__memory__add_observations({
  observations: [
    {
      entityName: 'authentication-service',
      contents: ['Migrated to Argon2 password hashing in March 2026'],
    },
  ],
});

// Search for relevant nodes
mcp__memory__search_nodes({ query: 'authentication' });

// Retrieve specific entities
mcp__memory__open_nodes({ names: ['authentication-service', 'Alice Chen'] });

Session Startup Pattern (MANDATORY)

At the start of every agent session that uses knowledge graphs:

// 1. Load existing graph context
const existing = await mcp__memory__read_graph({});
// Review existing entities for this domain

// 2. Search for relevant entities
const relevant = await mcp__memory__search_nodes({ query: '<current task domain>' });

// 3. Update entities with new observations from this session
// ... do work ...

// 4. Before session end, add observations for key discoveries
await mcp__memory__add_observations({
  observations: [
    {
      entityName: 'my-project',
      contents: [`Session ${new Date().toISOString()}: Discovered X, fixed Y`],
    },
  ],
});

Option 2: Local JSON Knowledge Graph

For agents without MCP memory server access, use a local JSON file:

import json
import uuid
from datetime import datetime, timezone
from pathlib import Path

GRAPH_PATH = Path(".claude/context/memory/knowledge-graph.json")

def load_graph() -> dict:
    if GRAPH_PATH.exists():
        return json.loads(GRAPH_PATH.read_text())
    return {"entities": {}, "relations": []}

def save_graph(graph: dict):
    GRAPH_PATH.parent.mkdir(parents=True, exist_ok=True)
    GRAPH_PATH.write_text(json.dumps(graph, indent=2))

def add_entity(graph: dict, name: str, entity_type: str, observations: list[str]) -> str:
    """Add or update an entity. Returns entity ID."""
    # Check if entity with this name exists
    for eid, entity in graph["entities"].items():
        if entity["name"] == name:
            entity["observations"].extend(observations)
            entity["updated_at"] = datetime.now(timezone.utc).isoformat()
            return eid

    # Create new entity
    eid = str(uuid.uuid4())[:8]
    graph["entities"][eid] = {
        "id": eid,
        "name": name,
        "type": entity_type,
        "observations": observations,
        "created_at": datetime.now(timezone.utc).isoformat(),
        "updated_at": datetime.now(timezone.utc).isoformat(),
    }
    return eid

def add_relation(graph: dict, from_name: str, to_name: str, relation_type: str):
    """Add a directed relation between two entities by name."""
    graph["relations"].append({
        "from": from_name,
        "to": to_name,
        "type": relation_type,
        "created_at": datetime.now(timezone.utc).isoformat(),
    })

def search_entities(graph: dict, query: str) -> list[dict]:
    """Search entities by name, type, or observation content."""
    query_lower = query.lower()
    results = []
    for entity in graph["entities"].values():
        if (query_lower in entity["name"].lower()
            or query_lower in entity["type"].lower()
            or any(query_lower in obs.lower() for obs in entity["observations"])):
            results.append(entity)
    return results

def get_entity_relations(graph: dict, entity_name: str) -> dict:
    """Get all relations for an entity (outgoing and incoming)."""
    outgoing = [r for r in graph["relations"] if r["from"] == entity_name]
    incoming = [r for r in graph["relations"] if r["to"] == entity_name]
    return {"outgoing": outgoing, "incoming": incoming}

# Usage
graph = load_graph()

auth_id = add_entity(graph, "authentication-service", "service", [
    "Handles JWT authentication",
    "Located at src/auth/",
    "Uses Redis for token storage",
])

add_entity(graph, "Alice Chen", "person", [
    "Senior engineer, owns the auth service",
])

add_relation(graph, "Alice Chen", "authentication-service", "owns")

save_graph(graph)

# Query
results = search_entities(graph, "auth")
relations = get_entity_relations(graph, "Alice Chen")

Entity Schema

interface Entity {
  id: string; // Stable UUID (never changes)
  name: string; // Human-readable identifier
  type: EntityType; // Normalized type string
  observations: string[]; // Timestamped facts (append-only)
  created_at: string; // ISO 8601
  updated_at: string; // ISO 8601
}

interface Relation {
  from: string; // Entity name (source)
  to: string; // Entity name (target)
  type: string; // Directional verb: "owns", "depends_on", "calls", "manages"
  created_at: string;
}

// Common entity types
type EntityType =
  | 'person'
  | 'service'
  | 'codebase'
  | 'file'
  | 'concept'
  | 'organization'
  | 'tool'
  | 'decision'
  | 'issue'
  | 'feature';

Common Relation Types

Relation Direction Example
owns person → service Alice owns auth-service
depends_on service → service api-gateway depends_on auth-service
calls service → service checkout calls payment-processor
manages person → person CTO manages engineering team
implements service → concept auth-service implements JWT
documented_in feature → file login documented_in README
fixes commit → issue fix/abc123 fixes issue-456
blocks issue → issue JIRA-100 blocks JIRA-101

Codebase Knowledge Graph Pattern

Build a knowledge graph of a codebase as you explore it:

// As you discover things about a codebase, record them
async function recordCodebaseDiscovery(findings: Discovery[]) {
  const entities = findings.map(f => ({
    name: f.componentName,
    entityType: f.type, // "module", "class", "service", "database"
    observations: f.observations,
  }));

  await mcp__memory__create_entities({ entities });

  const relations = findings.flatMap(f =>
    f.dependencies.map(dep => ({
      from: f.componentName,
      to: dep,
      relationType: "depends_on",
    }))
  );

  if (relations.length > 0) {
    await mcp__memory__create_relations({ relations });
  }
}

// Session startup: check what we already know
async function loadCodebaseContext(projectName: string) {
  const graph = await mcp__memory__search_nodes({ query: projectName });
  if (graph.entities.length > 0) {
    console.log(`Loaded ${graph.entities.length} known entities for ${projectName}`);
    return graph;
  }
  return null; // Fresh start
}

Graph Storage Location

  • MCP server graph: MEMORY_FILE_PATH env var → default: in-memory (not persisted)
  • Local JSON graph: .claude/context/memory/knowledge-graph.json
  • Project-specific graphs: .claude/context/memory/kg-<project-name>.json

Anti-Patterns

  • Never use mutable names as entity IDs — names change, IDs must not
  • Never store observations without timestamps — temporal context is critical
  • Never use symmetric relation types like "related_to" — always directional
  • Never let graphs grow unbounded — prune entities older than 90 days with no recent observations
  • Never query the full graph for every lookup — use search_nodes with specific queries

Related Skills

  • memory-search — Semantic search over agent memory
  • context-compressor — Compress large knowledge graphs when approaching token limits
  • mcp-builder — Build custom MCP servers for specialized knowledge graph backends

Memory Protocol (MANDATORY)

Before starting any task:

# Check what the agent already knows
mcp__memory__search_nodes({ query: "<task domain>" })

After completing work:

// Record key discoveries as observations
mcp__memory__add_observations({
  observations: [
    {
      entityName: '<project-or-domain>',
      contents: ['<what was learned this session>'],
    },
  ],
});

ASSUME INTERRUPTION: Your context may reset. If it's not in memory, it didn't happen.

Weekly Installs
1
GitHub Stars
25
First Seen
Mar 22, 2026