AthelaPerk commited on
Commit
f84b794
ยท
verified ยท
1 Parent(s): 562ad5a

v4: SLM-inspired architecture with three-tier memory, neural links, self-tuning

Browse files
Files changed (1) hide show
  1. mnemo.py +927 -241
mnemo.py CHANGED
@@ -1,14 +1,23 @@
1
  #!/usr/bin/env python3
2
  """
3
- Mnemo v3 TUNED - Final Version with Optimized Parameters
4
- =========================================================
5
 
6
- Based on benchmark testing:
7
- - Optimal similarity threshold: 0.4-0.5 (not 0.6)
8
- - Quality threshold: 0.35
9
- - Context window detection enabled
10
- - Relevance re-ranking enabled
11
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  """
13
 
14
  import hashlib
@@ -16,10 +25,13 @@ import time
16
  import re
17
  import threading
18
  import numpy as np
19
- from typing import Dict, List, Optional, Tuple, Any, Callable
20
  from dataclasses import dataclass, field
21
  from collections import defaultdict
 
 
22
 
 
23
  try:
24
  import faiss
25
  HAS_FAISS = True
@@ -39,138 +51,706 @@ except ImportError:
39
  HAS_BM25 = False
40
 
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  @dataclass
43
  class Memory:
 
44
  id: str
45
  content: str
46
  embedding: np.ndarray
 
47
  namespace: str = "default"
48
- quality_score: float = 1.0
 
 
 
 
 
 
49
  access_count: int = 0
50
- usefulness_score: float = 0.5
 
 
 
 
 
51
  metadata: Dict = field(default_factory=dict)
 
 
 
 
 
 
 
 
 
52
  created_at: float = field(default_factory=time.time)
 
 
53
 
54
 
55
- @dataclass
56
  class SearchResult:
 
57
  id: str
58
  content: str
59
  score: float
60
- relevance_score: float = 0.0
 
61
  strategy_scores: Dict[str, float] = field(default_factory=dict)
62
  metadata: Dict = field(default_factory=dict)
63
 
64
 
65
- # Smart injection signals
66
- MEMORY_INJECTION_SIGNALS = [
67
- "previous", "earlier", "before", "you said", "you mentioned",
68
- "as you", "based on", "using your", "your analysis", "your framework",
69
- "we discussed", "we analyzed", "refer to", "from your",
70
- "compare", "contrast", "synthesize", "combine", "integrate",
71
- "apply your", "using your", "based on your",
72
- "you previously", "your earlier", "you have analyzed"
73
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
 
 
 
75
 
76
- def should_inject_memory(query: str, context: str = "", conversation_history: str = "") -> Tuple[bool, str]:
77
- """Smart context-check with 90% accuracy"""
78
- combined = (query + " " + context).lower()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
- for signal in MEMORY_INJECTION_SIGNALS:
81
- if signal in combined:
82
- # Check if conversation already has context
83
- if conversation_history and len(conversation_history.split()) > 500:
84
- query_kws = set(query.lower().split()) - {"the", "a", "is", "are", "to", "of"}
85
- if sum(1 for kw in query_kws if kw in conversation_history.lower()) >= len(query_kws) * 0.7:
86
- return False, "context_window_has_info"
87
- return True, f"signal:{signal}"
 
 
 
 
 
 
 
 
 
 
 
88
 
89
- return False, "no_signal"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
 
 
 
91
 
92
- def estimate_quality(content: str) -> float:
93
- """Estimate content quality before storing"""
94
- score = 0.5
95
- words = len(content.split())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
- if words < 5:
98
- score -= 0.3
99
- elif words > 20:
100
- score += 0.1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
- if any(r in content.lower() for r in ["because", "therefore", "shows", "indicates"]):
103
- score += 0.2
 
 
 
 
 
 
 
 
 
 
 
104
 
105
- if re.search(r'\d+', content):
106
- score += 0.1
 
 
 
 
 
 
 
 
 
107
 
108
- if any(v in content.lower() for v in ["something", "stuff", "maybe"]):
109
- score -= 0.2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
- if any(e in content.lower() for e in ["error", "failed", "wrong"]):
112
- score -= 0.3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
- return max(0.0, min(1.0, score))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
 
117
- def rerank_by_relevance(query: str, results: List[SearchResult]) -> List[SearchResult]:
118
- """Re-rank by task relevance"""
119
- query_lower = query.lower()
120
- query_kws = set(query_lower.split()) - {"the", "a", "is", "are", "to", "of"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
- for result in results:
123
- content_lower = result.content.lower()
124
- content_words = set(content_lower.split())
125
 
126
- overlap = len(query_kws & content_words) / max(len(query_kws), 1)
 
 
 
 
127
 
128
- qa_bonus = 0
129
- if "why" in query_lower and "because" in content_lower:
130
- qa_bonus = 0.2
131
- if "compare" in query_lower and any(w in content_lower for w in ["differ", "similar", "both"]):
132
- qa_bonus = 0.3
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
- result.relevance_score = overlap * 0.5 + qa_bonus + result.score * 0.3
 
 
 
 
 
 
135
 
136
- results.sort(key=lambda x: x.relevance_score, reverse=True)
137
- return results
 
 
 
 
 
 
 
 
138
 
 
 
 
139
 
140
  class Mnemo:
141
  """
142
- Mnemo v3 TUNED - Optimized AI Memory System
143
 
144
- Tuned parameters based on benchmarks:
145
- - similarity_threshold: 0.45 (optimal range 0.4-0.5)
146
- - quality_threshold: 0.35
147
- """
 
148
 
149
- # TUNED DEFAULTS
150
- DEFAULT_SIMILARITY_THRESHOLD = 0.45 # TUNED from 0.6
151
- DEFAULT_QUALITY_THRESHOLD = 0.35 # TUNED from 0.4
152
 
153
  STOP_WORDS = {"a", "an", "the", "is", "are", "was", "were", "be", "been",
154
  "to", "of", "in", "for", "on", "with", "at", "by", "from",
155
  "and", "but", "or", "not", "this", "that", "i", "me", "my"}
156
 
157
- def __init__(self,
158
- embedding_dim: int = 384,
159
- similarity_threshold: float = 0.45, # TUNED
160
- quality_threshold: float = 0.35, # TUNED
161
- semantic_weight: float = 0.5,
162
- bm25_weight: float = 0.3,
163
- graph_weight: float = 0.2):
164
-
165
  self.embedding_dim = embedding_dim
166
- self.similarity_threshold = similarity_threshold
167
- self.quality_threshold = quality_threshold
168
- self.semantic_weight = semantic_weight
169
- self.bm25_weight = bm25_weight
170
- self.graph_weight = graph_weight
171
-
172
- self.memories: Dict[str, Memory] = {}
173
- self.namespaces: Dict[str, List[str]] = defaultdict(list)
174
  self._embeddings: List[np.ndarray] = []
175
  self._ids: List[str] = []
176
 
@@ -179,35 +759,40 @@ class Mnemo:
179
  else:
180
  self.index = None
181
 
 
182
  self.bm25 = None
183
  self._tokenized_docs: List[List[str]] = []
184
 
 
185
  if HAS_NETWORKX:
186
  self.graph = nx.DiGraph()
187
  else:
188
  self.graph = None
189
 
190
- self._doc_boosts: Dict[str, float] = defaultdict(float)
191
- self._query_doc_scores: Dict[str, Dict[str, float]] = defaultdict(dict)
192
-
193
  self._cache: Dict[str, Any] = {}
194
  self._cache_lock = threading.Lock()
195
 
 
196
  self.stats = {
197
- "adds": 0, "adds_rejected": 0, "searches": 0,
198
- "results_filtered": 0, "feedback": 0,
199
- "cache_hits": 0, "cache_misses": 0,
200
- "injections_triggered": 0, "injections_skipped": 0
 
201
  }
202
 
203
  def _get_embedding(self, text: str) -> np.ndarray:
 
204
  cache_key = f"emb:{hashlib.md5(text.encode()).hexdigest()}"
 
205
  with self._cache_lock:
206
  if cache_key in self._cache:
207
  self.stats["cache_hits"] += 1
208
  return self._cache[cache_key]
209
  self.stats["cache_misses"] += 1
210
 
 
211
  embedding = np.zeros(self.embedding_dim, dtype=np.float32)
212
  words = text.lower().split()
213
  for i, word in enumerate(words):
@@ -223,23 +808,49 @@ class Mnemo:
223
 
224
  return embedding
225
 
226
- def should_inject(self, query: str, context: str = "", conversation_history: str = "") -> bool:
227
- should, reason = should_inject_memory(query, context, conversation_history)
 
 
228
 
229
- if should:
230
- self.stats["injections_triggered"] += 1
231
- else:
232
- self.stats["injections_skipped"] += 1
 
 
 
233
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  return should
235
 
236
  def add(self, content: str, namespace: str = "default",
237
  metadata: Dict = None, skip_quality_check: bool = False) -> Optional[str]:
 
 
 
238
 
239
- quality = estimate_quality(content)
240
-
241
- if not skip_quality_check and quality < self.quality_threshold:
242
  self.stats["adds_rejected"] += 1
 
243
  return None
244
 
245
  memory_id = f"mem_{hashlib.md5(content.encode()).hexdigest()[:8]}"
@@ -254,8 +865,10 @@ class Mnemo:
254
  metadata=metadata or {}
255
  )
256
 
257
- self.memories[memory_id] = memory
258
- self.namespaces[namespace].append(memory_id)
 
 
259
  self._embeddings.append(embedding)
260
  self._ids.append(memory_id)
261
 
@@ -267,26 +880,53 @@ class Mnemo:
267
  if HAS_BM25:
268
  self.bm25 = BM25Okapi(self._tokenized_docs)
269
 
270
- if HAS_NETWORKX and self.graph is not None:
271
- self.graph.add_node(memory_id, content=content, namespace=namespace)
272
- keywords = [w for w in tokens if w not in self.STOP_WORDS and len(w) > 2][:5]
273
- for kw in keywords:
274
- entity_id = f"entity_{kw}"
275
- if not self.graph.has_node(entity_id):
276
- self.graph.add_node(entity_id, type="keyword")
277
- self.graph.add_edge(memory_id, entity_id, relation="contains")
278
 
279
  self.stats["adds"] += 1
 
 
280
  return memory_id
281
 
282
- def search(self, query: str, top_k: int = 5, namespace: Optional[str] = None) -> List[SearchResult]:
283
- if not self.memories:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  return []
285
 
286
  self.stats["searches"] += 1
287
  query_embedding = self._get_embedding(query)
 
288
 
289
- # Semantic search
290
  semantic_scores = {}
291
  if HAS_FAISS and self.index is not None and self.index.ntotal > 0:
292
  k = min(top_k * 3, self.index.ntotal)
@@ -295,10 +935,10 @@ class Mnemo:
295
  if 0 <= idx < len(self._ids):
296
  semantic_scores[self._ids[idx]] = float(score)
297
  else:
298
- for mem_id, embedding in zip(self._ids, self._embeddings):
299
- semantic_scores[mem_id] = float(np.dot(query_embedding, embedding))
300
 
301
- # BM25
302
  bm25_scores = {}
303
  if HAS_BM25 and self.bm25 is not None:
304
  tokens = query.lower().split()
@@ -308,64 +948,67 @@ class Mnemo:
308
  if score > 0.1 * max_score:
309
  bm25_scores[self._ids[idx]] = float(score / max_score)
310
 
311
- # Graph
312
- graph_scores = {}
313
- if HAS_NETWORKX and self.graph is not None:
314
- keywords = [w for w in query.lower().split() if w not in self.STOP_WORDS and len(w) > 2]
315
- for kw in keywords:
316
- entity_id = f"entity_{kw}"
317
- if self.graph.has_node(entity_id):
318
- for neighbor in self.graph.predecessors(entity_id):
319
- if neighbor.startswith("mem_"):
320
- graph_scores[neighbor] = graph_scores.get(neighbor, 0) + 0.5
321
 
322
- # Combine
323
- all_ids = set(semantic_scores.keys()) | set(bm25_scores.keys()) | set(graph_scores.keys())
324
 
325
  if namespace:
326
- all_ids = all_ids & set(self.namespaces.get(namespace, []))
 
 
 
327
 
328
  results = []
329
  for mem_id in all_ids:
330
  strat = {
331
  "semantic": semantic_scores.get(mem_id, 0),
332
  "bm25": bm25_scores.get(mem_id, 0),
333
- "graph": graph_scores.get(mem_id, 0)
334
  }
335
 
336
  combined = (
337
- self.semantic_weight * strat["semantic"] +
338
- self.bm25_weight * strat["bm25"] +
339
- self.graph_weight * strat["graph"]
340
  )
341
 
342
- # Feedback adjustment
343
- query_key = " ".join(sorted(set(query.lower().split()))[:5])
344
- combined += self._doc_boosts.get(mem_id, 0) * 0.1
345
- combined += self._query_doc_scores.get(query_key, {}).get(mem_id, 0) * 0.2
346
-
347
- memory = self.memories.get(mem_id)
348
- if memory:
349
- combined *= (0.5 + 0.5 * memory.quality_score)
 
 
 
 
 
 
 
 
 
350
 
351
- if combined >= self.similarity_threshold:
352
- memory.access_count += 1
353
- results.append(SearchResult(
354
- id=mem_id, content=memory.content, score=combined,
355
- strategy_scores=strat, metadata=memory.metadata
356
- ))
357
- else:
358
- self.stats["results_filtered"] += 1
359
 
360
  results.sort(key=lambda x: x.score, reverse=True)
361
-
362
- # Re-rank
363
- if results:
364
- results = rerank_by_relevance(query, results)
365
-
366
  return results[:top_k]
367
 
368
- def get_context(self, query: str, top_k: int = 3, namespace: Optional[str] = None) -> str:
 
 
369
  results = self.search(query, top_k=top_k, namespace=namespace)
370
 
371
  if not results:
@@ -373,112 +1016,155 @@ class Mnemo:
373
 
374
  parts = ["[RELEVANT CONTEXT FROM MEMORY]"]
375
  for r in results:
376
- parts.append(f"โ€ข {r.content}")
 
377
  parts.append("[END CONTEXT]\n")
378
 
379
  return "\n".join(parts)
380
 
381
  def feedback(self, query: str, memory_id: str, relevance: float):
 
382
  relevance = max(-1, min(1, relevance))
383
- self._doc_boosts[memory_id] += 0.1 * relevance
384
-
385
- query_key = " ".join(sorted(set(query.lower().split()))[:5])
386
- current = self._query_doc_scores[query_key].get(memory_id, 0)
387
- self._query_doc_scores[query_key][memory_id] = current + 0.1 * relevance
388
-
389
- if memory_id in self.memories:
390
- mem = self.memories[memory_id]
391
- mem.usefulness_score = 0.7 * mem.usefulness_score + 0.3 * ((relevance + 1) / 2)
392
- if mem.usefulness_score < 0.3:
393
- mem.quality_score *= 0.9
394
-
395
- self.stats["feedback"] += 1
396
-
397
- def get(self, memory_id: str) -> Optional[Memory]:
398
- return self.memories.get(memory_id)
399
-
400
- def delete(self, memory_id: str) -> bool:
401
- if memory_id in self.memories:
402
- mem = self.memories[memory_id]
403
- if mem.namespace in self.namespaces:
404
- try:
405
- self.namespaces[mem.namespace].remove(memory_id)
406
- except ValueError:
407
- pass
408
- del self.memories[memory_id]
409
- return True
410
- return False
411
 
412
- def list_all(self, namespace: Optional[str] = None) -> List[Memory]:
413
- if namespace:
414
- return [self.memories[mid] for mid in self.namespaces.get(namespace, []) if mid in self.memories]
415
- return list(self.memories.values())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
 
417
  def get_stats(self) -> Dict:
 
418
  return {
419
- "total_memories": len(self.memories),
420
- "namespaces": {ns: len(ids) for ns, ids in self.namespaces.items()},
421
- "adds": self.stats["adds"],
422
- "adds_rejected": self.stats["adds_rejected"],
423
- "searches": self.stats["searches"],
424
- "results_filtered": self.stats["results_filtered"],
425
- "feedback": self.stats["feedback"],
426
- "similarity_threshold": self.similarity_threshold,
427
- "quality_threshold": self.quality_threshold,
428
- "has_faiss": HAS_FAISS,
429
- "has_bm25": HAS_BM25,
430
- "has_graph": HAS_NETWORKX
431
  }
432
 
433
- def clear(self, namespace: Optional[str] = None):
434
- if namespace:
435
- for mid in list(self.namespaces.get(namespace, [])):
436
- self.delete(mid)
437
- else:
438
- self.memories.clear()
439
- self.namespaces.clear()
440
- self._embeddings.clear()
441
- self._ids.clear()
442
- self._tokenized_docs.clear()
443
- self.bm25 = None
444
- self._cache.clear()
445
- if HAS_FAISS:
446
- self.index = faiss.IndexFlatIP(self.embedding_dim)
447
- if HAS_NETWORKX:
448
- self.graph = nx.DiGraph()
449
 
450
  def __len__(self):
451
- return len(self.memories)
452
 
453
  def __repr__(self):
454
- return f"Mnemo(memories={len(self.memories)}, threshold={self.similarity_threshold})"
455
 
456
 
 
 
 
 
457
  def demo():
458
- print("="*60)
459
- print("MNEMO v3 TUNED - Optimized Parameters")
460
- print("="*60)
 
 
 
 
 
 
 
 
461
 
462
- m = Mnemo() # Uses tuned defaults
463
- print(f"\nโœ“ {m}")
464
- print(f" Similarity threshold: {m.similarity_threshold}")
465
- print(f" Quality threshold: {m.quality_threshold}")
 
 
 
 
 
466
 
467
- # Quick test
468
- m.add("User prefers Python because it has clean syntax")
469
- m.add("Previous analysis showed gender bias patterns")
470
- m.add("Framework has 5 checkpoints for detection")
471
 
472
- print(f"\nโœ“ Added {len(m)} memories")
 
 
 
 
 
 
 
473
 
474
- results = m.search("previous analysis", top_k=2)
475
- print(f"โœ“ Search returned {len(results)} results")
 
 
 
476
 
 
 
 
477
  for r in results:
478
- print(f" [{r.id}] score={r.score:.3f}: {r.content[:50]}...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
479
 
480
- print("\n" + "="*60)
481
- print("โœ… Ready for production!")
 
482
 
483
 
484
  if __name__ == "__main__":
 
1
  #!/usr/bin/env python3
2
  """
3
+ Mnemo v4: SLM-Inspired Architecture
4
+ ====================================
5
 
6
+ Implements key SLM architecture features with parameter adjustments
7
+ based on Mnemo benchmark findings.
 
 
 
8
 
9
+ SLM Features Implemented:
10
+ 1. Three-Tiered Memory (Working โ†’ Token โ†’ Semantic)
11
+ 2. Promotion/Demotion Algorithms
12
+ 3. Neural Link Types (8 types with decay)
13
+ 4. Self-Tuning Parameters
14
+ 5. Memory Utility Predictor (NEW - from benchmarks)
15
+
16
+ Key Parameter Adjustments (from benchmarks):
17
+ - Semantic threshold: 0.65 โ†’ 0.50 (SLM was too high)
18
+ - Quality acceptance: 0.30 โ†’ 0.50 (SLM too permissive)
19
+ - Promotion threshold: 0.65 โ†’ 0.55 (faster promotion)
20
+ - Link pruning: 60 days โ†’ 30 days (faster cleanup)
21
  """
22
 
23
  import hashlib
 
25
  import re
26
  import threading
27
  import numpy as np
28
+ from typing import Dict, List, Optional, Tuple, Any, Set
29
  from dataclasses import dataclass, field
30
  from collections import defaultdict
31
+ from enum import Enum
32
+ import json
33
 
34
+ # Optional imports
35
  try:
36
  import faiss
37
  HAS_FAISS = True
 
51
  HAS_BM25 = False
52
 
53
 
54
+ # =============================================================================
55
+ # ENUMS AND CONSTANTS (from SLM spec)
56
+ # =============================================================================
57
+
58
+ class MemoryTier(Enum):
59
+ """Three-tiered memory hierarchy from SLM"""
60
+ WORKING = "working" # 32MB, <1ms, current context
61
+ TOKEN = "token" # 100-250 items, 1-10ms, compressed
62
+ SEMANTIC = "semantic" # Persistent, 10-100ms, full knowledge
63
+
64
+
65
+ class LinkType(Enum):
66
+ """Eight link types from SLM Neural Link system"""
67
+ DIRECT_REFERENCE = "direct_reference" # Explicit reference
68
+ SEMANTIC_SIMILARITY = "semantic_similarity" # Vector similarity
69
+ CO_OCCURRENCE = "co_occurrence" # Appear together
70
+ HIERARCHICAL = "hierarchical" # Parent-child
71
+ TEMPORAL = "temporal" # Time-based
72
+ CAUSAL = "causal" # Cause-effect
73
+ CROSS_DOMAIN = "cross_domain" # Different domains
74
+ ASSOCIATIVE = "associative" # General association
75
+
76
+
77
+ # SLM Link Type Properties (adjusted based on benchmarks)
78
+ LINK_PROPERTIES = {
79
+ LinkType.DIRECT_REFERENCE: {
80
+ "creation_threshold": 0.85, # SLM: 0.90
81
+ "initial_strength": 0.90,
82
+ "decay_rate": 0.005, # per day
83
+ "usage_boost": 0.05
84
+ },
85
+ LinkType.SEMANTIC_SIMILARITY: {
86
+ "creation_threshold": 0.50, # SLM: 0.65, ADJUSTED from benchmarks
87
+ "initial_strength": 0.75,
88
+ "decay_rate": 0.01,
89
+ "usage_boost": 0.03
90
+ },
91
+ LinkType.CO_OCCURRENCE: {
92
+ "creation_threshold": 0.60,
93
+ "initial_strength": 0.70,
94
+ "decay_rate": 0.015,
95
+ "usage_boost": 0.04
96
+ },
97
+ LinkType.HIERARCHICAL: {
98
+ "creation_threshold": 0.80, # SLM: 0.85
99
+ "initial_strength": 0.85,
100
+ "decay_rate": 0.003,
101
+ "usage_boost": 0.02
102
+ },
103
+ LinkType.TEMPORAL: {
104
+ "creation_threshold": 0.55,
105
+ "initial_strength": 0.65,
106
+ "decay_rate": 0.02,
107
+ "usage_boost": 0.05
108
+ },
109
+ LinkType.CAUSAL: {
110
+ "creation_threshold": 0.75,
111
+ "initial_strength": 0.80,
112
+ "decay_rate": 0.005,
113
+ "usage_boost": 0.03
114
+ },
115
+ LinkType.CROSS_DOMAIN: {
116
+ "creation_threshold": 0.70, # SLM: 0.80
117
+ "initial_strength": 0.65, # SLM: 0.70
118
+ "decay_rate": 0.008,
119
+ "usage_boost": 0.04
120
+ },
121
+ LinkType.ASSOCIATIVE: {
122
+ "creation_threshold": 0.45, # Permissive for exploration
123
+ "initial_strength": 0.60,
124
+ "decay_rate": 0.025,
125
+ "usage_boost": 0.06
126
+ }
127
+ }
128
+
129
+
130
+ # =============================================================================
131
+ # DATA CLASSES
132
+ # =============================================================================
133
+
134
  @dataclass
135
  class Memory:
136
+ """Memory unit with SLM-style metadata"""
137
  id: str
138
  content: str
139
  embedding: np.ndarray
140
+ tier: MemoryTier = MemoryTier.SEMANTIC
141
  namespace: str = "default"
142
+
143
+ # Quality and relevance (SLM quality gates)
144
+ quality_score: float = 0.5
145
+ relevance_score: float = 0.5
146
+ confidence: float = 0.5
147
+
148
+ # Access tracking (for promotion/demotion)
149
  access_count: int = 0
150
+ last_accessed: float = field(default_factory=time.time)
151
+ created_at: float = field(default_factory=time.time)
152
+
153
+ # SLM priority decay
154
+ priority: float = 1.0
155
+
156
  metadata: Dict = field(default_factory=dict)
157
+
158
+
159
+ @dataclass
160
+ class NeuralLink:
161
+ """SLM Neural Link between memories"""
162
+ source_id: str
163
+ target_id: str
164
+ link_type: LinkType
165
+ strength: float
166
  created_at: float = field(default_factory=time.time)
167
+ last_traversed: float = field(default_factory=time.time)
168
+ traversal_count: int = 0
169
 
170
 
171
+ @dataclass
172
  class SearchResult:
173
+ """Search result with multi-strategy scores"""
174
  id: str
175
  content: str
176
  score: float
177
+ tier: MemoryTier = MemoryTier.SEMANTIC
178
+ link_path: List[str] = field(default_factory=list)
179
  strategy_scores: Dict[str, float] = field(default_factory=dict)
180
  metadata: Dict = field(default_factory=dict)
181
 
182
 
183
+ # =============================================================================
184
+ # MEMORY UTILITY PREDICTOR (NEW - from Mnemo benchmarks)
185
+ # =============================================================================
186
+
187
+ class MemoryUtilityPredictor:
188
+ """
189
+ Predicts whether memory injection will help or hurt.
190
+
191
+ Key finding from benchmarks:
192
+ - Within-conversation: Memory often HURTS (-3 to -12 pts)
193
+ - Cross-session: Memory HELPS (+2 pts on dependent questions)
194
+ """
195
+
196
+ # Signals that indicate memory should be used
197
+ INJECTION_SIGNALS = [
198
+ "previous", "earlier", "before", "you said", "you mentioned",
199
+ "as you", "based on", "using your", "your analysis", "your framework",
200
+ "we discussed", "we analyzed", "refer to", "from your",
201
+ "compare", "contrast", "synthesize", "combine", "integrate",
202
+ "apply your", "using your", "based on your",
203
+ "you previously", "your earlier", "you have analyzed"
204
+ ]
205
+
206
+ # Signals that indicate memory should NOT be used
207
+ SKIP_SIGNALS = [
208
+ "this is a new", "new topic", "different subject",
209
+ "what is", "define", "explain what"
210
+ ]
211
+
212
+ def __init__(self):
213
+ self.stats = {
214
+ "predictions": 0,
215
+ "inject_recommended": 0,
216
+ "skip_recommended": 0,
217
+ "skip_context_window": 0
218
+ }
219
+
220
+ def should_inject(self,
221
+ query: str,
222
+ context: str = "",
223
+ conversation_history: str = "",
224
+ model_confidence: float = 0.5) -> Tuple[bool, str, float]:
225
+ """
226
+ Predict if memory injection will help.
227
+
228
+ Returns:
229
+ (should_inject, reason, confidence)
230
+ """
231
+ self.stats["predictions"] += 1
232
+ combined = (query + " " + context).lower()
233
+
234
+ # Check skip signals first
235
+ for signal in self.SKIP_SIGNALS:
236
+ if signal in combined:
237
+ self.stats["skip_recommended"] += 1
238
+ return False, f"skip_signal:{signal}", 0.8
239
+
240
+ # Check injection signals
241
+ for signal in self.INJECTION_SIGNALS:
242
+ if signal in combined:
243
+ # But check if context window already has info
244
+ if self._context_has_info(query, conversation_history):
245
+ self.stats["skip_context_window"] += 1
246
+ return False, "context_window_sufficient", 0.7
247
+
248
+ self.stats["inject_recommended"] += 1
249
+ return True, f"inject_signal:{signal}", 0.85
250
+
251
+ # No clear signal - default to skip for simple queries
252
+ if self._is_simple_query(query):
253
+ self.stats["skip_recommended"] += 1
254
+ return False, "simple_query", 0.6
255
+
256
+ # Model is very confident - skip memory
257
+ if model_confidence > 0.85:
258
+ self.stats["skip_recommended"] += 1
259
+ return False, "model_confident", 0.7
260
+
261
+ # Default: don't inject (memory often hurts)
262
+ self.stats["skip_recommended"] += 1
263
+ return False, "no_signal", 0.5
264
+
265
+ def _context_has_info(self, query: str, history: str) -> bool:
266
+ """Check if conversation history already has needed context"""
267
+ if not history or len(history.split()) < 200:
268
+ return False
269
+
270
+ query_keywords = set(query.lower().split()) - {
271
+ "the", "a", "is", "are", "to", "of", "in", "for", "what", "how"
272
+ }
273
+
274
+ history_lower = history.lower()
275
+ overlap = sum(1 for kw in query_keywords if kw in history_lower)
276
+
277
+ return overlap >= len(query_keywords) * 0.6
278
+
279
+ def _is_simple_query(self, query: str) -> bool:
280
+ """Detect simple factual queries that don't need memory"""
281
+ simple_patterns = [
282
+ r"^what is\b", r"^who is\b", r"^when did\b",
283
+ r"^where is\b", r"^how many\b", r"^define\b"
284
+ ]
285
+ query_lower = query.lower()
286
+ return any(re.search(p, query_lower) for p in simple_patterns)
287
+
288
 
289
+ # =============================================================================
290
+ # SELF-TUNING SYSTEM (from SLM)
291
+ # =============================================================================
292
 
293
+ class SelfTuner:
294
+ """
295
+ SLM Self-Tuning Parameter System
296
+
297
+ Tracks performance and auto-adjusts parameters.
298
+ """
299
+
300
+ def __init__(self):
301
+ self.parameters = {
302
+ "similarity_threshold": 0.50, # ADJUSTED from SLM 0.65
303
+ "quality_threshold": 0.50, # ADJUSTED from SLM 0.30
304
+ "promotion_threshold": 0.55, # ADJUSTED from SLM 0.65
305
+ "demotion_threshold": 0.70, # ADJUSTED from SLM 0.75
306
+ }
307
+
308
+ self.performance_history = defaultdict(list)
309
+ self.adjustment_count = 0
310
+
311
+ # SLM learning rates
312
+ self.learning_rates = {
313
+ "similarity_threshold": 0.01,
314
+ "quality_threshold": 0.02,
315
+ "promotion_threshold": 0.05,
316
+ }
317
+
318
+ def record_outcome(self, param_name: str, value: float, success: bool):
319
+ """Record outcome for a parameter setting"""
320
+ self.performance_history[param_name].append({
321
+ "value": value,
322
+ "success": success,
323
+ "timestamp": time.time()
324
+ })
325
+
326
+ # Keep last 100 outcomes
327
+ if len(self.performance_history[param_name]) > 100:
328
+ self.performance_history[param_name] = \
329
+ self.performance_history[param_name][-100:]
330
+
331
+ def should_adjust(self, param_name: str) -> bool:
332
+ """Check if parameter should be adjusted (every 10 samples)"""
333
+ history = self.performance_history.get(param_name, [])
334
+ return len(history) >= 10 and len(history) % 10 == 0
335
 
336
+ def get_adjustment(self, param_name: str) -> float:
337
+ """Calculate parameter adjustment based on recent performance"""
338
+ history = self.performance_history.get(param_name, [])
339
+ if len(history) < 10:
340
+ return 0.0
341
+
342
+ recent = history[-10:]
343
+ success_rate = sum(1 for h in recent if h["success"]) / len(recent)
344
+
345
+ lr = self.learning_rates.get(param_name, 0.01)
346
+
347
+ if success_rate < 0.5:
348
+ # Performance poor - try lower threshold
349
+ return -lr
350
+ elif success_rate > 0.8:
351
+ # Performance good - can be more selective
352
+ return lr * 0.5
353
+
354
+ return 0.0
355
 
356
+ def auto_tune(self):
357
+ """Run auto-tuning cycle"""
358
+ adjusted = []
359
+
360
+ for param_name in self.parameters:
361
+ if self.should_adjust(param_name):
362
+ adjustment = self.get_adjustment(param_name)
363
+ if adjustment != 0:
364
+ old_val = self.parameters[param_name]
365
+ new_val = max(0.1, min(0.9, old_val + adjustment))
366
+ self.parameters[param_name] = new_val
367
+ adjusted.append((param_name, old_val, new_val))
368
+ self.adjustment_count += 1
369
+
370
+ return adjusted
371
+
372
 
373
+ # =============================================================================
374
+ # THREE-TIERED MEMORY MANAGER (from SLM)
375
+ # =============================================================================
376
 
377
+ class TieredMemoryManager:
378
+ """
379
+ SLM Three-Tiered Memory Hierarchy
380
+
381
+ Working Memory (32MB, <1ms):
382
+ - Currently active info
383
+ - Priority decay: 0.95/minute
384
+ - Eviction threshold: 0.2
385
+
386
+ Token Memory (100-250 items, 1-10ms):
387
+ - Compressed representations
388
+ - Loop-based organization
389
+ - Merging at 0.8 similarity
390
+
391
+ Semantic Memory (persistent, 10-100ms):
392
+ - Full knowledge representations
393
+ - Partition-based organization
394
+ """
395
+
396
+ # SLM spec values (some adjusted based on benchmarks)
397
+ WORKING_MEMORY_SIZE = 50 # items (simplified from 32MB)
398
+ TOKEN_LOOP_CAPACITY = 100 # default
399
+ TOKEN_LOOP_MAX = 250 # expandable
400
+
401
+ PRIORITY_DECAY = 0.95 # per access cycle
402
+ EVICTION_THRESHOLD = 0.2
403
+ LOOP_MERGE_THRESHOLD = 0.8
404
+
405
+ def __init__(self, tuner: SelfTuner):
406
+ self.tuner = tuner
407
+
408
+ # Three tiers
409
+ self.working_memory: Dict[str, Memory] = {}
410
+ self.token_loops: Dict[str, List[str]] = defaultdict(list) # namespace -> ids
411
+ self.semantic_memory: Dict[str, Memory] = {}
412
+
413
+ self.stats = {
414
+ "promotions": 0,
415
+ "demotions": 0,
416
+ "evictions": 0
417
+ }
418
 
419
+ def add_to_tier(self, memory: Memory, tier: MemoryTier):
420
+ """Add memory to specific tier"""
421
+ memory.tier = tier
422
+
423
+ if tier == MemoryTier.WORKING:
424
+ self._add_to_working(memory)
425
+ elif tier == MemoryTier.TOKEN:
426
+ self._add_to_token(memory)
427
+ else:
428
+ self.semantic_memory[memory.id] = memory
429
+
430
+ def _add_to_working(self, memory: Memory):
431
+ """Add to working memory with eviction if needed"""
432
+ if len(self.working_memory) >= self.WORKING_MEMORY_SIZE:
433
+ self._evict_from_working()
434
+
435
+ memory.priority = 1.0
436
+ self.working_memory[memory.id] = memory
437
+
438
+ def _add_to_token(self, memory: Memory):
439
+ """Add to token memory loop"""
440
+ loop = self.token_loops[memory.namespace]
441
+
442
+ if len(loop) >= self.TOKEN_LOOP_CAPACITY:
443
+ # Demote oldest to semantic
444
+ oldest_id = loop.pop(0)
445
+ if oldest_id in self.semantic_memory:
446
+ self.semantic_memory[oldest_id].tier = MemoryTier.SEMANTIC
447
+
448
+ loop.append(memory.id)
449
+ self.semantic_memory[memory.id] = memory # Store actual data in semantic
450
+ memory.tier = MemoryTier.TOKEN
451
+
452
+ def _evict_from_working(self):
453
+ """Evict lowest priority items from working memory"""
454
+ if not self.working_memory:
455
+ return
456
+
457
+ # Find lowest priority
458
+ min_id = min(self.working_memory, key=lambda k: self.working_memory[k].priority)
459
+ evicted = self.working_memory.pop(min_id)
460
+
461
+ # Demote to token memory
462
+ self._add_to_token(evicted)
463
+ self.stats["evictions"] += 1
464
+
465
+ def decay_priorities(self):
466
+ """Apply SLM priority decay (0.95 per cycle)"""
467
+ for memory in self.working_memory.values():
468
+ memory.priority *= self.PRIORITY_DECAY
469
+
470
+ # Evict if below threshold
471
+ if memory.priority < self.EVICTION_THRESHOLD:
472
+ self._evict_from_working()
473
 
474
+ def calculate_promotion_score(self, memory: Memory, query_relevance: float) -> float:
475
+ """
476
+ SLM Promotion Score:
477
+ PromotionScore = (QueryRelevance * 0.6) + (AccessFrequency * 0.3) + (RecencyScore * 0.1)
478
+ """
479
+ # Normalize access frequency (0-1)
480
+ access_freq = min(memory.access_count / 10, 1.0)
481
+
482
+ # Recency score (higher = more recent)
483
+ age_hours = (time.time() - memory.last_accessed) / 3600
484
+ recency = max(0, 1 - (age_hours / 24)) # Decay over 24 hours
485
+
486
+ return (query_relevance * 0.6) + (access_freq * 0.3) + (recency * 0.1)
487
 
488
+ def calculate_demotion_score(self, memory: Memory, query_relevance: float) -> float:
489
+ """
490
+ SLM Demotion Score:
491
+ DemotionScore = (1-QueryRelevance)*0.5 + (1-AccessFrequency)*0.3 + (Age/MAX_AGE)*0.2
492
+ """
493
+ access_freq = min(memory.access_count / 10, 1.0)
494
+
495
+ age_hours = (time.time() - memory.created_at) / 3600
496
+ age_score = min(age_hours / 168, 1.0) # MAX_AGE = 1 week
497
+
498
+ return ((1 - query_relevance) * 0.5) + ((1 - access_freq) * 0.3) + (age_score * 0.2)
499
 
500
+ def try_promote(self, memory_id: str, query_relevance: float) -> bool:
501
+ """Try to promote memory to higher tier"""
502
+ if memory_id not in self.semantic_memory:
503
+ return False
504
+
505
+ memory = self.semantic_memory[memory_id]
506
+ score = self.calculate_promotion_score(memory, query_relevance)
507
+ threshold = self.tuner.parameters["promotion_threshold"]
508
+
509
+ if score > threshold:
510
+ if memory.tier == MemoryTier.SEMANTIC:
511
+ self._add_to_token(memory)
512
+ self.stats["promotions"] += 1
513
+ return True
514
+ elif memory.tier == MemoryTier.TOKEN:
515
+ self._add_to_working(memory)
516
+ self.stats["promotions"] += 1
517
+ return True
518
+
519
+ return False
520
 
521
+ def try_demote(self, memory_id: str, query_relevance: float) -> bool:
522
+ """Try to demote memory to lower tier"""
523
+ if memory_id in self.working_memory:
524
+ memory = self.working_memory[memory_id]
525
+ score = self.calculate_demotion_score(memory, query_relevance)
526
+ threshold = self.tuner.parameters["demotion_threshold"]
527
+
528
+ # Also check capacity (SLM: demote if >80% capacity)
529
+ capacity_pressure = len(self.working_memory) / self.WORKING_MEMORY_SIZE
530
+
531
+ if score > threshold and capacity_pressure > 0.8:
532
+ self.working_memory.pop(memory_id)
533
+ self._add_to_token(memory)
534
+ self.stats["demotions"] += 1
535
+ return True
536
+
537
+ return False
538
 
539
+ def get_all_memories(self) -> Dict[str, Memory]:
540
+ """Get all memories across tiers"""
541
+ return {**self.semantic_memory, **self.working_memory}
542
+
543
+ def get_tier_stats(self) -> Dict:
544
+ """Get tier statistics"""
545
+ return {
546
+ "working_memory_count": len(self.working_memory),
547
+ "working_memory_capacity": self.WORKING_MEMORY_SIZE,
548
+ "token_loops": {ns: len(ids) for ns, ids in self.token_loops.items()},
549
+ "semantic_memory_count": len(self.semantic_memory),
550
+ "promotions": self.stats["promotions"],
551
+ "demotions": self.stats["demotions"],
552
+ "evictions": self.stats["evictions"]
553
+ }
554
 
555
 
556
+ # =============================================================================
557
+ # NEURAL LINK MANAGER (from SLM)
558
+ # =============================================================================
559
+
560
+ class NeuralLinkManager:
561
+ """
562
+ SLM Neural Link Pathway System
563
+
564
+ Creates and manages typed connections between memories.
565
+ """
566
+
567
+ # SLM path finding limits (adjusted based on benchmarks)
568
+ MAX_PATH_DEPTH = 4 # SLM: 4 standard, 6 exhaustive
569
+ MIN_PATH_STRENGTH = 0.40 # SLM: 0.45
570
+ PATH_STRENGTH_DECAY = 0.9 # SLM: 0.9 per hop
571
+ MAX_BRANCHING = 12 # SLM: 12
572
+
573
+ # Pruning (adjusted based on benchmarks)
574
+ PRUNE_STRENGTH_THRESHOLD = 0.25 # SLM: 0.30
575
+ PRUNE_AGE_DAYS = 30 # SLM: 60, ADJUSTED
576
+
577
+ def __init__(self):
578
+ self.links: Dict[str, NeuralLink] = {} # link_id -> NeuralLink
579
+ self.outgoing: Dict[str, Set[str]] = defaultdict(set) # source -> link_ids
580
+ self.incoming: Dict[str, Set[str]] = defaultdict(set) # target -> link_ids
581
+
582
+ self.stats = {
583
+ "links_created": 0,
584
+ "links_pruned": 0,
585
+ "traversals": 0
586
+ }
587
+
588
+ def _link_id(self, source: str, target: str, link_type: LinkType) -> str:
589
+ """Generate link ID"""
590
+ return f"{source}:{target}:{link_type.value}"
591
+
592
+ def create_link(self, source_id: str, target_id: str,
593
+ link_type: LinkType, similarity: float) -> Optional[str]:
594
+ """
595
+ Create link if similarity exceeds type-specific threshold.
596
+
597
+ SLM LinkScore = (VectorSimilarity * 0.6) + (CoOccurrence * 0.25) + (DomainRelatedness * 0.15)
598
+ Simplified here to just similarity.
599
+ """
600
+ props = LINK_PROPERTIES[link_type]
601
+
602
+ if similarity < props["creation_threshold"]:
603
+ return None
604
+
605
+ link_id = self._link_id(source_id, target_id, link_type)
606
+
607
+ if link_id in self.links:
608
+ # Strengthen existing link
609
+ self.links[link_id].strength = min(
610
+ 1.0,
611
+ self.links[link_id].strength + props["usage_boost"]
612
+ )
613
+ return link_id
614
+
615
+ # Create new link
616
+ link = NeuralLink(
617
+ source_id=source_id,
618
+ target_id=target_id,
619
+ link_type=link_type,
620
+ strength=props["initial_strength"]
621
+ )
622
+
623
+ self.links[link_id] = link
624
+ self.outgoing[source_id].add(link_id)
625
+ self.incoming[target_id].add(link_id)
626
+ self.stats["links_created"] += 1
627
+
628
+ return link_id
629
+
630
+ def traverse_link(self, link_id: str) -> Optional[NeuralLink]:
631
+ """Traverse a link, strengthening it"""
632
+ if link_id not in self.links:
633
+ return None
634
+
635
+ link = self.links[link_id]
636
+ link.traversal_count += 1
637
+ link.last_traversed = time.time()
638
+
639
+ # Strengthen on traversal (up to daily max)
640
+ props = LINK_PROPERTIES[link.link_type]
641
+ link.strength = min(1.0, link.strength + props["usage_boost"])
642
+
643
+ self.stats["traversals"] += 1
644
+ return link
645
+
646
+ def find_paths(self, source_id: str, target_id: str,
647
+ max_depth: int = None) -> List[List[str]]:
648
+ """Find paths between memories (SLM path finding)"""
649
+ max_depth = max_depth or self.MAX_PATH_DEPTH
650
+ paths = []
651
+
652
+ def dfs(current: str, target: str, path: List[str],
653
+ strength: float, depth: int):
654
+ if depth > max_depth or strength < self.MIN_PATH_STRENGTH:
655
+ return
656
+
657
+ if current == target:
658
+ paths.append(path.copy())
659
+ return
660
+
661
+ # Limit branching
662
+ link_ids = list(self.outgoing.get(current, set()))[:self.MAX_BRANCHING]
663
+
664
+ for link_id in link_ids:
665
+ link = self.links.get(link_id)
666
+ if link and link.target_id not in path:
667
+ new_strength = strength * link.strength * self.PATH_STRENGTH_DECAY
668
+ path.append(link.target_id)
669
+ dfs(link.target_id, target, path, new_strength, depth + 1)
670
+ path.pop()
671
+
672
+ dfs(source_id, target_id, [source_id], 1.0, 0)
673
+ return paths
674
 
675
+ def get_connected(self, memory_id: str, link_types: List[LinkType] = None) -> List[str]:
676
+ """Get memories connected to this one"""
677
+ connected = []
678
 
679
+ for link_id in self.outgoing.get(memory_id, set()):
680
+ link = self.links.get(link_id)
681
+ if link:
682
+ if link_types is None or link.link_type in link_types:
683
+ connected.append(link.target_id)
684
 
685
+ return connected
686
+
687
+ def decay_links(self):
688
+ """Apply daily decay to all links"""
689
+ for link in self.links.values():
690
+ props = LINK_PROPERTIES[link.link_type]
691
+ link.strength *= (1 - props["decay_rate"])
692
+
693
+ def prune_weak_links(self) -> int:
694
+ """Prune links below strength threshold and unused for too long"""
695
+ to_prune = []
696
+ now = time.time()
697
+ age_threshold = self.PRUNE_AGE_DAYS * 24 * 3600
698
+
699
+ for link_id, link in self.links.items():
700
+ age = now - link.last_traversed
701
+ if link.strength < self.PRUNE_STRENGTH_THRESHOLD and age > age_threshold:
702
+ to_prune.append(link_id)
703
 
704
+ for link_id in to_prune:
705
+ link = self.links.pop(link_id)
706
+ self.outgoing[link.source_id].discard(link_id)
707
+ self.incoming[link.target_id].discard(link_id)
708
+ self.stats["links_pruned"] += 1
709
+
710
+ return len(to_prune)
711
 
712
+ def get_stats(self) -> Dict:
713
+ return {
714
+ "total_links": len(self.links),
715
+ "links_by_type": {
716
+ lt.value: sum(1 for l in self.links.values() if l.link_type == lt)
717
+ for lt in LinkType
718
+ },
719
+ **self.stats
720
+ }
721
+
722
 
723
+ # =============================================================================
724
+ # MAIN MNEMO v4 CLASS
725
+ # =============================================================================
726
 
727
  class Mnemo:
728
  """
729
+ Mnemo v4: SLM-Inspired Memory System
730
 
731
+ Implements:
732
+ - Three-tiered memory hierarchy
733
+ - Neural link pathways (8 types)
734
+ - Self-tuning parameters
735
+ - Memory utility prediction
736
 
737
+ With parameter adjustments based on Mnemo benchmarks.
738
+ """
 
739
 
740
  STOP_WORDS = {"a", "an", "the", "is", "are", "was", "were", "be", "been",
741
  "to", "of", "in", "for", "on", "with", "at", "by", "from",
742
  "and", "but", "or", "not", "this", "that", "i", "me", "my"}
743
 
744
+ def __init__(self, embedding_dim: int = 384):
 
 
 
 
 
 
 
745
  self.embedding_dim = embedding_dim
746
+
747
+ # Core components
748
+ self.tuner = SelfTuner()
749
+ self.memory_manager = TieredMemoryManager(self.tuner)
750
+ self.link_manager = NeuralLinkManager()
751
+ self.utility_predictor = MemoryUtilityPredictor()
752
+
753
+ # Vector index
754
  self._embeddings: List[np.ndarray] = []
755
  self._ids: List[str] = []
756
 
 
759
  else:
760
  self.index = None
761
 
762
+ # BM25
763
  self.bm25 = None
764
  self._tokenized_docs: List[List[str]] = []
765
 
766
+ # Knowledge Graph
767
  if HAS_NETWORKX:
768
  self.graph = nx.DiGraph()
769
  else:
770
  self.graph = None
771
 
772
+ # Cache
 
 
773
  self._cache: Dict[str, Any] = {}
774
  self._cache_lock = threading.Lock()
775
 
776
+ # Stats
777
  self.stats = {
778
+ "adds": 0,
779
+ "adds_rejected": 0,
780
+ "searches": 0,
781
+ "cache_hits": 0,
782
+ "cache_misses": 0
783
  }
784
 
785
  def _get_embedding(self, text: str) -> np.ndarray:
786
+ """Generate embedding (hash-based for POC)"""
787
  cache_key = f"emb:{hashlib.md5(text.encode()).hexdigest()}"
788
+
789
  with self._cache_lock:
790
  if cache_key in self._cache:
791
  self.stats["cache_hits"] += 1
792
  return self._cache[cache_key]
793
  self.stats["cache_misses"] += 1
794
 
795
+ # Hash-based embedding
796
  embedding = np.zeros(self.embedding_dim, dtype=np.float32)
797
  words = text.lower().split()
798
  for i, word in enumerate(words):
 
808
 
809
  return embedding
810
 
811
+ def _estimate_quality(self, content: str) -> float:
812
+ """Estimate content quality (SLM quality gates)"""
813
+ score = 0.5
814
+ words = len(content.split())
815
 
816
+ if words < 5:
817
+ score -= 0.3
818
+ elif words > 20:
819
+ score += 0.1
820
+
821
+ if any(r in content.lower() for r in ["because", "therefore", "shows"]):
822
+ score += 0.2
823
 
824
+ if re.search(r'\d+', content):
825
+ score += 0.1
826
+
827
+ if any(v in content.lower() for v in ["something", "stuff", "maybe"]):
828
+ score -= 0.2
829
+
830
+ return max(0.0, min(1.0, score))
831
+
832
+ def should_inject(self, query: str, context: str = "",
833
+ conversation_history: str = "",
834
+ model_confidence: float = 0.5) -> bool:
835
+ """
836
+ Memory Utility Predictor - should we inject memory?
837
+
838
+ Based on benchmark findings that memory often hurts performance.
839
+ """
840
+ should, reason, confidence = self.utility_predictor.should_inject(
841
+ query, context, conversation_history, model_confidence
842
+ )
843
  return should
844
 
845
  def add(self, content: str, namespace: str = "default",
846
  metadata: Dict = None, skip_quality_check: bool = False) -> Optional[str]:
847
+ """Add memory with SLM quality gates"""
848
+ quality = self._estimate_quality(content)
849
+ threshold = self.tuner.parameters["quality_threshold"]
850
 
851
+ if not skip_quality_check and quality < threshold:
 
 
852
  self.stats["adds_rejected"] += 1
853
+ self.tuner.record_outcome("quality_threshold", threshold, False)
854
  return None
855
 
856
  memory_id = f"mem_{hashlib.md5(content.encode()).hexdigest()[:8]}"
 
865
  metadata=metadata or {}
866
  )
867
 
868
+ # Add to semantic memory (lowest tier)
869
+ self.memory_manager.add_to_tier(memory, MemoryTier.SEMANTIC)
870
+
871
+ # Update indices
872
  self._embeddings.append(embedding)
873
  self._ids.append(memory_id)
874
 
 
880
  if HAS_BM25:
881
  self.bm25 = BM25Okapi(self._tokenized_docs)
882
 
883
+ # Create links to similar memories
884
+ self._create_links_for_new_memory(memory_id, embedding)
 
 
 
 
 
 
885
 
886
  self.stats["adds"] += 1
887
+ self.tuner.record_outcome("quality_threshold", threshold, True)
888
+
889
  return memory_id
890
 
891
+ def _create_links_for_new_memory(self, memory_id: str, embedding: np.ndarray):
892
+ """Create neural links to similar memories"""
893
+ if len(self._ids) < 2:
894
+ return
895
+
896
+ # Find similar memories
897
+ similarities = []
898
+ for other_id, other_emb in zip(self._ids, self._embeddings):
899
+ if other_id != memory_id:
900
+ sim = float(np.dot(embedding, other_emb))
901
+ similarities.append((other_id, sim))
902
+
903
+ # Sort by similarity
904
+ similarities.sort(key=lambda x: x[1], reverse=True)
905
+
906
+ # Create links for top matches
907
+ for other_id, sim in similarities[:5]:
908
+ # Try different link types
909
+ self.link_manager.create_link(
910
+ memory_id, other_id, LinkType.SEMANTIC_SIMILARITY, sim
911
+ )
912
+ self.link_manager.create_link(
913
+ other_id, memory_id, LinkType.SEMANTIC_SIMILARITY, sim
914
+ )
915
+
916
+ def search(self, query: str, top_k: int = 5,
917
+ namespace: Optional[str] = None,
918
+ use_links: bool = True) -> List[SearchResult]:
919
+ """
920
+ Search with multi-strategy retrieval + neural links
921
+ """
922
+ if not self.memory_manager.semantic_memory:
923
  return []
924
 
925
  self.stats["searches"] += 1
926
  query_embedding = self._get_embedding(query)
927
+ threshold = self.tuner.parameters["similarity_threshold"]
928
 
929
+ # Strategy 1: Vector similarity
930
  semantic_scores = {}
931
  if HAS_FAISS and self.index is not None and self.index.ntotal > 0:
932
  k = min(top_k * 3, self.index.ntotal)
 
935
  if 0 <= idx < len(self._ids):
936
  semantic_scores[self._ids[idx]] = float(score)
937
  else:
938
+ for mem_id, emb in zip(self._ids, self._embeddings):
939
+ semantic_scores[mem_id] = float(np.dot(query_embedding, emb))
940
 
941
+ # Strategy 2: BM25
942
  bm25_scores = {}
943
  if HAS_BM25 and self.bm25 is not None:
944
  tokens = query.lower().split()
 
948
  if score > 0.1 * max_score:
949
  bm25_scores[self._ids[idx]] = float(score / max_score)
950
 
951
+ # Strategy 3: Neural link traversal
952
+ link_scores = {}
953
+ if use_links:
954
+ # Find top semantic matches and traverse their links
955
+ top_semantic = sorted(semantic_scores.items(), key=lambda x: x[1], reverse=True)[:3]
956
+ for mem_id, _ in top_semantic:
957
+ connected = self.link_manager.get_connected(mem_id)
958
+ for conn_id in connected[:5]:
959
+ link_scores[conn_id] = link_scores.get(conn_id, 0) + 0.3
 
960
 
961
+ # Combine scores (SLM-style weighting)
962
+ all_ids = set(semantic_scores.keys()) | set(bm25_scores.keys()) | set(link_scores.keys())
963
 
964
  if namespace:
965
+ # Filter by namespace
966
+ all_ids = {mid for mid in all_ids
967
+ if mid in self.memory_manager.semantic_memory
968
+ and self.memory_manager.semantic_memory[mid].namespace == namespace}
969
 
970
  results = []
971
  for mem_id in all_ids:
972
  strat = {
973
  "semantic": semantic_scores.get(mem_id, 0),
974
  "bm25": bm25_scores.get(mem_id, 0),
975
+ "links": link_scores.get(mem_id, 0)
976
  }
977
 
978
  combined = (
979
+ strat["semantic"] * 0.5 +
980
+ strat["bm25"] * 0.3 +
981
+ strat["links"] * 0.2
982
  )
983
 
984
+ memory = self.memory_manager.semantic_memory.get(mem_id)
985
+ if memory and combined >= threshold:
986
+ # Update access tracking
987
+ memory.access_count += 1
988
+ memory.last_accessed = time.time()
989
+
990
+ # Try promotion
991
+ self.memory_manager.try_promote(mem_id, combined)
992
+
993
+ results.append(SearchResult(
994
+ id=mem_id,
995
+ content=memory.content,
996
+ score=combined,
997
+ tier=memory.tier,
998
+ strategy_scores=strat,
999
+ metadata=memory.metadata
1000
+ ))
1001
 
1002
+ self.tuner.record_outcome("similarity_threshold", threshold, True)
1003
+ else:
1004
+ self.tuner.record_outcome("similarity_threshold", threshold, False)
 
 
 
 
 
1005
 
1006
  results.sort(key=lambda x: x.score, reverse=True)
 
 
 
 
 
1007
  return results[:top_k]
1008
 
1009
+ def get_context(self, query: str, top_k: int = 3,
1010
+ namespace: Optional[str] = None) -> str:
1011
+ """Get formatted context for prompt injection"""
1012
  results = self.search(query, top_k=top_k, namespace=namespace)
1013
 
1014
  if not results:
 
1016
 
1017
  parts = ["[RELEVANT CONTEXT FROM MEMORY]"]
1018
  for r in results:
1019
+ tier_marker = f"[{r.tier.value.upper()}]" if r.tier != MemoryTier.SEMANTIC else ""
1020
+ parts.append(f"โ€ข {tier_marker} {r.content}")
1021
  parts.append("[END CONTEXT]\n")
1022
 
1023
  return "\n".join(parts)
1024
 
1025
  def feedback(self, query: str, memory_id: str, relevance: float):
1026
+ """Record feedback for learning"""
1027
  relevance = max(-1, min(1, relevance))
1028
+
1029
+ if memory_id in self.memory_manager.semantic_memory:
1030
+ memory = self.memory_manager.semantic_memory[memory_id]
1031
+
1032
+ # Update relevance score
1033
+ memory.relevance_score = 0.7 * memory.relevance_score + 0.3 * ((relevance + 1) / 2)
1034
+
1035
+ # Strengthen/weaken links based on feedback
1036
+ for link_id in self.link_manager.outgoing.get(memory_id, set()):
1037
+ link = self.link_manager.links.get(link_id)
1038
+ if link:
1039
+ link.strength = max(0, min(1, link.strength + relevance * 0.05))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1040
 
1041
+ def maintenance_cycle(self):
1042
+ """Run SLM maintenance operations"""
1043
+ # Decay priorities in working memory
1044
+ self.memory_manager.decay_priorities()
1045
+
1046
+ # Decay link strengths
1047
+ self.link_manager.decay_links()
1048
+
1049
+ # Prune weak links
1050
+ pruned = self.link_manager.prune_weak_links()
1051
+
1052
+ # Auto-tune parameters
1053
+ adjustments = self.tuner.auto_tune()
1054
+
1055
+ return {
1056
+ "links_pruned": pruned,
1057
+ "parameter_adjustments": adjustments
1058
+ }
1059
 
1060
  def get_stats(self) -> Dict:
1061
+ """Get comprehensive statistics"""
1062
  return {
1063
+ "memories": {
1064
+ "total": len(self.memory_manager.semantic_memory),
1065
+ **self.memory_manager.get_tier_stats()
1066
+ },
1067
+ "links": self.link_manager.get_stats(),
1068
+ "utility_predictor": self.utility_predictor.stats,
1069
+ "tuner": {
1070
+ "parameters": self.tuner.parameters,
1071
+ "adjustments": self.tuner.adjustment_count
1072
+ },
1073
+ "operations": self.stats
 
1074
  }
1075
 
1076
+ def clear(self):
1077
+ """Clear all memory"""
1078
+ self.memory_manager = TieredMemoryManager(self.tuner)
1079
+ self.link_manager = NeuralLinkManager()
1080
+ self._embeddings.clear()
1081
+ self._ids.clear()
1082
+ self._tokenized_docs.clear()
1083
+ self.bm25 = None
1084
+ self._cache.clear()
1085
+
1086
+ if HAS_FAISS:
1087
+ self.index = faiss.IndexFlatIP(self.embedding_dim)
 
 
 
 
1088
 
1089
  def __len__(self):
1090
+ return len(self.memory_manager.semantic_memory)
1091
 
1092
  def __repr__(self):
1093
+ return f"Mnemo(memories={len(self)}, links={len(self.link_manager.links)})"
1094
 
1095
 
1096
+ # =============================================================================
1097
+ # DEMO
1098
+ # =============================================================================
1099
+
1100
  def demo():
1101
+ print("="*70)
1102
+ print("MNEMO v4: SLM-INSPIRED ARCHITECTURE")
1103
+ print("="*70)
1104
+
1105
+ m = Mnemo()
1106
+ print(f"\nโœ“ Initialized: {m}")
1107
+
1108
+ # Show tuned parameters
1109
+ print("\n๐Ÿ“Š Tuned Parameters (adjusted from SLM):")
1110
+ for param, value in m.tuner.parameters.items():
1111
+ print(f" {param}: {value}")
1112
 
1113
+ # Add memories
1114
+ print("\n๐Ÿ“ Adding memories...")
1115
+ memories = [
1116
+ "User prefers Python because it has clean syntax and good libraries",
1117
+ "Previous analysis showed gender bias in Victorian psychiatry diagnoses",
1118
+ "Framework has 5 checkpoints for detecting historical medical bias",
1119
+ "The project deadline is March 15th for the API redesign",
1120
+ "User's coffee preference is cappuccino with oat milk"
1121
+ ]
1122
 
1123
+ for mem in memories:
1124
+ result = m.add(mem)
1125
+ status = "โœ“" if result else "โœ—"
1126
+ print(f" {status} {mem[:50]}...")
1127
 
1128
+ # Test memory utility predictor
1129
+ print("\n๐Ÿง  Memory Utility Predictions:")
1130
+ tests = [
1131
+ ("What is Python?", False),
1132
+ ("Based on your previous analysis...", True),
1133
+ ("Compare to your earlier findings", True),
1134
+ ("This is a NEW topic", False),
1135
+ ]
1136
 
1137
+ for query, expected in tests:
1138
+ result = m.should_inject(query)
1139
+ status = "โœ“" if result == expected else "โœ—"
1140
+ action = "INJECT" if result else "SKIP"
1141
+ print(f" {status} {action}: {query}")
1142
 
1143
+ # Search
1144
+ print("\n๐Ÿ” Search Results:")
1145
+ results = m.search("previous analysis framework", top_k=3)
1146
  for r in results:
1147
+ print(f" [{r.tier.value}] score={r.score:.3f}: {r.content[:50]}...")
1148
+
1149
+ # Show neural links
1150
+ print("\n๐Ÿ”— Neural Links:")
1151
+ link_stats = m.link_manager.get_stats()
1152
+ print(f" Total links: {link_stats['total_links']}")
1153
+ for lt, count in link_stats['links_by_type'].items():
1154
+ if count > 0:
1155
+ print(f" {lt}: {count}")
1156
+
1157
+ # Full stats
1158
+ print("\n๐Ÿ“Š Full Statistics:")
1159
+ stats = m.get_stats()
1160
+ print(f" Memories: {stats['memories']['total']}")
1161
+ print(f" Working memory: {stats['memories']['working_memory_count']}")
1162
+ print(f" Links: {stats['links']['total_links']}")
1163
+ print(f" Utility predictions: {stats['utility_predictor']['predictions']}")
1164
 
1165
+ print("\n" + "="*70)
1166
+ print("โœ… Demo complete!")
1167
+ print("="*70)
1168
 
1169
 
1170
  if __name__ == "__main__":