Knowledge Graph Features¶
Mnemex now includes comprehensive knowledge graph capabilities inspired by the reference MCP memory server, adapted for temporal memory management.
Overview¶
The knowledge graph provides:
- Entity Tracking: Tag memories with named entities
- Explicit Relations: Create directed links between memories
- Graph Navigation: Read the entire graph or access specific nodes
- Temporal Scoring: All graph operations respect memory decay
Core Concepts¶
Memories as Nodes¶
Each memory is a node in the graph with: - Content: The actual information stored - Entities: Named entities mentioned (e.g., people, projects, concepts) - Metadata: Tags, source, context - Temporal Properties: Score, use_count, last_used - Status: Active, promoted, or archived
Relations as Edges¶
Relations connect memories with: - Type: The nature of the relationship (e.g., "references", "similar_to", "follows_from") - Direction: From one memory to another - Strength: Weight of the relationship (0.0-1.0) - Metadata: Additional context about the relation
Knowledge Graph Structure¶
{
"memories": [
{
"id": "mem-123",
"content": "Project X deadline is Friday",
"entities": ["project-x"],
"tags": ["deadline", "work"],
"score": 0.82,
...
}
],
"relations": [
{
"from": "mem-123",
"to": "mem-456",
"type": "references",
"strength": 0.9
}
],
"stats": {
"total_memories": 150,
"total_relations": 45,
"avg_score": 0.42
}
}
New Tools¶
read_graph¶
Get the complete knowledge graph.
Use Cases: - Visualize the entire memory network - Export memories for analysis - Understand memory structure - Feed full context to LLM
Example:
{
"status": "active", // Filter: "active", "promoted", "archived", "all"
"include_scores": true, // Include temporal decay scores
"limit": 100 // Optional: limit number of memories
}
Response:
{
"success": true,
"memories": [
{
"id": "mem-123",
"content": "...",
"entities": ["project-x", "john"],
"tags": ["work"],
"score": 0.82,
"use_count": 5,
"age_days": 2.5
}
],
"relations": [
{
"from": "mem-123",
"to": "mem-456",
"type": "references",
"strength": 0.9
}
],
"stats": {
"total_memories": 150,
"total_relations": 45,
"avg_score": 0.42,
"avg_use_count": 3.2,
"status_filter": "active"
}
}
open_memories¶
Retrieve specific memories with their relations.
Use Cases: - Get detailed info about specific memories - Navigate the graph by following relations - Context assembly for LLM - Debugging and inspection
Example:
{
"memory_ids": ["mem-123", "mem-456"], // Single ID or array
"include_relations": true, // Include incoming/outgoing relations
"include_scores": true // Include temporal scores
}
Response:
{
"success": true,
"count": 2,
"memories": [
{
"id": "mem-123",
"content": "...",
"entities": ["project-x"],
"tags": ["work"],
"score": 0.82,
"relations": {
"outgoing": [
{
"to": "mem-456",
"type": "references",
"strength": 0.9
}
],
"incoming": [
{
"from": "mem-789",
"type": "similar_to",
"strength": 0.85
}
]
}
}
],
"not_found": []
}
create_relation¶
Create an explicit directed link between two memories.
Use Cases: - Manual linking of related information - Building knowledge graphs explicitly - Documenting dependencies - Creating semantic networks
Relation Types:
Common relation types:
- references
: One memory mentions/cites another
- follows_from
: Temporal sequence (this came after that)
- similar_to
: Semantic similarity
- contradicts
: Conflicting information
- elaborates_on
: Provides detail about another memory
- part_of
: Hierarchical relationship
Example:
{
"from_memory_id": "mem-123",
"to_memory_id": "mem-456",
"relation_type": "references",
"strength": 0.9,
"metadata": {
"context": "same project",
"created_by": "manual"
}
}
Response:
{
"success": true,
"relation_id": "rel-789",
"from": "mem-123",
"to": "mem-456",
"type": "references",
"strength": 0.9,
"message": "Relation created: mem-123 --[references]--> mem-456"
}
Usage Patterns¶
1. Entity-Based Navigation¶
Tag memories with entities for easier retrieval:
# Save with entities
save_memory({
"content": "John Smith joined Project X as lead engineer",
"entities": ["john-smith", "project-x"],
"tags": ["team", "project"]
})
# Later: Find all memories about john-smith
search_memory({
"query": "john-smith", # Searches entities too
"tags": ["team"]
})
# Or read full graph and filter client-side by entity
read_graph({"status": "active"})
2. Explicit Knowledge Chains¶
Build chains of related information:
# Memory 1: Initial decision
save_memory({
"content": "Decided to use PostgreSQL for analytics",
"entities": ["postgresql", "analytics-project"]
})
# -> Returns mem-123
# Memory 2: Follow-up
save_memory({
"content": "Set up PostgreSQL cluster with streaming replication",
"entities": ["postgresql", "infrastructure"]
})
# -> Returns mem-456
# Link them
create_relation({
"from_memory_id": "mem-456",
"to_memory_id": "mem-123",
"relation_type": "implements_decision"
})
# Later: Navigate the chain
open_memories({
"memory_ids": ["mem-123"],
"include_relations": true
})
# See that mem-456 implements this decision
3. Context Assembly¶
Build rich context by following graph:
# Start with a memory
memories = open_memories({
"memory_ids": ["mem-123"],
"include_relations": true
})
# Get related memories
related_ids = [r["to"] for r in memories["memories"][0]["relations"]["outgoing"]]
# Fetch them
related = open_memories({
"memory_ids": related_ids,
"include_relations": false
})
# Assemble full context for LLM
context = memories + related
4. Graph Visualization¶
Export graph for visualization:
graph = read_graph({
"status": "active",
"include_scores": true
})
# Convert to format for visualization tools:
# - Graphviz: dot format
# - D3.js: nodes/links arrays
# - Neo4j: Cypher import
# - Obsidian Canvas: .canvas format
Automatic vs Manual Relations¶
Automatic Relations¶
The clustering tool can auto-detect relations based on similarity:
# Find similar memories
clusters = cluster_memories({
"strategy": "similarity",
"threshold": 0.85
})
# STM can suggest relations:
# High similarity (>0.9) -> "similar_to"
# Moderate (>0.8) -> "related_to"
Manual Relations¶
Explicit relations you create:
create_relation({
"from_memory_id": "mem-123",
"to_memory_id": "mem-456",
"relation_type": "references"
})
Both types coexist. Manual relations have higher fidelity but require effort. Automatic relations provide coverage but may be noisy.
Integration with Temporal Decay¶
Graph features respect temporal properties:
1. Relations Survive Forgetting¶
If a memory is forgotten (GC'd), its relations are deleted (CASCADE).
But: if one memory is promoted and another forgotten, the relation is preserved in the promoted memory's metadata.
2. Scoring Affects Graph Traversal¶
When following relations, low-scoring memories are less prominent:
# Open memories with scores
open_memories({
"memory_ids": [...],
"include_scores": true
})
# Client can filter by score
memories_above_threshold = [m for m in result if m["score"] > 0.3]
3. Promotion Preserves Relations¶
When promoting a memory to Obsidian: - Relations are recorded in note frontmatter - Links to other memories (if also promoted) become wiki-links - Un-promoted relation targets are noted as STM references
Advanced: Graph Queries¶
While not yet built-in, you can build graph queries client-side:
graph = read_graph({"status": "active"})
# Find all memories that reference project-x
project_x_memories = [
m for m in graph["memories"]
if "project-x" in m["entities"]
]
# Find all 2-hop neighbors of a memory
def get_neighbors(memory_id, graph, hops=2):
neighbors = set()
current = {memory_id}
for _ in range(hops):
next_hop = set()
for mid in current:
rels = [r for r in graph["relations"] if r["from"] == mid]
next_hop.update(r["to"] for r in rels)
neighbors.update(next_hop)
current = next_hop
return neighbors
# Strongly connected components
# Topological sort
# Path finding
# etc.
Comparison to Reference Memory Server¶
Feature | Reference Memory | Mnemex |
---|---|---|
Primary Unit | Entity (person, org) | Memory (time-bound info) |
Observations | Attached to entities | N/A (content is primary) |
Relations | Between entities | Between memories |
Temporal | No decay | Exponential decay + promotion |
read_graph | ✅ | ✅ |
search_nodes | ✅ | ✅ (as search_memory) |
open_nodes | ✅ | ✅ (as open_memories) |
create_entities | ✅ | Via save_memory with entities |
create_relations | ✅ | ✅ |
Persistence | Permanent | Temporal → Optional promotion |
Best Practices¶
- Use Entities Consistently: Pick a naming scheme and stick to it
- Good:
"project-x"
,"john-smith"
-
Avoid:
"Project X"
,"John"
,"john"
-
Relation Types: Define a small set of relation types
- Too many types → hard to query
- Too few → lack of semantics
-
Recommended: 5-10 core types
-
Bidirectional Relations: Create both directions if needed
-
Metadata: Use relation metadata for context
-
Graph Size: Monitor graph growth
- Use
read_graph().stats
to track size - Run GC regularly to prune low-scoring memories
- Consider archiving old but important memories
Future Enhancements¶
Planned features:
- Graph Queries: Built-in query language for graph traversal
- Automatic Relation Detection: NER + coreference resolution
- Relation Types Ontology: Predefined semantic types
- Graph Embeddings: Node2Vec for memory embeddings based on structure
- Community Detection: Find clusters of related memories
- Temporal Graph Analysis: How relationships change over time