memorycoreclaw 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- memorycoreclaw/__init__.py +21 -0
- memorycoreclaw/cognitive/__init__.py +8 -0
- memorycoreclaw/cognitive/contextual.py +100 -0
- memorycoreclaw/cognitive/forgetting.py +162 -0
- memorycoreclaw/cognitive/heuristic.py +217 -0
- memorycoreclaw/cognitive/working.py +179 -0
- memorycoreclaw/cognitive/working_memory.py +271 -0
- memorycoreclaw/core/__init__.py +15 -0
- memorycoreclaw/core/engine.py +944 -0
- memorycoreclaw/core/memory.py +373 -0
- memorycoreclaw/py.typed +2 -0
- memorycoreclaw/retrieval/__init__.py +6 -0
- memorycoreclaw/retrieval/ontology.py +214 -0
- memorycoreclaw/retrieval/semantic.py +192 -0
- memorycoreclaw/storage/__init__.py +6 -0
- memorycoreclaw/storage/database.py +79 -0
- memorycoreclaw/storage/multimodal.py +79 -0
- memorycoreclaw/utils/__init__.py +6 -0
- memorycoreclaw/utils/export.py +114 -0
- memorycoreclaw/utils/visualization.py +1016 -0
- memorycoreclaw-2.0.0.dist-info/METADATA +398 -0
- memorycoreclaw-2.0.0.dist-info/RECORD +25 -0
- memorycoreclaw-2.0.0.dist-info/WHEEL +5 -0
- memorycoreclaw-2.0.0.dist-info/licenses/LICENSE +21 -0
- memorycoreclaw-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MemoryCoreClaw
|
|
2
|
+
# Human-brain-inspired Long-term Memory Engine for AI Agents
|
|
3
|
+
|
|
4
|
+
__version__ = "2.0.0"
|
|
5
|
+
__author__ = "MemoryCoreClaw Team"
|
|
6
|
+
|
|
7
|
+
from .core.memory import Memory, get_memory
|
|
8
|
+
from .core.engine import MemoryEngine, MemoryLayer, Emotion, Fact, Lesson, STANDARD_RELATIONS
|
|
9
|
+
from .cognitive.contextual import Context
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
'Memory',
|
|
13
|
+
'MemoryEngine',
|
|
14
|
+
'get_memory',
|
|
15
|
+
'Context',
|
|
16
|
+
'MemoryLayer',
|
|
17
|
+
'Emotion',
|
|
18
|
+
'Fact',
|
|
19
|
+
'Lesson',
|
|
20
|
+
'STANDARD_RELATIONS',
|
|
21
|
+
]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""MemoryCoreClaw Cognitive Module"""
|
|
2
|
+
|
|
3
|
+
from .forgetting import ForgettingCurve
|
|
4
|
+
from .contextual import ContextualMemory
|
|
5
|
+
from .working import WorkingMemory
|
|
6
|
+
from .heuristic import HeuristicEngine
|
|
7
|
+
|
|
8
|
+
__all__ = ["ForgettingCurve", "ContextualMemory", "WorkingMemory", "HeuristicEngine"]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MemoryCoreClaw - Contextual Memory Module
|
|
3
|
+
|
|
4
|
+
Implements context-based memory recall.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Optional, List, Dict, Any
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class Context:
|
|
14
|
+
"""Memory context"""
|
|
15
|
+
location: Optional[str] = None
|
|
16
|
+
people: List[str] = field(default_factory=list)
|
|
17
|
+
emotion: Optional[str] = None
|
|
18
|
+
activity: Optional[str] = None
|
|
19
|
+
channel: Optional[str] = None
|
|
20
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ContextualMemory:
|
|
24
|
+
"""
|
|
25
|
+
Contextual Memory Engine
|
|
26
|
+
|
|
27
|
+
Features:
|
|
28
|
+
- Bind memories to contexts
|
|
29
|
+
- Trigger recall by context
|
|
30
|
+
- Score context matches
|
|
31
|
+
|
|
32
|
+
Usage:
|
|
33
|
+
cm = ContextualMemory()
|
|
34
|
+
ctx = Context(location="office", people=["Alice"])
|
|
35
|
+
cm.bind("fact", 1, ctx)
|
|
36
|
+
results = cm.recall(people=["Alice"])
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, db_connection=None):
|
|
40
|
+
self.conn = db_connection
|
|
41
|
+
|
|
42
|
+
def create_context(self, context: Context) -> int:
|
|
43
|
+
"""Create a context record"""
|
|
44
|
+
# Implementation depends on database
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
def bind_memory(self, memory_type: str, memory_id: int, context: Context) -> int:
|
|
48
|
+
"""Bind a memory to a context"""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
def recall_by_context(
|
|
52
|
+
self,
|
|
53
|
+
location: str = None,
|
|
54
|
+
people: List[str] = None,
|
|
55
|
+
emotion: str = None,
|
|
56
|
+
activity: str = None
|
|
57
|
+
) -> List[Dict]:
|
|
58
|
+
"""Recall memories matching context"""
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
def score_match(self, query_context: Context, memory_context: Context) -> float:
|
|
62
|
+
"""
|
|
63
|
+
Score how well two contexts match.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
query_context: The search context
|
|
67
|
+
memory_context: The memory's context
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Match score (0-1)
|
|
71
|
+
"""
|
|
72
|
+
score = 0.0
|
|
73
|
+
max_score = 0.0
|
|
74
|
+
|
|
75
|
+
# Location match (weight: 1.0)
|
|
76
|
+
if query_context.location:
|
|
77
|
+
max_score += 1.0
|
|
78
|
+
if memory_context.location == query_context.location:
|
|
79
|
+
score += 1.0
|
|
80
|
+
|
|
81
|
+
# People overlap (weight: 0.8 per person)
|
|
82
|
+
if query_context.people:
|
|
83
|
+
for person in query_context.people:
|
|
84
|
+
max_score += 0.8
|
|
85
|
+
if person in memory_context.people:
|
|
86
|
+
score += 0.8
|
|
87
|
+
|
|
88
|
+
# Emotion match (weight: 0.5)
|
|
89
|
+
if query_context.emotion:
|
|
90
|
+
max_score += 0.5
|
|
91
|
+
if memory_context.emotion == query_context.emotion:
|
|
92
|
+
score += 0.5
|
|
93
|
+
|
|
94
|
+
# Activity match (weight: 0.7)
|
|
95
|
+
if query_context.activity:
|
|
96
|
+
max_score += 0.7
|
|
97
|
+
if memory_context.activity == query_context.activity:
|
|
98
|
+
score += 0.7
|
|
99
|
+
|
|
100
|
+
return score / max_score if max_score > 0 else 0.0
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MemoryCoreClaw - Cognitive Module: Forgetting Curve
|
|
3
|
+
|
|
4
|
+
Implements Ebbinghaus forgetting curve for memory strength decay.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import math
|
|
8
|
+
from datetime import datetime, timedelta
|
|
9
|
+
from typing import Optional
|
|
10
|
+
import sqlite3
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ForgettingCurve:
|
|
14
|
+
"""
|
|
15
|
+
Ebbinghaus forgetting curve implementation.
|
|
16
|
+
|
|
17
|
+
Formula: R = e^(-t/(S*100))
|
|
18
|
+
Where:
|
|
19
|
+
R = retention rate
|
|
20
|
+
t = time since last access (days)
|
|
21
|
+
S = memory strength (importance)
|
|
22
|
+
|
|
23
|
+
Features:
|
|
24
|
+
- Time-based decay
|
|
25
|
+
- Importance-based strength
|
|
26
|
+
- Emotion enhancement
|
|
27
|
+
- Access reinforcement
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, db_path: str):
|
|
31
|
+
self.db_path = db_path
|
|
32
|
+
self._init_tables()
|
|
33
|
+
|
|
34
|
+
def _init_tables(self):
|
|
35
|
+
"""Initialize memory strength table."""
|
|
36
|
+
conn = sqlite3.connect(self.db_path)
|
|
37
|
+
cursor = conn.cursor()
|
|
38
|
+
|
|
39
|
+
cursor.execute('''
|
|
40
|
+
CREATE TABLE IF NOT EXISTS memory_strength (
|
|
41
|
+
memory_id INTEGER PRIMARY KEY,
|
|
42
|
+
memory_type TEXT DEFAULT 'fact',
|
|
43
|
+
base_strength REAL DEFAULT 0.5,
|
|
44
|
+
current_strength REAL DEFAULT 0.5,
|
|
45
|
+
last_accessed TIMESTAMP,
|
|
46
|
+
access_count INTEGER DEFAULT 0,
|
|
47
|
+
decay_rate REAL DEFAULT 0.1
|
|
48
|
+
)
|
|
49
|
+
''')
|
|
50
|
+
|
|
51
|
+
conn.commit()
|
|
52
|
+
conn.close()
|
|
53
|
+
|
|
54
|
+
def calculate_retention(self, days_since_access: float,
|
|
55
|
+
importance: float,
|
|
56
|
+
emotion: str = "neutral") -> float:
|
|
57
|
+
"""
|
|
58
|
+
Calculate memory retention rate.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
days_since_access: Days since last access
|
|
62
|
+
importance: Memory importance (0-1)
|
|
63
|
+
emotion: Emotional marker
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Retention rate (0-1)
|
|
67
|
+
"""
|
|
68
|
+
# Base strength from importance
|
|
69
|
+
strength = importance
|
|
70
|
+
|
|
71
|
+
# Emotion enhancement
|
|
72
|
+
emotion_multiplier = {
|
|
73
|
+
"milestone": 1.5,
|
|
74
|
+
"negative": 1.3,
|
|
75
|
+
"positive": 1.2,
|
|
76
|
+
"neutral": 1.0
|
|
77
|
+
}
|
|
78
|
+
strength *= emotion_multiplier.get(emotion, 1.0)
|
|
79
|
+
|
|
80
|
+
# Ebbinghaus formula
|
|
81
|
+
if days_since_access <= 0:
|
|
82
|
+
return 1.0
|
|
83
|
+
|
|
84
|
+
retention = math.exp(-days_since_access / (strength * 100))
|
|
85
|
+
|
|
86
|
+
# Minimum retention
|
|
87
|
+
return max(0.1, min(1.0, retention))
|
|
88
|
+
|
|
89
|
+
def apply_forgetting_curve(self, min_importance: float = 0.3):
|
|
90
|
+
"""
|
|
91
|
+
Apply forgetting curve to all memories.
|
|
92
|
+
|
|
93
|
+
Memories below min_importance will be candidates for removal.
|
|
94
|
+
"""
|
|
95
|
+
conn = sqlite3.connect(self.db_path)
|
|
96
|
+
cursor = conn.cursor()
|
|
97
|
+
|
|
98
|
+
# Get all facts
|
|
99
|
+
cursor.execute('''
|
|
100
|
+
SELECT f.id, f.importance, f.emotion, f.last_accessed, f.created_at
|
|
101
|
+
FROM facts f
|
|
102
|
+
WHERE f.importance >= ?
|
|
103
|
+
''', (min_importance,))
|
|
104
|
+
|
|
105
|
+
facts = cursor.fetchall()
|
|
106
|
+
updated = 0
|
|
107
|
+
|
|
108
|
+
for fact_id, importance, emotion, last_accessed, created_at in facts:
|
|
109
|
+
# Calculate days since access
|
|
110
|
+
if last_accessed:
|
|
111
|
+
last = datetime.fromisoformat(last_accessed)
|
|
112
|
+
elif created_at:
|
|
113
|
+
last = datetime.fromisoformat(created_at)
|
|
114
|
+
else:
|
|
115
|
+
last = datetime.now()
|
|
116
|
+
|
|
117
|
+
days_since = (datetime.now() - last).days
|
|
118
|
+
|
|
119
|
+
# Calculate new retention
|
|
120
|
+
retention = self.calculate_retention(days_since, importance, emotion or "neutral")
|
|
121
|
+
|
|
122
|
+
# Update strength
|
|
123
|
+
new_strength = importance * retention
|
|
124
|
+
|
|
125
|
+
# Core memories (importance >= 0.9) don't decay below 0.8
|
|
126
|
+
if importance >= 0.9:
|
|
127
|
+
new_strength = max(0.8, new_strength)
|
|
128
|
+
|
|
129
|
+
cursor.execute('''
|
|
130
|
+
INSERT OR REPLACE INTO memory_strength
|
|
131
|
+
(memory_id, memory_type, current_strength, last_accessed)
|
|
132
|
+
VALUES (?, 'fact', ?, ?)
|
|
133
|
+
''', (fact_id, new_strength, datetime.now().isoformat()))
|
|
134
|
+
|
|
135
|
+
updated += 1
|
|
136
|
+
|
|
137
|
+
conn.commit()
|
|
138
|
+
conn.close()
|
|
139
|
+
|
|
140
|
+
return updated
|
|
141
|
+
|
|
142
|
+
def reinforce_memory(self, memory_id: int, factor: float = 1.1):
|
|
143
|
+
"""
|
|
144
|
+
Reinforce memory strength (called on access).
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
memory_id: Memory ID
|
|
148
|
+
factor: Reinforcement factor (default 1.1 = 10% increase)
|
|
149
|
+
"""
|
|
150
|
+
conn = sqlite3.connect(self.db_path)
|
|
151
|
+
cursor = conn.cursor()
|
|
152
|
+
|
|
153
|
+
cursor.execute('''
|
|
154
|
+
UPDATE memory_strength
|
|
155
|
+
SET current_strength = MIN(1.0, current_strength * ?),
|
|
156
|
+
access_count = access_count + 1,
|
|
157
|
+
last_accessed = ?
|
|
158
|
+
WHERE memory_id = ?
|
|
159
|
+
''', (factor, datetime.now().isoformat(), memory_id))
|
|
160
|
+
|
|
161
|
+
conn.commit()
|
|
162
|
+
conn.close()
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MemoryCoreClaw - Heuristic Engine Module
|
|
3
|
+
|
|
4
|
+
Implements cognitive schema recognition and thought patterns.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List, Dict, Optional
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class Schema:
|
|
14
|
+
"""A cognitive schema pattern"""
|
|
15
|
+
name: str
|
|
16
|
+
description: str
|
|
17
|
+
patterns: List[str]
|
|
18
|
+
triggers: List[str]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class HeuristicEngine:
|
|
22
|
+
"""
|
|
23
|
+
Cognitive Heuristic Engine
|
|
24
|
+
|
|
25
|
+
Recognizes thought patterns and cognitive schemas.
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
he = HeuristicEngine()
|
|
29
|
+
schemas = he.recognize("Why did this happen? How can I fix it?")
|
|
30
|
+
for s in schemas:
|
|
31
|
+
print(f"Detected: {s.name}")
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# Predefined cognitive schemas
|
|
35
|
+
DEFAULT_SCHEMAS = [
|
|
36
|
+
Schema(
|
|
37
|
+
name="problem_solving",
|
|
38
|
+
description="Identifying problem and seeking solution",
|
|
39
|
+
patterns=[
|
|
40
|
+
r"why\s+did",
|
|
41
|
+
r"how\s+can\s+i\s+fix",
|
|
42
|
+
r"what\s+caused",
|
|
43
|
+
r"how\s+to\s+solve"
|
|
44
|
+
],
|
|
45
|
+
triggers=["why", "how", "fix", "solve", "problem"]
|
|
46
|
+
),
|
|
47
|
+
Schema(
|
|
48
|
+
name="planning",
|
|
49
|
+
description="Planning future actions",
|
|
50
|
+
patterns=[
|
|
51
|
+
r"i\s+will",
|
|
52
|
+
r"we\s+plan\s+to",
|
|
53
|
+
r"scheduled\s+for",
|
|
54
|
+
r"next\s+step"
|
|
55
|
+
],
|
|
56
|
+
triggers=["plan", "will", "schedule", "next", "tomorrow"]
|
|
57
|
+
),
|
|
58
|
+
Schema(
|
|
59
|
+
name="learning",
|
|
60
|
+
description="Learning from experience",
|
|
61
|
+
patterns=[
|
|
62
|
+
r"i\s+learned",
|
|
63
|
+
r"the\s+lesson\s+is",
|
|
64
|
+
r"i\s+realized",
|
|
65
|
+
r"now\s+i\s+know"
|
|
66
|
+
],
|
|
67
|
+
triggers=["learned", "lesson", "realized", "know", "understand"]
|
|
68
|
+
),
|
|
69
|
+
Schema(
|
|
70
|
+
name="inquiry",
|
|
71
|
+
description="Asking questions to understand",
|
|
72
|
+
patterns=[
|
|
73
|
+
r"who\s+is",
|
|
74
|
+
r"what\s+is",
|
|
75
|
+
r"where\s+is",
|
|
76
|
+
r"when\s+did",
|
|
77
|
+
r"\?$"
|
|
78
|
+
],
|
|
79
|
+
triggers=["who", "what", "where", "when", "?"]
|
|
80
|
+
),
|
|
81
|
+
Schema(
|
|
82
|
+
name="reflection",
|
|
83
|
+
description="Reflecting on past events",
|
|
84
|
+
patterns=[
|
|
85
|
+
r"i\s+remember",
|
|
86
|
+
r"looking\s+back",
|
|
87
|
+
r"in\s+the\s+past",
|
|
88
|
+
r"previously"
|
|
89
|
+
],
|
|
90
|
+
triggers=["remember", "past", "before", "previously"]
|
|
91
|
+
),
|
|
92
|
+
Schema(
|
|
93
|
+
name="decision",
|
|
94
|
+
description="Making a decision",
|
|
95
|
+
patterns=[
|
|
96
|
+
r"i\s+decided",
|
|
97
|
+
r"i\s+chose",
|
|
98
|
+
r"the\s+best\s+option",
|
|
99
|
+
r"i\s+prefer"
|
|
100
|
+
],
|
|
101
|
+
triggers=["decided", "chose", "option", "prefer", "better"]
|
|
102
|
+
),
|
|
103
|
+
Schema(
|
|
104
|
+
name="emotion_expression",
|
|
105
|
+
description="Expressing emotions",
|
|
106
|
+
patterns=[
|
|
107
|
+
r"i\s+feel",
|
|
108
|
+
r"i\s+am\s+(happy|sad|angry|worried|excited)",
|
|
109
|
+
r"makes\s+me\s+feel"
|
|
110
|
+
],
|
|
111
|
+
triggers=["feel", "happy", "sad", "angry", "worried", "excited"]
|
|
112
|
+
)
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
def __init__(self, custom_schemas: List[Schema] = None):
|
|
116
|
+
"""
|
|
117
|
+
Initialize heuristic engine.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
custom_schemas: Additional schemas to include
|
|
121
|
+
"""
|
|
122
|
+
self.schemas = self.DEFAULT_SCHEMAS.copy()
|
|
123
|
+
if custom_schemas:
|
|
124
|
+
self.schemas.extend(custom_schemas)
|
|
125
|
+
|
|
126
|
+
def recognize(self, text: str) -> List[Schema]:
|
|
127
|
+
"""
|
|
128
|
+
Recognize cognitive schemas in text.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
text: Input text
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
List of matched schemas (sorted by relevance)
|
|
135
|
+
"""
|
|
136
|
+
text_lower = text.lower()
|
|
137
|
+
matches = []
|
|
138
|
+
|
|
139
|
+
for schema in self.schemas:
|
|
140
|
+
score = 0
|
|
141
|
+
|
|
142
|
+
# Check patterns
|
|
143
|
+
for pattern in schema.patterns:
|
|
144
|
+
if re.search(pattern, text_lower):
|
|
145
|
+
score += 2
|
|
146
|
+
|
|
147
|
+
# Check triggers
|
|
148
|
+
for trigger in schema.triggers:
|
|
149
|
+
if trigger in text_lower:
|
|
150
|
+
score += 1
|
|
151
|
+
|
|
152
|
+
if score > 0:
|
|
153
|
+
matches.append((schema, score))
|
|
154
|
+
|
|
155
|
+
# Sort by score
|
|
156
|
+
matches.sort(key=lambda x: x[1], reverse=True)
|
|
157
|
+
|
|
158
|
+
return [m[0] for m in matches]
|
|
159
|
+
|
|
160
|
+
def get_schema(self, name: str) -> Optional[Schema]:
|
|
161
|
+
"""Get a schema by name"""
|
|
162
|
+
for schema in self.schemas:
|
|
163
|
+
if schema.name == name:
|
|
164
|
+
return schema
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
def add_schema(self, schema: Schema) -> bool:
|
|
168
|
+
"""Add a custom schema"""
|
|
169
|
+
if self.get_schema(schema.name):
|
|
170
|
+
return False
|
|
171
|
+
self.schemas.append(schema)
|
|
172
|
+
return True
|
|
173
|
+
|
|
174
|
+
def suggest_followup(self, schema_name: str) -> List[str]:
|
|
175
|
+
"""
|
|
176
|
+
Suggest follow-up questions/actions for a schema.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
schema_name: Name of detected schema
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
List of suggestions
|
|
183
|
+
"""
|
|
184
|
+
suggestions = {
|
|
185
|
+
"problem_solving": [
|
|
186
|
+
"What is the root cause?",
|
|
187
|
+
"What solutions have been tried?",
|
|
188
|
+
"What resources are available?"
|
|
189
|
+
],
|
|
190
|
+
"planning": [
|
|
191
|
+
"What is the timeline?",
|
|
192
|
+
"Who is responsible?",
|
|
193
|
+
"What could go wrong?"
|
|
194
|
+
],
|
|
195
|
+
"learning": [
|
|
196
|
+
"How can this be applied elsewhere?",
|
|
197
|
+
"What would you do differently?",
|
|
198
|
+
"Who else should know this?"
|
|
199
|
+
],
|
|
200
|
+
"inquiry": [
|
|
201
|
+
"What do you already know?",
|
|
202
|
+
"Where can you find more information?",
|
|
203
|
+
"Who might have the answer?"
|
|
204
|
+
],
|
|
205
|
+
"reflection": [
|
|
206
|
+
"What changed since then?",
|
|
207
|
+
"What did you learn?",
|
|
208
|
+
"How does this affect the future?"
|
|
209
|
+
],
|
|
210
|
+
"decision": [
|
|
211
|
+
"What alternatives were considered?",
|
|
212
|
+
"What are the trade-offs?",
|
|
213
|
+
"When will you reevaluate?"
|
|
214
|
+
]
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return suggestions.get(schema_name, [])
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MemoryCoreClaw - Working Memory Module
|
|
3
|
+
|
|
4
|
+
Implements limited-capacity temporary storage (7±2 model).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
from typing import Optional, Any, Dict, List
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
import json
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class WorkingItem:
|
|
15
|
+
"""An item in working memory"""
|
|
16
|
+
key: str
|
|
17
|
+
value: Any
|
|
18
|
+
priority: float
|
|
19
|
+
created_at: datetime
|
|
20
|
+
expires_at: Optional[datetime] = None
|
|
21
|
+
access_count: int = 0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class WorkingMemory:
|
|
25
|
+
"""
|
|
26
|
+
Working Memory Engine
|
|
27
|
+
|
|
28
|
+
Based on Baddeley's working memory model with capacity limit.
|
|
29
|
+
|
|
30
|
+
Features:
|
|
31
|
+
- Limited capacity (default 9 items, 7±2)
|
|
32
|
+
- Priority-based eviction
|
|
33
|
+
- TTL support
|
|
34
|
+
- Access tracking
|
|
35
|
+
|
|
36
|
+
Usage:
|
|
37
|
+
wm = WorkingMemory()
|
|
38
|
+
wm.hold("task", "processing", priority=0.9)
|
|
39
|
+
task = wm.retrieve("task")
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
DEFAULT_CAPACITY = 9 # 7±2 model
|
|
43
|
+
|
|
44
|
+
def __init__(self, capacity: int = DEFAULT_CAPACITY):
|
|
45
|
+
"""
|
|
46
|
+
Initialize working memory.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
capacity: Maximum items (default 9)
|
|
50
|
+
"""
|
|
51
|
+
self.capacity = capacity
|
|
52
|
+
self.items: Dict[str, WorkingItem] = {}
|
|
53
|
+
self.stats = {
|
|
54
|
+
'adds': 0,
|
|
55
|
+
'retrieves': 0,
|
|
56
|
+
'evictions': 0,
|
|
57
|
+
'expirations': 0
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
def hold(
|
|
61
|
+
self,
|
|
62
|
+
key: str,
|
|
63
|
+
value: Any,
|
|
64
|
+
priority: float = 0.5,
|
|
65
|
+
ttl_seconds: int = None
|
|
66
|
+
) -> bool:
|
|
67
|
+
"""
|
|
68
|
+
Store an item in working memory.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
key: Item key
|
|
72
|
+
value: Item value
|
|
73
|
+
priority: Priority (0-1, higher = less likely to evict)
|
|
74
|
+
ttl_seconds: Time to live in seconds
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
True if stored successfully
|
|
78
|
+
"""
|
|
79
|
+
# Evict if at capacity
|
|
80
|
+
if len(self.items) >= self.capacity and key not in self.items:
|
|
81
|
+
self._evict_lowest_priority()
|
|
82
|
+
|
|
83
|
+
expires_at = None
|
|
84
|
+
if ttl_seconds:
|
|
85
|
+
expires_at = datetime.now() + timedelta(seconds=ttl_seconds)
|
|
86
|
+
|
|
87
|
+
self.items[key] = WorkingItem(
|
|
88
|
+
key=key,
|
|
89
|
+
value=value,
|
|
90
|
+
priority=priority,
|
|
91
|
+
created_at=datetime.now(),
|
|
92
|
+
expires_at=expires_at
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
self.stats['adds'] += 1
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
def retrieve(self, key: str) -> Optional[Any]:
|
|
99
|
+
"""
|
|
100
|
+
Retrieve an item from working memory.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
key: Item key
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Item value or None if not found/expired
|
|
107
|
+
"""
|
|
108
|
+
if key not in self.items:
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
item = self.items[key]
|
|
112
|
+
|
|
113
|
+
# Check expiration
|
|
114
|
+
if item.expires_at and datetime.now() > item.expires_at:
|
|
115
|
+
del self.items[key]
|
|
116
|
+
self.stats['expirations'] += 1
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
item.access_count += 1
|
|
120
|
+
self.stats['retrieves'] += 1
|
|
121
|
+
return item.value
|
|
122
|
+
|
|
123
|
+
def forget(self, key: str) -> bool:
|
|
124
|
+
"""Remove an item"""
|
|
125
|
+
if key in self.items:
|
|
126
|
+
del self.items[key]
|
|
127
|
+
return True
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
def clear(self) -> int:
|
|
131
|
+
"""Clear all items"""
|
|
132
|
+
count = len(self.items)
|
|
133
|
+
self.items.clear()
|
|
134
|
+
return count
|
|
135
|
+
|
|
136
|
+
def _evict_lowest_priority(self):
|
|
137
|
+
"""Evict the lowest priority item"""
|
|
138
|
+
if not self.items:
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
# Find lowest priority item
|
|
142
|
+
lowest_key = min(self.items.keys(), key=lambda k: self.items[k].priority)
|
|
143
|
+
del self.items[lowest_key]
|
|
144
|
+
self.stats['evictions'] += 1
|
|
145
|
+
|
|
146
|
+
def get_all(self) -> List[Dict]:
|
|
147
|
+
"""Get all items"""
|
|
148
|
+
self._clean_expired()
|
|
149
|
+
return [
|
|
150
|
+
{
|
|
151
|
+
'key': item.key,
|
|
152
|
+
'value': item.value,
|
|
153
|
+
'priority': item.priority,
|
|
154
|
+
'created_at': item.created_at.isoformat(),
|
|
155
|
+
'expires_at': item.expires_at.isoformat() if item.expires_at else None
|
|
156
|
+
}
|
|
157
|
+
for item in self.items.values()
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
def _clean_expired(self):
|
|
161
|
+
"""Remove expired items"""
|
|
162
|
+
now = datetime.now()
|
|
163
|
+
expired = [
|
|
164
|
+
key for key, item in self.items.items()
|
|
165
|
+
if item.expires_at and now > item.expires_at
|
|
166
|
+
]
|
|
167
|
+
for key in expired:
|
|
168
|
+
del self.items[key]
|
|
169
|
+
self.stats['expirations'] += 1
|
|
170
|
+
|
|
171
|
+
def get_stats(self) -> Dict:
|
|
172
|
+
"""Get statistics"""
|
|
173
|
+
self._clean_expired()
|
|
174
|
+
return {
|
|
175
|
+
'capacity': self.capacity,
|
|
176
|
+
'used': len(self.items),
|
|
177
|
+
'utilization': len(self.items) / self.capacity,
|
|
178
|
+
'stats': self.stats
|
|
179
|
+
}
|