← Back to blog
2025-12-1811 min read

Knowledge Graphs and Spreading Activation: How Context Surfaces

graphsalgorithmsarchitecture
knowledge-graph-spreading-activation.md

Knowledge Graphs and Spreading Activation

Vector search finds similar documents. Knowledge graphs find connected concepts. Together, they enable proactive context.

The Spreading Activation Model

In cognitive psychology, spreading activation explains how thinking of one concept primes related concepts:

```

Think: "dog"

Activates: pet → animal → bark → leash → walk → park

```

Activation spreads along associative links, weakening with distance. This is how context surfaces naturally.

Our Graph Structure

```rust

pub struct KnowledgeGraph {

nodes: HashMap<NodeId, Node>,

edges: HashMap<(NodeId, NodeId), Edge>,

}

pub struct Node {

id: NodeId,

content: String,

entity_type: EntityType, // Person, Concept, Event, etc.

activation: f32,

}

pub struct Edge {

source: NodeId,

target: NodeId,

relation: RelationType, // causes, contains, similar, etc.

strength: f32,

}

```

Entity Extraction

When a memory is stored, we extract entities:

```rust

fn extract_entities(text: &str) -> Vec<Entity> {

let ner_results = ner_model.predict(text);

ner_results.iter().map(|r| Entity {

text: r.text.clone(),

entity_type: classify_type(&r.label),

confidence: r.score,

}).collect()

}

```

Then we create/update graph nodes and edges between co-occurring entities.

Spreading Activation Algorithm

```rust

fn spread_activation(seed_nodes: &[NodeId], depth: usize) -> Vec<(NodeId, f32)> {

let mut activations: HashMap<NodeId, f32> = HashMap::new();

// Initialize seed nodes

for node in seed_nodes {

activations.insert(*node, 1.0);

}

// Spread for N iterations

for _ in 0..depth {

let mut new_activations = HashMap::new();

for (node, activation) in &activations {

for edge in self.edges_from(*node) {

let spread = activation * edge.strength * DECAY_FACTOR;

if spread > ACTIVATION_THRESHOLD {

*new_activations.entry(edge.target).or_insert(0.0) += spread;

}

}

}

for (node, act) in new_activations {

*activations.entry(node).or_insert(0.0) += act;

}

}

activations.into_iter().sorted_by_key(|(_, a)| OrderedFloat(-a)).collect()

}

```

Proactive Context Retrieval

When the user asks a question, we:

1. Extract entities from the query

2. Spread activation from those entities

3. Retrieve memories connected to activated nodes

4. Fuse with vector search results

```rust

fn proactive_context(query: &str) -> Vec<Memory> {

let query_entities = extract_entities(query);

let query_nodes = self.find_nodes(&query_entities);

let activated = spread_activation(&query_nodes, 3);

let graph_memories = activated.iter()

.flat_map(|(node, _)| self.memories_for_node(*node))

.collect();

let vector_memories = self.vectors.search(query, 10);

fuse_and_rank(graph_memories, vector_memories)

}

```

Example

Query: "How should I handle database errors?"

Entities extracted: [database, errors]

Spreading activation from "database":

PostgreSQL (strength 0.9)
SQL (strength 0.7)
connection pool (strength 0.6)

Spreading from "errors":

exception handling (strength 0.8)
logging (strength 0.5)
retry logic (strength 0.4)

Combined context surfaces: PostgreSQL error handling best practices, the project's existing retry logic, and relevant logging patterns.