huronvalley21 commited on
Commit
17dc482
·
verified ·
1 Parent(s): 9fceda9

Upload mythos/memory.py

Browse files
Files changed (1) hide show
  1. mythos/memory.py +134 -0
mythos/memory.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Memory system for Mythos agents — MIRIX-inspired 4-component taxonomy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import uuid
6
+ from datetime import datetime, timezone
7
+ from enum import Enum
8
+ from typing import Any, Optional
9
+
10
+ from pydantic import BaseModel, Field
11
+
12
+
13
+ class MemoryType(str, Enum):
14
+ """Types of memory in the MIRIX taxonomy."""
15
+
16
+ CORE = "core" # Identity, persona, values
17
+ EPISODIC = "episodic" # Timestamped events, experiences
18
+ SEMANTIC = "semantic" # Facts, knowledge, concepts
19
+ PROCEDURAL = "procedural" # Workflows, skills, how-to
20
+
21
+
22
+ class MemoryEntry(BaseModel):
23
+ """A single memory entry."""
24
+
25
+ id: str = Field(default_factory=lambda: str(uuid.uuid4()))
26
+ type: MemoryType
27
+ content: str
28
+ agent: str # Which agent owns this memory
29
+ timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
30
+ importance: float = 0.5 # 0.0 to 1.0
31
+ tags: list[str] = Field(default_factory=list)
32
+ source: Optional[str] = None # Where did this memory come from
33
+ access_count: int = 0 # For LRU-style eviction
34
+ last_accessed: Optional[datetime] = None
35
+ embedding: Optional[list[float]] = None # For vector search
36
+
37
+ def touch(self) -> None:
38
+ """Mark memory as accessed."""
39
+ self.access_count += 1
40
+ self.last_accessed = datetime.now(timezone.utc)
41
+
42
+
43
+ class Memory(BaseModel):
44
+ """Agent memory store with Active Retrieval."""
45
+
46
+ agent_name: str
47
+ entries: list[MemoryEntry] = Field(default_factory=list)
48
+ max_entries: int = 1000
49
+ auto_retrieve: bool = True
50
+
51
+ def add(
52
+ self,
53
+ content: str,
54
+ mem_type: MemoryType,
55
+ importance: float = 0.5,
56
+ tags: Optional[list[str]] = None,
57
+ source: Optional[str] = None,
58
+ ) -> MemoryEntry:
59
+ """Add a new memory entry."""
60
+ entry = MemoryEntry(
61
+ type=mem_type,
62
+ content=content,
63
+ agent=self.agent_name,
64
+ importance=importance,
65
+ tags=tags or [],
66
+ source=source,
67
+ )
68
+ self.entries.append(entry)
69
+ if len(self.entries) > self.max_entries:
70
+ self._evict_least_important()
71
+ return entry
72
+
73
+ def retrieve(
74
+ self,
75
+ query: str,
76
+ mem_type: Optional[MemoryType] = None,
77
+ limit: int = 5,
78
+ ) -> list[MemoryEntry]:
79
+ """Retrieve relevant memories. Simple keyword match; override for vector search."""
80
+ query_lower = query.lower()
81
+ candidates = self.entries
82
+ if mem_type:
83
+ candidates = [e for e in candidates if e.type == mem_type]
84
+
85
+ scored = []
86
+ for entry in candidates:
87
+ score = 0.0
88
+ if query_lower in entry.content.lower():
89
+ score += 1.0
90
+ score += entry.importance * 0.5
91
+ score += min(entry.access_count * 0.01, 0.3)
92
+ scored.append((score, entry))
93
+
94
+ scored.sort(key=lambda x: x[0], reverse=True)
95
+ results = [entry for _, entry in scored[:limit]]
96
+ for entry in results:
97
+ entry.touch()
98
+ return results
99
+
100
+ def active_retrieve(self, context: str) -> list[MemoryEntry]:
101
+ """Infer topics from context and retrieve relevant memories automatically."""
102
+ # Simple topic extraction: split into words, filter short ones
103
+ words = [w.lower() for w in context.split() if len(w) > 4]
104
+ topics = list(set(words))[:5]
105
+
106
+ all_results: list[MemoryEntry] = []
107
+ for topic in topics:
108
+ all_results.extend(self.retrieve(topic, limit=3))
109
+
110
+ # Deduplicate by id
111
+ seen = set()
112
+ deduped = []
113
+ for entry in all_results:
114
+ if entry.id not in seen:
115
+ seen.add(entry.id)
116
+ deduped.append(entry)
117
+
118
+ deduped.sort(key=lambda e: e.importance, reverse=True)
119
+ return deduped[:10]
120
+
121
+ def _evict_least_important(self) -> None:
122
+ """Remove least important / least accessed memory."""
123
+ self.entries.sort(
124
+ key=lambda e: (e.importance, e.access_count, e.timestamp),
125
+ )
126
+ self.entries.pop(0)
127
+
128
+ def to_context(self, entries: Optional[list[MemoryEntry]] = None) -> str:
129
+ """Format memories as context string for LLM prompt."""
130
+ entries = entries or self.entries[-20:]
131
+ lines = []
132
+ for entry in entries:
133
+ lines.append(f"[{entry.type.value.upper()}] {entry.content}")
134
+ return "\n".join(lines)