mentedb_cognitive/
interference.rs1use mentedb_core::MemoryNode;
2use mentedb_core::types::MemoryId;
3
4#[derive(Debug, Clone)]
5pub struct InterferencePair {
6 pub memory_a: MemoryId,
7 pub memory_b: MemoryId,
8 pub similarity: f32,
9 pub disambiguation: String,
10}
11
12pub struct InterferenceDetector {
13 similarity_threshold: f32,
14}
15
16fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
17 if a.len() != b.len() || a.is_empty() {
18 return 0.0;
19 }
20 let mut dot = 0.0f32;
21 let mut norm_a = 0.0f32;
22 let mut norm_b = 0.0f32;
23 for i in 0..a.len() {
24 dot += a[i] * b[i];
25 norm_a += a[i] * a[i];
26 norm_b += b[i] * b[i];
27 }
28 let denom = norm_a.sqrt() * norm_b.sqrt();
29 if denom == 0.0 { 0.0 } else { dot / denom }
30}
31
32impl InterferenceDetector {
33 pub fn new(similarity_threshold: f32) -> Self {
34 Self {
35 similarity_threshold,
36 }
37 }
38
39 pub fn detect_interference(&self, memories: &[MemoryNode]) -> Vec<InterferencePair> {
40 let mut pairs = Vec::new();
41 for i in 0..memories.len() {
42 for j in (i + 1)..memories.len() {
43 let sim = cosine_similarity(&memories[i].embedding, &memories[j].embedding);
44 if sim > self.similarity_threshold && memories[i].content != memories[j].content {
45 pairs.push(InterferencePair {
46 memory_a: memories[i].id,
47 memory_b: memories[j].id,
48 similarity: sim,
49 disambiguation: self.generate_disambiguation(&memories[i], &memories[j]),
50 });
51 }
52 }
53 }
54 pairs
55 }
56
57 pub fn generate_disambiguation(&self, a: &MemoryNode, b: &MemoryNode) -> String {
58 format!(
59 "Note: Memory A: \"{}\" (created {}), Memory B: \"{}\" (created {}). Do not confuse.",
60 &a.content, a.created_at, &b.content, b.created_at
61 )
62 }
63
64 pub fn arrange_with_separation(
66 memories: Vec<MemoryId>,
67 pairs: &[InterferencePair],
68 ) -> Vec<MemoryId> {
69 if memories.is_empty() || pairs.is_empty() {
70 return memories;
71 }
72
73 let conflicts: ahash::AHashSet<(MemoryId, MemoryId)> = pairs
74 .iter()
75 .flat_map(|p| [(p.memory_a, p.memory_b), (p.memory_b, p.memory_a)])
76 .collect();
77
78 let mut result: Vec<MemoryId> = Vec::with_capacity(memories.len());
79 let mut remaining: std::collections::VecDeque<MemoryId> = memories.into();
80
81 while let Some(first) = remaining.pop_front() {
83 if result.is_empty() {
84 result.push(first);
85 continue;
86 }
87
88 let last = *result.last().unwrap();
89 if !conflicts.contains(&(last, first)) {
90 result.push(first);
91 } else {
92 let mut found = false;
94 for i in 0..remaining.len() {
95 if !conflicts.contains(&(last, remaining[i])) {
96 let item = remaining.remove(i).unwrap();
97 result.push(item);
98 remaining.push_front(first); found = true;
100 break;
101 }
102 }
103 if !found {
104 result.push(first);
106 }
107 }
108 }
109
110 result
111 }
112}
113
114impl Default for InterferenceDetector {
115 fn default() -> Self {
116 Self::new(0.8)
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use mentedb_core::memory::MemoryType;
124 use mentedb_core::types::AgentId;
125
126 fn make_memory(content: &str, embedding: Vec<f32>) -> MemoryNode {
127 MemoryNode::new(
128 AgentId::new(),
129 MemoryType::Semantic,
130 content.to_string(),
131 embedding,
132 )
133 }
134
135 #[test]
136 fn test_detect_interference() {
137 let a = make_memory("Project Alpha uses React", vec![1.0, 0.0, 0.0]);
138 let b = make_memory("Project Beta uses Vue", vec![0.99, 0.1, 0.0]);
139 let c = make_memory("Cooking recipe for pasta", vec![0.0, 0.0, 1.0]);
140
141 let detector = InterferenceDetector::default();
142 let pairs = detector.detect_interference(&[a, b, c]);
143 assert_eq!(pairs.len(), 1);
145 assert!(pairs[0].disambiguation.contains("Do not confuse"));
146 }
147
148 #[test]
149 fn test_arrange_separation() {
150 let ids: Vec<MemoryId> = (0..4).map(|_| MemoryId::new()).collect();
151 let pairs = vec![InterferencePair {
152 memory_a: ids[0],
153 memory_b: ids[1],
154 similarity: 0.9,
155 disambiguation: String::new(),
156 }];
157
158 let arranged = InterferenceDetector::arrange_with_separation(ids.clone(), &pairs);
159 for w in arranged.windows(2) {
161 assert!(
162 !(w[0] == ids[0] && w[1] == ids[1]) && !(w[0] == ids[1] && w[1] == ids[0]),
163 "Interference pair should not be adjacent"
164 );
165 }
166 }
167}