Skip to main content

Memory Graph

The memory graph is DriftGuard's core data structure. It is a directed graph where nodes represent normalized text concepts and edges represent causal relationships.

Structure

Every record() call adds a three-node chain:

action node → feedback node → outcome node

Example:

"increase salt" → "too salty" → "dish ruined"

Semantic merging

When a new event arrives, the merge engine checks for similar existing nodes before creating new ones.

guard.record("increase salt", "too salty", "dish ruined")
guard.record("add more salt", "over-seasoned", "dish ruined")

The second call merges "add more salt" into the existing "increase salt" node because their cosine similarity exceeds the action threshold. The frequency counter increments. No duplicate node is created.

This means two paraphrased mistakes reinforce one memory rather than fragmenting the graph.

Node attributes

AttributeDescription
typeaction, feedback, or outcome
embeddingNormalized float32 vector
frequencyTimes merged or seen
first_seenUTC datetime of first record
last_seenUTC datetime of most recent reinforcement

Edge attributes

AttributeDescription
frequencyTimes this causal link was observed
weightEdge weight (default 1.0)
created_atUTC datetime of first creation

Graph stats

print(guard.stats())
# → {'nodes': 9, 'edges': 8}

Pruning

Call guard.prune() on a schedule to keep the graph healthy:

result = guard.prune()
print(result)
# → {
# 'status': 'pruned',
# 'before': {'nodes': 12, 'edges': 10},
# 'after': {'nodes': 9, 'edges': 8},
# 'details': {...}
# }

Pruning removes:

  1. Weak edges — seen fewer times than prune_edge_min_frequency (default 2)
  2. Stale nodes — not updated within prune_node_stale_days (default 60)
  3. Isolated nodes — no edges remaining after step 1 and 2

Persistence

The graph saves automatically after every record() call. It loads automatically when DriftGuard is instantiated.

See Storage Backends for JSON vs SQLite options.