By Sagar Shankaran, Founder of CallSphere
Implement associative memory networks for AI agents that link related memories together, using association graphs, link strength, spreading activation, and pattern-based retrieval.
Key takeaways
Traditional agent memory stores memories as independent items and retrieves them by similarity to a query. This misses a fundamental property of useful memory — connections. When you think of "coffee," you do not just retrieve the definition. You recall your favorite cafe, that meeting where coffee was spilled on a laptop, and the fact that your colleague is allergic to caffeine. These associations make memory powerful.
Associative memory networks model memories as nodes in a graph, with edges representing relationships between them. Retrieving one memory activates its neighbors, surfacing contextually relevant information that a flat search would miss.
Each memory becomes a node. Edges between nodes carry a weight representing association strength. Associations can be created explicitly (the agent recognizes a connection) or implicitly (two memories appear in the same conversation turn).
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
flowchart TD
MSG(["New message"])
WORKING["Working memory<br/>rolling window"]
EPISODIC[("Episodic memory<br/>past sessions")]
SEMANTIC[("Semantic memory<br/>facts and preferences")]
SUM["Summarizer<br/>compresses old turns"]
ROUTER{"Retrieve<br/>needed memories"}
PROMPT["Assembled context"]
LLM["LLM"]
UPD["Memory updater<br/>writes new facts"]
MSG --> WORKING --> ROUTER
ROUTER -->|Past sessions| EPISODIC
ROUTER -->|User facts| SEMANTIC
EPISODIC --> SUM --> PROMPT
SEMANTIC --> PROMPT
WORKING --> PROMPT --> LLM --> UPD
UPD --> EPISODIC
UPD --> SEMANTIC
style ROUTER fill:#4f46e5,stroke:#4338ca,color:#fff
style LLM fill:#f59e0b,stroke:#d97706,color:#1f2937
style EPISODIC fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
style SEMANTIC fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
from dataclasses import dataclass, field
from datetime import datetime
from collections import defaultdict
@dataclass
class MemoryNode:
id: str
content: str
created_at: datetime
metadata: dict = field(default_factory=dict)
class AssociativeMemory:
def __init__(self):
self.nodes: dict[str, MemoryNode] = {}
# edges[node_id] = {neighbor_id: weight}
self.edges: dict[str, dict[str, float]] = defaultdict(dict)
self._next_id = 0
def _gen_id(self) -> str:
self._next_id += 1
return f"mem_{self._next_id:06d}"
def add(self, content: str, **meta) -> str:
node_id = self._gen_id()
node = MemoryNode(
id=node_id,
content=content,
created_at=datetime.now(),
metadata=meta,
)
self.nodes[node_id] = node
return node_id
def associate(
self, id_a: str, id_b: str, weight: float = 0.5
):
"""Create or strengthen a bidirectional link."""
self.edges[id_a][id_b] = min(
self.edges[id_a].get(id_b, 0) + weight, 1.0
)
self.edges[id_b][id_a] = min(
self.edges[id_b].get(id_a, 0) + weight, 1.0
)
Manually linking every pair of related memories is impractical. The system should detect associations automatically based on shared context.
def auto_associate(
self,
new_id: str,
context_ids: list[str],
base_weight: float = 0.3,
):
"""Link a new memory to all memories in the current context."""
for ctx_id in context_ids:
if ctx_id != new_id and ctx_id in self.nodes:
self.associate(new_id, ctx_id, base_weight)
def associate_by_keywords(
self, node_id: str, weight: float = 0.2
):
"""Link memories that share significant words."""
node = self.nodes[node_id]
words = set(node.content.lower().split())
stopwords = {"the", "a", "an", "is", "are", "was", "to", "in", "of"}
keywords = words - stopwords
for other_id, other_node in self.nodes.items():
if other_id == node_id:
continue
other_words = set(other_node.content.lower().split())
overlap = keywords & (other_words - stopwords)
if len(overlap) >= 2:
self.associate(node_id, other_id, weight)
Association strength is not static. Links strengthen when both memories are retrieved together and weaken over time if they are not co-accessed. This mirrors Hebbian learning — neurons that fire together wire together.
def strengthen_link(self, id_a: str, id_b: str, amount: float = 0.1):
if id_b in self.edges.get(id_a, {}):
self.edges[id_a][id_b] = min(
self.edges[id_a][id_b] + amount, 1.0
)
self.edges[id_b][id_a] = min(
self.edges[id_b][id_a] + amount, 1.0
)
def decay_links(self, decay_factor: float = 0.95):
"""Weaken all links slightly — called periodically."""
for source in self.edges:
for target in list(self.edges[source]):
self.edges[source][target] *= decay_factor
if self.edges[source][target] < 0.01:
del self.edges[source][target]
Spreading activation is the core retrieval algorithm for associative memory. Starting from seed nodes that match the query, activation energy spreads outward along edges, with the energy attenuated by link weight at each hop.
def spreading_activation(
self,
seed_ids: list[str],
initial_energy: float = 1.0,
decay: float = 0.5,
max_hops: int = 3,
) -> dict[str, float]:
"""Return node_id -> activation_level for all reached nodes."""
activation: dict[str, float] = {}
frontier = {nid: initial_energy for nid in seed_ids}
for hop in range(max_hops):
next_frontier: dict[str, float] = {}
for node_id, energy in frontier.items():
current = activation.get(node_id, 0)
activation[node_id] = max(current, energy)
for neighbor, weight in self.edges.get(node_id, {}).items():
spread = energy * weight * decay
if spread > 0.01:
existing = next_frontier.get(neighbor, 0)
next_frontier[neighbor] = max(existing, spread)
frontier = next_frontier
return dict(
sorted(activation.items(), key=lambda x: x[1], reverse=True)
)
def retrieve(self, query: str, top_k: int = 5) -> list[MemoryNode]:
# Find seed nodes matching the query
seeds = [
nid for nid, node in self.nodes.items()
if query.lower() in node.content.lower()
]
if not seeds:
return []
activation = self.spreading_activation(seeds)
# Strengthen links between co-activated nodes
activated_ids = list(activation.keys())[:top_k]
for i, a in enumerate(activated_ids):
for b in activated_ids[i + 1:]:
self.strengthen_link(a, b, 0.05)
return [
self.nodes[nid]
for nid in activated_ids
if nid in self.nodes
]
Associative retrieval excels at surfacing non-obvious connections. If a user mentions a problem they had with "authentication," the agent retrieves not just memories about auth but also the related memory about the API key rotation they discussed last week, and the OAuth provider migration planned for next month — because those memories were linked during earlier conversations.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
Use link decay to prune weak associations over time. Set a minimum weight threshold below which edges are deleted. Also limit the maximum number of edges per node — when a node exceeds the limit, drop its weakest links.
The algorithm is bounded by max_hops and the branching factor. With link decay keeping the graph sparse, spreading activation typically visits fewer than 100 nodes even in stores with thousands of memories. For very large graphs, limit the frontier size at each hop.
Vector similarity finds memories with similar content. Associative retrieval finds memories with meaningful relationships — including those with very different content. The two approaches are complementary. Use vector search to find seed nodes, then spread activation to discover related context.
#AssociativeMemory #MemoryNetworks #GraphMemory #Python #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
The 2026 desktop AI agent landscape — ServiceNow Project Arc, Anthropic Claude offerings, OpenAI agents, and Google Mariner. A buyer's map.
Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.
An agentic-AI perspective on Anthropic Skills system, covering orchestration patterns, tool use, and how agent tooling fits production agent stacks.
Enterprise CIO Guide perspective on Comet's general-availability launch put an agentic browser in front of millions of consumers, and it works better than the demos suggested.
Enterprise CIO Guide perspective on Harvey AI's enterprise rollout numbers show legal agents have moved past the pilot stage at AmLaw 100 firms.
Enterprise CIO Guide perspective on Hippocratic AI's deployment numbers show healthcare voice agents are moving from pilot to production across major US health systems.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.