Skip to content

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_TO chains
  • “What solutions have we tried?” — follow SOLVES / ALTERNATIVE_TO paths
  • “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

Terminal window
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:

RangeMeaningExample
0.8 — 1.0Strong, verified relationshipA known root cause
0.5 — 0.7Moderate confidenceA likely connection
0.1 — 0.4Weak or speculativeA 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?”

RelationshipMeaning
parent_ofA is the parent of B
child_ofA is a child of B
part_ofA is part of B
containsA contains B

Causal — “What led to what?”

RelationshipMeaning
causesA directly causes B
triggersA triggers B to happen
leads_toA eventually leads to B
preventsA prevents B from occurring

Solution — “What fixes what?”

RelationshipMeaning
solvesA is a solution for B
addressesA partially addresses B
alternative_toA is an alternative to B
improvesA is an improvement on B

Context — “Where does this apply?”

RelationshipMeaning
occurs_inA happens in context B
applies_toA is relevant to B
works_withA works together with B
requiresA requires B

Learning — “How does knowledge evolve?”

RelationshipMeaning
builds_onA builds on the knowledge in B
contradictsA contradicts B
confirmsA confirms/validates B
supersedesA replaces B (B is outdated)
RelationshipMeaning
similar_toA is similar to B
variant_ofA is a variant/version of B
related_toA is generally related to B

Workflow — “What’s the sequence?”

RelationshipMeaning
followsA comes after B
depends_onA depends on B
enablesA enables B to happen
blocksA blocks/prevents B

Quality — “What’s the best approach?”

RelationshipMeaning
effective_forA is effective for B
preferred_overA is preferred over B
deprecated_byA 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 memory
associations = 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:

DirectionDescription
outgoingAssociations where the memory is the source
incomingAssociations where the memory is the target
bothAll associations (default)
# Only get associations pointing away from this memory
outgoing = await client.get_associations("mem_123", direction="outgoing")
# Only get associations pointing to this memory
incoming = 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 IDs

Traversal Parameters

ParameterDefaultDescription
max_depth3Maximum hops from the starting memory (1-5)
relationship_typesallFilter to specific relationship types
relationship_categoriesallFilter by high-level categories
directionbothTraversal direction (outgoing, incoming, both)
min_strength0.0Minimum edge strength to traverse
max_paths100Maximum paths to return
max_nodes50Maximum 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 memories
alice = 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 relationships
await 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 memories
error = 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 relationships
await 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