Knowledge Graph
MemoryLayer’s knowledge graph connects memories with typed relationships, enabling queries that vector similarity search alone cannot answer.
Why a Knowledge Graph?
Vector search finds memories with similar content. But some questions require following chains of relationships:
- “What caused this error?” — follow
CAUSES/LEADS_TOchains - “What solutions have we tried?” — follow
SOLVES/ALTERNATIVE_TOpaths - “Is this still the recommended approach?” — check
SUPERSEDES/DEPRECATED_BY
Creating Associations
Python
from memorylayer import RelationshipType
await client.associate( source_id="mem_problem_123", target_id="mem_solution_456", relationship=RelationshipType.SOLVES, strength=0.9)TypeScript
import { RelationshipType } from "@scitrera/memorylayer-sdk";
await client.associate( "mem-problem-123", "mem-solution-456", RelationshipType.SOLVES, 0.9);REST API
curl -X POST http://localhost:61001/v1/memories/mem_problem_123/associations \ -H "Content-Type: application/json" \ -d '{ "target_id": "mem_solution_456", "relationship": "solves", "strength": 0.9 }'Relationship Strength
The strength parameter is a float between 0.0 and 1.0 that indicates how strong the relationship is:
| Range | Meaning | Example |
|---|---|---|
| 0.8 — 1.0 | Strong, verified relationship | A known root cause |
| 0.5 — 0.7 | Moderate confidence | A likely connection |
| 0.1 — 0.4 | Weak or speculative | A possible relationship worth tracking |
Strength values affect graph traversal during recall. When expanding results through associations, the score of a discovered memory is weighted by the association strength:
discovered_score = parent_score * association_strength * (0.8 ^ depth)Typed Relationships
MemoryLayer provides 63 relationship types organized into 11 categories. Here are the most commonly used:
Hierarchical — “What’s the structure?”
| Relationship | Meaning |
|---|---|
parent_of | A is the parent of B |
child_of | A is a child of B |
part_of | A is part of B |
contains | A contains B |
Causal — “What led to what?”
| Relationship | Meaning |
|---|---|
causes | A directly causes B |
triggers | A triggers B to happen |
leads_to | A eventually leads to B |
prevents | A prevents B from occurring |
Solution — “What fixes what?”
| Relationship | Meaning |
|---|---|
solves | A is a solution for B |
addresses | A partially addresses B |
alternative_to | A is an alternative to B |
improves | A is an improvement on B |
Context — “Where does this apply?”
| Relationship | Meaning |
|---|---|
occurs_in | A happens in context B |
applies_to | A is relevant to B |
works_with | A works together with B |
requires | A requires B |
Learning — “How does knowledge evolve?”
| Relationship | Meaning |
|---|---|
builds_on | A builds on the knowledge in B |
contradicts | A contradicts B |
confirms | A confirms/validates B |
supersedes | A replaces B (B is outdated) |
Similarity — “What’s related?”
| Relationship | Meaning |
|---|---|
similar_to | A is similar to B |
variant_of | A is a variant/version of B |
related_to | A is generally related to B |
Workflow — “What’s the sequence?”
| Relationship | Meaning |
|---|---|
follows | A comes after B |
depends_on | A depends on B |
enables | A enables B to happen |
blocks | A blocks/prevents B |
Quality — “What’s the best approach?”
| Relationship | Meaning |
|---|---|
effective_for | A is effective for B |
preferred_over | A is preferred over B |
deprecated_by | A is deprecated in favor of B |
Additional categories include Temporal (before, after, during), Refinement (refines, refined_by, replaces, replaced_by), and Reference (references, referenced_by). See the Relationship Types Reference for the complete list.
Graph Traversal
Getting Associations
# Get all associations for a memoryassociations = await client.get_associations("mem_123", direction="both")
for assoc in associations: print(f"{assoc.relationship}: {assoc.target_id} (strength: {assoc.strength})")Filtering by Direction
Control which associations are returned:
| Direction | Description |
|---|---|
outgoing | Associations where the memory is the source |
incoming | Associations where the memory is the target |
both | All associations (default) |
# Only get associations pointing away from this memoryoutgoing = await client.get_associations("mem_123", direction="outgoing")
# Only get associations pointing to this memoryincoming = await client.get_associations("mem_123", direction="incoming")Multi-Hop Traversal
Follow chains of relationships across multiple memories with configurable depth, direction, and filtering:
const result = await client.traverseGraph("mem-123", { relationshipTypes: [RelationshipType.CAUSES, RelationshipType.LEADS_TO], maxDepth: 3, direction: "both", minStrength: 0.5,});
// result.paths contains all discovered paths// result.unique_nodes contains all visited memory IDsTraversal Parameters
| Parameter | Default | Description |
|---|---|---|
max_depth | 3 | Maximum hops from the starting memory (1-5) |
relationship_types | all | Filter to specific relationship types |
relationship_categories | all | Filter by high-level categories |
direction | both | Traversal direction (outgoing, incoming, both) |
min_strength | 0.0 | Minimum edge strength to traverse |
max_paths | 100 | Maximum paths to return |
max_nodes | 50 | Maximum nodes in result |
Graph-Enhanced Recall
During recall, MemoryLayer can automatically expand results by traversing associations from the initial vector search hits. This is controlled by two parameters:
results = await client.recall( query="What caused the payment errors?", include_associations=True, # Enable graph expansion traverse_depth=2, # Follow up to 2 hops max_expansion=50 # Discover up to 50 additional memories)Discovered memories are scored relative to their parent:
score = parent_score * association_strength * (0.8 ^ depth)This means directly-connected memories (depth 1) retain 80% of the parent’s score weighted by edge strength, while two-hop memories retain 64%, and so on.
MCP Tool
{ "start_memory_id": "mem_abc123", "relationship_types": ["causes", "triggers", "leads_to"], "max_depth": 3, "direction": "both", "max_paths": 50}Practical Use Cases
Entity Relationships
Model relationships between people, teams, and systems:
# Store entity memoriesalice = await client.remember("Alice is the tech lead for the payments team")bob = await client.remember("Bob is a senior engineer on the payments team")payments = await client.remember("Payments service handles all transaction processing")
# Create relationshipsawait client.associate(alice.id, payments.id, "applies_to", 0.9)await client.associate(bob.id, payments.id, "applies_to", 0.8)await client.associate(alice.id, bob.id, "works_with", 0.7)Cause-Effect Chains
Track how problems propagate through a system:
error = await client.remember("Retry timeout errors in payment service")cause = await client.remember("Connection pool exhaustion under load")root = await client.remember("Database connection leak in connection factory")
await client.associate(root.id, cause.id, "causes", 0.95)await client.associate(cause.id, error.id, "causes", 0.9)Querying “What caused the timeout errors?” now traverses the full causal chain:
[DB connection leak] ──causes──> [Pool exhaustion] ──causes──> [Timeout errors]Procedural Dependencies
Document step-by-step processes with dependencies:
step1 = await client.remember("Run database migrations before deploying")step2 = await client.remember("Deploy backend service containers")step3 = await client.remember("Run integration tests against staging")step4 = await client.remember("Update load balancer routing")
await client.associate(step2.id, step1.id, "depends_on", 1.0)await client.associate(step3.id, step2.id, "follows", 1.0)await client.associate(step4.id, step3.id, "follows", 1.0)Solution Evolution
Track how solutions evolve over time:
problem = await client.remember("High latency on product search API")fix_v1 = await client.remember("Added Redis cache for search results")fix_v2 = await client.remember("Switched to Elasticsearch for full-text search")
await client.associate(fix_v1.id, problem.id, "addresses", 0.6)await client.associate(fix_v2.id, problem.id, "solves", 0.9)await client.associate(fix_v2.id, fix_v1.id, "supersedes", 0.9)Example: Debugging Chain
[Retry timeout error] ├──CAUSED_BY──> [Connection pool exhaustion] │ ├──SOLVED_BY──> [Increased pool size to 50] │ └──SUPERSEDED_BY──> [Implemented circuit breaker] └──OCCURS_IN──> [Payment service under load]Building this graph:
# Store the memorieserror = await client.remember("Retry timeout errors in payment service", ...)cause = await client.remember("Connection pool exhaustion under load", ...)fix1 = await client.remember("Increased connection pool size to 50", ...)fix2 = await client.remember("Implemented circuit breaker pattern", ...)
# Create the relationshipsawait client.associate(cause.id, error.id, RelationshipType.CAUSES, 0.9)await client.associate(cause.id, fix1.id, RelationshipType.SOLVES, 0.7)await client.associate(fix1.id, fix2.id, RelationshipType.SUPERSEDES, 0.9)Now querying “What happened with retry errors?” traverses the graph and returns the full causal chain from problem to final solution.
Auditing
Find contradictions and inconsistencies in your knowledge graph:
# Auditing is available via the MCP tool `memory_audit`# or the REST API: GET /v1/workspaces/{workspace_id}/contradictions// MCP tool{ "memory_id": "mem_abc123", "auto_resolve": false}The audit checks for:
- Contradicting memories (A contradicts B)
- Superseded information that’s still being referenced
- Circular dependency chains