powermem 0.1.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.
- powermem/__init__.py +103 -0
- powermem/agent/__init__.py +35 -0
- powermem/agent/abstract/__init__.py +22 -0
- powermem/agent/abstract/collaboration.py +259 -0
- powermem/agent/abstract/context.py +187 -0
- powermem/agent/abstract/manager.py +232 -0
- powermem/agent/abstract/permission.py +217 -0
- powermem/agent/abstract/privacy.py +267 -0
- powermem/agent/abstract/scope.py +199 -0
- powermem/agent/agent.py +791 -0
- powermem/agent/components/__init__.py +18 -0
- powermem/agent/components/collaboration_coordinator.py +645 -0
- powermem/agent/components/permission_controller.py +586 -0
- powermem/agent/components/privacy_protector.py +767 -0
- powermem/agent/components/scope_controller.py +685 -0
- powermem/agent/factories/__init__.py +16 -0
- powermem/agent/factories/agent_factory.py +266 -0
- powermem/agent/factories/config_factory.py +308 -0
- powermem/agent/factories/memory_factory.py +229 -0
- powermem/agent/implementations/__init__.py +16 -0
- powermem/agent/implementations/hybrid.py +728 -0
- powermem/agent/implementations/multi_agent.py +1040 -0
- powermem/agent/implementations/multi_user.py +1020 -0
- powermem/agent/types.py +53 -0
- powermem/agent/wrappers/__init__.py +14 -0
- powermem/agent/wrappers/agent_memory_wrapper.py +427 -0
- powermem/agent/wrappers/compatibility_wrapper.py +520 -0
- powermem/config_loader.py +318 -0
- powermem/configs.py +249 -0
- powermem/core/__init__.py +19 -0
- powermem/core/async_memory.py +1493 -0
- powermem/core/audit.py +258 -0
- powermem/core/base.py +165 -0
- powermem/core/memory.py +1567 -0
- powermem/core/setup.py +162 -0
- powermem/core/telemetry.py +215 -0
- powermem/integrations/__init__.py +17 -0
- powermem/integrations/embeddings/__init__.py +13 -0
- powermem/integrations/embeddings/aws_bedrock.py +100 -0
- powermem/integrations/embeddings/azure_openai.py +55 -0
- powermem/integrations/embeddings/base.py +31 -0
- powermem/integrations/embeddings/config/base.py +132 -0
- powermem/integrations/embeddings/configs.py +31 -0
- powermem/integrations/embeddings/factory.py +48 -0
- powermem/integrations/embeddings/gemini.py +39 -0
- powermem/integrations/embeddings/huggingface.py +41 -0
- powermem/integrations/embeddings/langchain.py +35 -0
- powermem/integrations/embeddings/lmstudio.py +29 -0
- powermem/integrations/embeddings/mock.py +11 -0
- powermem/integrations/embeddings/ollama.py +53 -0
- powermem/integrations/embeddings/openai.py +49 -0
- powermem/integrations/embeddings/qwen.py +102 -0
- powermem/integrations/embeddings/together.py +31 -0
- powermem/integrations/embeddings/vertexai.py +54 -0
- powermem/integrations/llm/__init__.py +18 -0
- powermem/integrations/llm/anthropic.py +87 -0
- powermem/integrations/llm/base.py +132 -0
- powermem/integrations/llm/config/anthropic.py +56 -0
- powermem/integrations/llm/config/azure.py +56 -0
- powermem/integrations/llm/config/base.py +62 -0
- powermem/integrations/llm/config/deepseek.py +56 -0
- powermem/integrations/llm/config/ollama.py +56 -0
- powermem/integrations/llm/config/openai.py +79 -0
- powermem/integrations/llm/config/qwen.py +68 -0
- powermem/integrations/llm/config/qwen_asr.py +46 -0
- powermem/integrations/llm/config/vllm.py +56 -0
- powermem/integrations/llm/configs.py +26 -0
- powermem/integrations/llm/deepseek.py +106 -0
- powermem/integrations/llm/factory.py +118 -0
- powermem/integrations/llm/gemini.py +201 -0
- powermem/integrations/llm/langchain.py +65 -0
- powermem/integrations/llm/ollama.py +106 -0
- powermem/integrations/llm/openai.py +166 -0
- powermem/integrations/llm/openai_structured.py +80 -0
- powermem/integrations/llm/qwen.py +207 -0
- powermem/integrations/llm/qwen_asr.py +171 -0
- powermem/integrations/llm/vllm.py +106 -0
- powermem/integrations/rerank/__init__.py +20 -0
- powermem/integrations/rerank/base.py +43 -0
- powermem/integrations/rerank/config/__init__.py +7 -0
- powermem/integrations/rerank/config/base.py +27 -0
- powermem/integrations/rerank/configs.py +23 -0
- powermem/integrations/rerank/factory.py +68 -0
- powermem/integrations/rerank/qwen.py +159 -0
- powermem/intelligence/__init__.py +17 -0
- powermem/intelligence/ebbinghaus_algorithm.py +354 -0
- powermem/intelligence/importance_evaluator.py +361 -0
- powermem/intelligence/intelligent_memory_manager.py +284 -0
- powermem/intelligence/manager.py +148 -0
- powermem/intelligence/plugin.py +229 -0
- powermem/prompts/__init__.py +29 -0
- powermem/prompts/graph/graph_prompts.py +217 -0
- powermem/prompts/graph/graph_tools_prompts.py +469 -0
- powermem/prompts/importance_evaluation.py +246 -0
- powermem/prompts/intelligent_memory_prompts.py +163 -0
- powermem/prompts/templates.py +193 -0
- powermem/storage/__init__.py +14 -0
- powermem/storage/adapter.py +896 -0
- powermem/storage/base.py +109 -0
- powermem/storage/config/base.py +13 -0
- powermem/storage/config/oceanbase.py +58 -0
- powermem/storage/config/pgvector.py +52 -0
- powermem/storage/config/sqlite.py +27 -0
- powermem/storage/configs.py +159 -0
- powermem/storage/factory.py +59 -0
- powermem/storage/migration_manager.py +438 -0
- powermem/storage/oceanbase/__init__.py +8 -0
- powermem/storage/oceanbase/constants.py +162 -0
- powermem/storage/oceanbase/oceanbase.py +1384 -0
- powermem/storage/oceanbase/oceanbase_graph.py +1441 -0
- powermem/storage/pgvector/__init__.py +7 -0
- powermem/storage/pgvector/pgvector.py +420 -0
- powermem/storage/sqlite/__init__.py +0 -0
- powermem/storage/sqlite/sqlite.py +218 -0
- powermem/storage/sqlite/sqlite_vector_store.py +311 -0
- powermem/utils/__init__.py +35 -0
- powermem/utils/utils.py +605 -0
- powermem/version.py +23 -0
- powermem-0.1.0.dist-info/METADATA +187 -0
- powermem-0.1.0.dist-info/RECORD +123 -0
- powermem-0.1.0.dist-info/WHEEL +5 -0
- powermem-0.1.0.dist-info/licenses/LICENSE +206 -0
- powermem-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1020 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Multi-User Memory Manager Implementation
|
|
3
|
+
|
|
4
|
+
This module provides the concrete implementation of the multi-user memory manager,
|
|
5
|
+
designed for single-agent scenarios with multiple users.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Any, Dict, List, Optional, Union
|
|
11
|
+
from powermem.agent.types import (
|
|
12
|
+
CollaborationLevel,
|
|
13
|
+
MemoryType,
|
|
14
|
+
MemoryScope,
|
|
15
|
+
PrivacyLevel,
|
|
16
|
+
AccessPermission
|
|
17
|
+
)
|
|
18
|
+
from powermem.intelligence.intelligent_memory_manager import IntelligentMemoryManager
|
|
19
|
+
from powermem.agent.abstract.manager import AgentMemoryManagerBase
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MultiUserMemoryManager(AgentMemoryManagerBase):
|
|
25
|
+
"""
|
|
26
|
+
Multi-user memory manager implementation.
|
|
27
|
+
|
|
28
|
+
Designed for single-agent scenarios with multiple users, providing
|
|
29
|
+
user isolation, cross-user sharing, and privacy protection.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, config: Dict[str, Any]):
|
|
33
|
+
"""
|
|
34
|
+
Initialize the multi-user memory manager.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
config: Memory configuration object
|
|
38
|
+
"""
|
|
39
|
+
super().__init__(config)
|
|
40
|
+
self.multi_user_config = config.agent_memory.multi_user_config
|
|
41
|
+
|
|
42
|
+
# Initialize components
|
|
43
|
+
self.intelligent_manager = None
|
|
44
|
+
|
|
45
|
+
# User-based memory storage
|
|
46
|
+
self.user_memories = {}
|
|
47
|
+
self.shared_memories = {}
|
|
48
|
+
self.user_sessions = {}
|
|
49
|
+
|
|
50
|
+
# User context tracking
|
|
51
|
+
self.user_contexts = {}
|
|
52
|
+
self.user_preferences = {}
|
|
53
|
+
|
|
54
|
+
# Cross-user sharing tracking
|
|
55
|
+
self.sharing_relationships = {}
|
|
56
|
+
self.consent_records = {}
|
|
57
|
+
|
|
58
|
+
def initialize(self) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Initialize the memory manager and all components.
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
# Initialize intelligent memory manager
|
|
64
|
+
self.intelligent_manager = IntelligentMemoryManager(self.config)
|
|
65
|
+
|
|
66
|
+
# Initialize user contexts
|
|
67
|
+
self._initialize_user_contexts()
|
|
68
|
+
|
|
69
|
+
self.initialized = True
|
|
70
|
+
logger.info("Multi-user memory manager initialized successfully")
|
|
71
|
+
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.error(f"Failed to initialize multi-user memory manager: {e}")
|
|
74
|
+
raise
|
|
75
|
+
|
|
76
|
+
def _initialize_user_contexts(self) -> None:
|
|
77
|
+
"""Initialize user contexts from configuration."""
|
|
78
|
+
# Initialize default user context settings
|
|
79
|
+
default_context = {
|
|
80
|
+
'max_memories': self.multi_user_config.user_context_config.get('max_user_memories', 10000),
|
|
81
|
+
'session_timeout': self.multi_user_config.user_context_config.get('user_session_timeout', 3600),
|
|
82
|
+
'privacy_level': PrivacyLevel.STANDARD,
|
|
83
|
+
'sharing_enabled': self.multi_user_config.cross_user_sharing,
|
|
84
|
+
'created_at': datetime.now().isoformat(),
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Store default context template
|
|
88
|
+
self.default_user_context = default_context
|
|
89
|
+
|
|
90
|
+
def process_memory(
|
|
91
|
+
self,
|
|
92
|
+
content: str,
|
|
93
|
+
agent_id: str,
|
|
94
|
+
context: Optional[Dict[str, Any]] = None,
|
|
95
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
96
|
+
) -> Dict[str, Any]:
|
|
97
|
+
"""
|
|
98
|
+
Process and store a new memory.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
content: The memory content to store
|
|
102
|
+
agent_id: ID of the user creating the memory
|
|
103
|
+
context: Additional context information
|
|
104
|
+
metadata: Additional metadata for the memory
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Dictionary containing the processed memory information
|
|
108
|
+
"""
|
|
109
|
+
try:
|
|
110
|
+
# Extract user information
|
|
111
|
+
user_id = self._extract_user_id(agent_id, context, metadata)
|
|
112
|
+
|
|
113
|
+
# Check user isolation
|
|
114
|
+
if self.multi_user_config.user_isolation:
|
|
115
|
+
self._ensure_user_isolation(user_id)
|
|
116
|
+
|
|
117
|
+
# Add user collaboration context information to metadata
|
|
118
|
+
agent_context_metadata = metadata or {}
|
|
119
|
+
|
|
120
|
+
# Get user collaboration information
|
|
121
|
+
user_collaboration_info = self._get_user_collaboration_context(user_id, context)
|
|
122
|
+
|
|
123
|
+
# Get user permission information
|
|
124
|
+
user_permission_info = self._get_user_permission_context(user_id, agent_id)
|
|
125
|
+
|
|
126
|
+
# Get user sharing information
|
|
127
|
+
user_sharing_info = self._get_user_sharing_context(user_id, context)
|
|
128
|
+
|
|
129
|
+
# Organize all agent/user-related information under 'agent' key
|
|
130
|
+
agent_info = {
|
|
131
|
+
'agent_id': agent_id,
|
|
132
|
+
'user_id': user_id,
|
|
133
|
+
'mode': 'multi_user',
|
|
134
|
+
'collaboration': user_collaboration_info,
|
|
135
|
+
'permissions': user_permission_info,
|
|
136
|
+
'sharing': user_sharing_info,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
agent_context_metadata['agent'] = agent_info
|
|
140
|
+
|
|
141
|
+
# Process with intelligent memory manager
|
|
142
|
+
enhanced_metadata = self.intelligent_manager.process_metadata(
|
|
143
|
+
content=content,
|
|
144
|
+
metadata=agent_context_metadata,
|
|
145
|
+
context=context or {}
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Determine memory type from enhanced metadata
|
|
149
|
+
memory_type = self._determine_memory_type_from_metadata(enhanced_metadata)
|
|
150
|
+
|
|
151
|
+
# Determine scope based on sharing settings
|
|
152
|
+
scope = self._determine_user_scope(user_id, context, enhanced_metadata)
|
|
153
|
+
|
|
154
|
+
# Persist to database first to get Snowflake ID
|
|
155
|
+
# Use temporary memory data for database insertion
|
|
156
|
+
temp_memory_data = {
|
|
157
|
+
'content': content,
|
|
158
|
+
'user_id': user_id,
|
|
159
|
+
'agent_id': agent_id,
|
|
160
|
+
'scope': scope,
|
|
161
|
+
'memory_type': memory_type,
|
|
162
|
+
'metadata': enhanced_metadata,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# Get Snowflake ID from database
|
|
166
|
+
memory_id = self._persist_memory_to_storage(temp_memory_data)
|
|
167
|
+
if not memory_id:
|
|
168
|
+
raise ValueError("Failed to get memory ID from database")
|
|
169
|
+
|
|
170
|
+
# Create complete memory data with Snowflake ID from database
|
|
171
|
+
memory_data = {
|
|
172
|
+
'id': memory_id,
|
|
173
|
+
'content': content, # Keep original content unchanged
|
|
174
|
+
'user_id': user_id,
|
|
175
|
+
'agent_id': agent_id, # Keep for compatibility
|
|
176
|
+
'scope': scope,
|
|
177
|
+
'memory_type': memory_type,
|
|
178
|
+
'metadata': enhanced_metadata, # Use enhanced metadata
|
|
179
|
+
'context': context or {},
|
|
180
|
+
'created_at': datetime.now().isoformat(),
|
|
181
|
+
'updated_at': datetime.now().isoformat(),
|
|
182
|
+
'access_count': 0,
|
|
183
|
+
'last_accessed': None,
|
|
184
|
+
'retention_score': enhanced_metadata.get('intelligence', {}).get('current_retention', 1.0),
|
|
185
|
+
'importance_level': enhanced_metadata.get('intelligence', {}).get('importance_score'),
|
|
186
|
+
'privacy_level': self._determine_privacy_level(enhanced_metadata),
|
|
187
|
+
'shared_with': enhanced_metadata.get('share_with', []),
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# Store in user-specific storage
|
|
191
|
+
if user_id not in self.user_memories:
|
|
192
|
+
self.user_memories[user_id] = {
|
|
193
|
+
MemoryType.WORKING: {},
|
|
194
|
+
MemoryType.SHORT_TERM: {},
|
|
195
|
+
MemoryType.LONG_TERM: {},
|
|
196
|
+
MemoryType.SEMANTIC: {},
|
|
197
|
+
MemoryType.EPISODIC: {},
|
|
198
|
+
MemoryType.PROCEDURAL: {},
|
|
199
|
+
MemoryType.PUBLIC_SHARED: {},
|
|
200
|
+
MemoryType.PRIVATE_AGENT: {},
|
|
201
|
+
MemoryType.COLLABORATIVE: {},
|
|
202
|
+
MemoryType.GROUP_CONSENSUS: {},
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
self.user_memories[user_id][memory_type][memory_id] = memory_data
|
|
206
|
+
|
|
207
|
+
# Handle cross-user sharing if enabled
|
|
208
|
+
if self.multi_user_config.cross_user_sharing and memory_data['shared_with']:
|
|
209
|
+
self._handle_cross_user_sharing(memory_id, user_id, memory_data['shared_with'])
|
|
210
|
+
|
|
211
|
+
# Update user context
|
|
212
|
+
self._update_user_context(user_id, memory_data)
|
|
213
|
+
|
|
214
|
+
# Apply privacy protection
|
|
215
|
+
self._apply_privacy_protection(memory_id, user_id, memory_data)
|
|
216
|
+
|
|
217
|
+
logger.info(f"Processed memory {memory_id} for user {user_id} with scope {scope}")
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
'id': memory_id,
|
|
221
|
+
'memory': content,
|
|
222
|
+
'scope': scope.value,
|
|
223
|
+
'memory_type': memory_type.value,
|
|
224
|
+
'user_id': user_id,
|
|
225
|
+
'agent_id': agent_id,
|
|
226
|
+
'created_at': memory_data['created_at'],
|
|
227
|
+
'metadata': memory_data['metadata'],
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
except Exception as e:
|
|
231
|
+
logger.error(f"Failed to process memory for user {agent_id}: {e}")
|
|
232
|
+
raise
|
|
233
|
+
|
|
234
|
+
def _persist_memory_to_storage(self, memory_data: Dict[str, Any]) -> int:
|
|
235
|
+
"""
|
|
236
|
+
Persist memory data to database and vector store.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
memory_data: Memory data dictionary (minimal data for database insertion)
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Snowflake ID (int) from database
|
|
243
|
+
"""
|
|
244
|
+
try:
|
|
245
|
+
# Use existing Memory infrastructure
|
|
246
|
+
if not hasattr(self, '_memory_instance'):
|
|
247
|
+
from powermem.core.memory import Memory
|
|
248
|
+
# Convert ConfigObject back to dict for Memory class
|
|
249
|
+
config_dict = self.config._data if hasattr(self.config, '_data') else self.config
|
|
250
|
+
self._memory_instance = Memory(config_dict)
|
|
251
|
+
|
|
252
|
+
# Use the existing Memory.add() method
|
|
253
|
+
# Get the Snowflake ID returned from database to ensure consistency
|
|
254
|
+
add_result = self._memory_instance.add(
|
|
255
|
+
messages=memory_data['content'],
|
|
256
|
+
user_id=memory_data.get('user_id'),
|
|
257
|
+
agent_id=memory_data.get('agent_id'),
|
|
258
|
+
metadata={
|
|
259
|
+
'scope': memory_data.get('scope').value if memory_data.get('scope') else None,
|
|
260
|
+
'memory_type': memory_data.get('memory_type').value if memory_data.get('memory_type') else None,
|
|
261
|
+
'retention_score': memory_data.get('retention_score'),
|
|
262
|
+
'importance_level': memory_data.get('importance_level'),
|
|
263
|
+
'privacy_level': memory_data.get('privacy_level').value if memory_data.get('privacy_level') else None,
|
|
264
|
+
**memory_data.get('metadata', {})
|
|
265
|
+
}
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Get the Snowflake ID from database
|
|
269
|
+
if add_result and 'results' in add_result and len(add_result['results']) > 0:
|
|
270
|
+
db_memory_id = add_result['results'][0].get('id')
|
|
271
|
+
if db_memory_id:
|
|
272
|
+
logger.info(f"Persisted memory {db_memory_id} to storage")
|
|
273
|
+
return db_memory_id
|
|
274
|
+
else:
|
|
275
|
+
raise ValueError("Failed to get memory ID from database")
|
|
276
|
+
else:
|
|
277
|
+
raise ValueError("Failed to persist memory to database")
|
|
278
|
+
|
|
279
|
+
except Exception as e:
|
|
280
|
+
logger.error(f"Failed to persist memory to storage: {e}")
|
|
281
|
+
# Re-raise exception to allow caller to handle it
|
|
282
|
+
raise
|
|
283
|
+
|
|
284
|
+
def _extract_user_id(
|
|
285
|
+
self,
|
|
286
|
+
agent_id: str,
|
|
287
|
+
context: Optional[Dict[str, Any]],
|
|
288
|
+
metadata: Optional[Dict[str, Any]]
|
|
289
|
+
) -> str:
|
|
290
|
+
"""Extract user ID from various sources."""
|
|
291
|
+
# Priority: metadata > context > agent_id
|
|
292
|
+
if metadata and 'user_id' in metadata:
|
|
293
|
+
return metadata['user_id']
|
|
294
|
+
elif context and 'user_id' in context:
|
|
295
|
+
return context['user_id']
|
|
296
|
+
else:
|
|
297
|
+
return agent_id # Use agent_id as user_id in multi-user mode
|
|
298
|
+
|
|
299
|
+
def _ensure_user_isolation(self, user_id: str) -> None:
|
|
300
|
+
"""Ensure user isolation is maintained."""
|
|
301
|
+
# Check memory limits
|
|
302
|
+
user_memory_count = self._get_user_memory_count(user_id)
|
|
303
|
+
max_memories = self.multi_user_config.user_context_config.get('max_user_memories', 10000)
|
|
304
|
+
|
|
305
|
+
if user_memory_count >= max_memories:
|
|
306
|
+
# Clean up old memories if limit exceeded
|
|
307
|
+
self._cleanup_old_user_memories(user_id, user_memory_count - max_memories + 1000)
|
|
308
|
+
|
|
309
|
+
def _determine_user_scope(
|
|
310
|
+
self,
|
|
311
|
+
user_id: str,
|
|
312
|
+
context: Optional[Dict[str, Any]],
|
|
313
|
+
metadata: Optional[Dict[str, Any]]
|
|
314
|
+
) -> MemoryScope:
|
|
315
|
+
"""Determine the appropriate scope for a user memory."""
|
|
316
|
+
# Check if memory is meant to be shared
|
|
317
|
+
if metadata and metadata.get('share_with'):
|
|
318
|
+
return MemoryScope.USER_GROUP
|
|
319
|
+
elif context and context.get('collaboration_level') == 'high':
|
|
320
|
+
return MemoryScope.USER_GROUP
|
|
321
|
+
else:
|
|
322
|
+
return MemoryScope.PRIVATE
|
|
323
|
+
|
|
324
|
+
def _determine_memory_type_from_metadata(self, enhanced_metadata: Dict[str, Any]) -> MemoryType:
|
|
325
|
+
"""Determine the memory type based on enhanced metadata."""
|
|
326
|
+
intelligence = enhanced_metadata.get('intelligence', {})
|
|
327
|
+
memory_type_str = intelligence.get('memory_type', 'working')
|
|
328
|
+
|
|
329
|
+
# Map string to enum
|
|
330
|
+
type_mapping = {
|
|
331
|
+
'working': MemoryType.WORKING,
|
|
332
|
+
'short_term': MemoryType.SHORT_TERM,
|
|
333
|
+
'long_term': MemoryType.LONG_TERM,
|
|
334
|
+
'semantic_memory': MemoryType.SEMANTIC,
|
|
335
|
+
'episodic_memory': MemoryType.EPISODIC,
|
|
336
|
+
'procedural_memory': MemoryType.PROCEDURAL,
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return type_mapping.get(memory_type_str, MemoryType.WORKING)
|
|
340
|
+
|
|
341
|
+
def _get_user_collaboration_context(self, user_id: str, context: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
|
342
|
+
"""Get user collaboration context information."""
|
|
343
|
+
return {
|
|
344
|
+
'is_collaborating': context.get('collaboration_level', 'low') == 'high' if context else False,
|
|
345
|
+
'collaboration_type': context.get('collaboration_type', 'asynchronous') if context else 'asynchronous',
|
|
346
|
+
'collaboration_status': 'active' if context and context.get('collaboration_level') == 'high' else 'inactive',
|
|
347
|
+
'participants': context.get('share_with', []) if context else [],
|
|
348
|
+
'collaboration_level': context.get('collaboration_level', 'low') if context else 'low'
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
def _get_user_permission_context(self, user_id: str, agent_id: str) -> Dict[str, Any]:
|
|
352
|
+
"""Get user permission context information."""
|
|
353
|
+
return {
|
|
354
|
+
'user_permissions': {
|
|
355
|
+
'read': True, # User can always read their own memories
|
|
356
|
+
'write': True, # User can write to their memories
|
|
357
|
+
'delete': True, # User can delete their own memories
|
|
358
|
+
'admin': True # User is admin of their own memories
|
|
359
|
+
},
|
|
360
|
+
'agent_permissions': {
|
|
361
|
+
'read': True, # Agent can read user memories
|
|
362
|
+
'write': True, # Agent can write to user memories
|
|
363
|
+
'delete': False, # Agent cannot delete user memories
|
|
364
|
+
'admin': False # Agent is not admin
|
|
365
|
+
},
|
|
366
|
+
'access_level': 'owner',
|
|
367
|
+
'isolation_enabled': getattr(self.multi_user_config, 'user_isolation', True)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
def _get_user_sharing_context(self, user_id: str, context: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
|
371
|
+
"""Get user sharing context information."""
|
|
372
|
+
sharing_info = {
|
|
373
|
+
'is_shared': False,
|
|
374
|
+
'shared_with': [],
|
|
375
|
+
'sharing_level': 'private',
|
|
376
|
+
'can_share': getattr(self.multi_user_config, 'sharing_enabled', False)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
# Check if memory is being shared
|
|
380
|
+
if context and context.get('share_with'):
|
|
381
|
+
sharing_info.update({
|
|
382
|
+
'is_shared': True,
|
|
383
|
+
'shared_with': context.get('share_with', []),
|
|
384
|
+
'sharing_level': 'collaborative'
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
# Check if user is in any groups (placeholder - would need to be implemented)
|
|
388
|
+
user_groups = [] # MultiUserMemoryManager doesn't have user_groups yet
|
|
389
|
+
if user_groups:
|
|
390
|
+
sharing_info.update({
|
|
391
|
+
'is_shared': True,
|
|
392
|
+
'shared_with': user_groups,
|
|
393
|
+
'sharing_level': 'group'
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
return sharing_info
|
|
397
|
+
|
|
398
|
+
def _determine_privacy_level(self, metadata: Optional[Dict[str, Any]]) -> PrivacyLevel:
|
|
399
|
+
"""Determine privacy level for the memory."""
|
|
400
|
+
if metadata and 'privacy_level' in metadata:
|
|
401
|
+
try:
|
|
402
|
+
return PrivacyLevel(metadata['privacy_level'])
|
|
403
|
+
except ValueError:
|
|
404
|
+
pass
|
|
405
|
+
return PrivacyLevel.STANDARD
|
|
406
|
+
|
|
407
|
+
def _handle_cross_user_sharing(
|
|
408
|
+
self,
|
|
409
|
+
memory_id: str,
|
|
410
|
+
owner_id: str,
|
|
411
|
+
shared_with: List[str]
|
|
412
|
+
) -> None:
|
|
413
|
+
"""Handle cross-user memory sharing."""
|
|
414
|
+
# Check if consent is required
|
|
415
|
+
if self.multi_user_config.user_context_config.get('cross_user_consent_required', True):
|
|
416
|
+
# Record sharing request
|
|
417
|
+
for target_user in shared_with:
|
|
418
|
+
if target_user not in self.consent_records:
|
|
419
|
+
self.consent_records[target_user] = {}
|
|
420
|
+
|
|
421
|
+
self.consent_records[target_user][memory_id] = {
|
|
422
|
+
'owner_id': owner_id,
|
|
423
|
+
'requested_at': datetime.now().isoformat(),
|
|
424
|
+
'consent_given': False,
|
|
425
|
+
'consent_required': True,
|
|
426
|
+
}
|
|
427
|
+
else:
|
|
428
|
+
# Direct sharing without consent
|
|
429
|
+
self._grant_shared_access(memory_id, owner_id, shared_with)
|
|
430
|
+
|
|
431
|
+
def _grant_shared_access(
|
|
432
|
+
self,
|
|
433
|
+
memory_id: str,
|
|
434
|
+
owner_id: str,
|
|
435
|
+
shared_with: List[str]
|
|
436
|
+
) -> None:
|
|
437
|
+
"""Grant shared access to memory."""
|
|
438
|
+
if memory_id not in self.shared_memories:
|
|
439
|
+
self.shared_memories[memory_id] = {
|
|
440
|
+
'owner_id': owner_id,
|
|
441
|
+
'shared_with': [],
|
|
442
|
+
'permissions': {},
|
|
443
|
+
'shared_at': datetime.now().isoformat(),
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
for user_id in shared_with:
|
|
447
|
+
if user_id not in self.shared_memories[memory_id]['shared_with']:
|
|
448
|
+
self.shared_memories[memory_id]['shared_with'].append(user_id)
|
|
449
|
+
self.shared_memories[memory_id]['permissions'][user_id] = ['read']
|
|
450
|
+
|
|
451
|
+
def _update_user_context(self, user_id: str, memory_data: Dict[str, Any]) -> None:
|
|
452
|
+
"""Update user context with new memory."""
|
|
453
|
+
if user_id not in self.user_contexts:
|
|
454
|
+
self.user_contexts[user_id] = self.default_user_context.copy()
|
|
455
|
+
|
|
456
|
+
# Update context
|
|
457
|
+
self.user_contexts[user_id]['last_memory_at'] = datetime.now().isoformat()
|
|
458
|
+
self.user_contexts[user_id]['total_memories'] = self._get_user_memory_count(user_id) + 1
|
|
459
|
+
|
|
460
|
+
# Update session if applicable
|
|
461
|
+
session_id = memory_data.get('context', {}).get('session_id')
|
|
462
|
+
if session_id:
|
|
463
|
+
if user_id not in self.user_sessions:
|
|
464
|
+
self.user_sessions[user_id] = {}
|
|
465
|
+
|
|
466
|
+
if session_id not in self.user_sessions[user_id]:
|
|
467
|
+
self.user_sessions[user_id][session_id] = {
|
|
468
|
+
'started_at': datetime.now().isoformat(),
|
|
469
|
+
'memories': [],
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
self.user_sessions[user_id][session_id]['memories'].append(memory_data['id'])
|
|
473
|
+
self.user_sessions[user_id][session_id]['last_activity'] = datetime.now().isoformat()
|
|
474
|
+
|
|
475
|
+
def _apply_privacy_protection(
|
|
476
|
+
self,
|
|
477
|
+
memory_id: str,
|
|
478
|
+
user_id: str,
|
|
479
|
+
memory_data: Dict[str, Any]
|
|
480
|
+
) -> None:
|
|
481
|
+
"""Apply privacy protection to memory."""
|
|
482
|
+
privacy_level = memory_data.get('privacy_level', PrivacyLevel.STANDARD)
|
|
483
|
+
|
|
484
|
+
# Apply privacy settings based on level
|
|
485
|
+
if privacy_level == PrivacyLevel.CONFIDENTIAL:
|
|
486
|
+
# Maximum privacy - encrypt content
|
|
487
|
+
memory_data['encrypted'] = True
|
|
488
|
+
memory_data['encryption_key'] = f"user_{user_id}_key"
|
|
489
|
+
elif privacy_level == PrivacyLevel.SENSITIVE:
|
|
490
|
+
# Enhanced privacy - anonymize sensitive data
|
|
491
|
+
memory_data['anonymized'] = True
|
|
492
|
+
|
|
493
|
+
def get_memories(
|
|
494
|
+
self,
|
|
495
|
+
agent_id: str,
|
|
496
|
+
query: Optional[str] = None,
|
|
497
|
+
filters: Optional[Dict[str, Any]] = None
|
|
498
|
+
) -> List[Dict[str, Any]]:
|
|
499
|
+
"""
|
|
500
|
+
Retrieve memories for a specific user.
|
|
501
|
+
|
|
502
|
+
Args:
|
|
503
|
+
agent_id: ID of the user
|
|
504
|
+
query: Optional query string for filtering
|
|
505
|
+
filters: Optional additional filters
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
List of memory dictionaries
|
|
509
|
+
"""
|
|
510
|
+
try:
|
|
511
|
+
user_id = self._extract_user_id(agent_id, None, filters)
|
|
512
|
+
accessible_memories = []
|
|
513
|
+
|
|
514
|
+
# Get user's own memories
|
|
515
|
+
if user_id in self.user_memories:
|
|
516
|
+
for memory_type in MemoryType:
|
|
517
|
+
for memory_data in self.user_memories[user_id][memory_type].values():
|
|
518
|
+
accessible_memories.append(memory_data)
|
|
519
|
+
|
|
520
|
+
# Get shared memories
|
|
521
|
+
for memory_id, sharing_data in self.shared_memories.items():
|
|
522
|
+
if user_id in sharing_data['shared_with']:
|
|
523
|
+
# Find the original memory
|
|
524
|
+
memory_data = self._find_memory(memory_id)
|
|
525
|
+
if memory_data:
|
|
526
|
+
accessible_memories.append(memory_data)
|
|
527
|
+
|
|
528
|
+
# Apply query filtering if provided
|
|
529
|
+
if query:
|
|
530
|
+
accessible_memories = [
|
|
531
|
+
memory for memory in accessible_memories
|
|
532
|
+
if query.lower() in memory['content'].lower()
|
|
533
|
+
]
|
|
534
|
+
|
|
535
|
+
# Apply additional filters if provided
|
|
536
|
+
if filters:
|
|
537
|
+
for key, value in filters.items():
|
|
538
|
+
if key != 'user_id': # Skip user_id filter as it's already applied
|
|
539
|
+
accessible_memories = [
|
|
540
|
+
memory for memory in accessible_memories
|
|
541
|
+
if memory.get(key) == value
|
|
542
|
+
]
|
|
543
|
+
|
|
544
|
+
# Update access statistics
|
|
545
|
+
for memory in accessible_memories:
|
|
546
|
+
memory['access_count'] += 1
|
|
547
|
+
memory['last_accessed'] = datetime.now().isoformat()
|
|
548
|
+
|
|
549
|
+
logger.info(f"Retrieved {len(accessible_memories)} memories for user {user_id}")
|
|
550
|
+
return accessible_memories
|
|
551
|
+
|
|
552
|
+
except Exception as e:
|
|
553
|
+
logger.error(f"Failed to get memories for user {agent_id}: {e}")
|
|
554
|
+
raise
|
|
555
|
+
|
|
556
|
+
def update_memory(
|
|
557
|
+
self,
|
|
558
|
+
memory_id: str,
|
|
559
|
+
agent_id: str,
|
|
560
|
+
updates: Dict[str, Any]
|
|
561
|
+
) -> Dict[str, Any]:
|
|
562
|
+
"""
|
|
563
|
+
Update an existing memory.
|
|
564
|
+
|
|
565
|
+
Args:
|
|
566
|
+
memory_id: ID of the memory to update
|
|
567
|
+
agent_id: ID of the user making the update
|
|
568
|
+
updates: Dictionary of updates to apply
|
|
569
|
+
|
|
570
|
+
Returns:
|
|
571
|
+
Dictionary containing the updated memory information
|
|
572
|
+
"""
|
|
573
|
+
try:
|
|
574
|
+
# Find the memory
|
|
575
|
+
memory_data = self._find_memory(memory_id)
|
|
576
|
+
if not memory_data:
|
|
577
|
+
raise ValueError(f"Memory {memory_id} not found")
|
|
578
|
+
|
|
579
|
+
user_id = self._extract_user_id(agent_id, None, None)
|
|
580
|
+
|
|
581
|
+
# Check if user owns the memory or has write permission
|
|
582
|
+
if memory_data['user_id'] != user_id:
|
|
583
|
+
# Check shared access
|
|
584
|
+
if memory_id not in self.shared_memories:
|
|
585
|
+
raise PermissionError(f"User {user_id} does not have permission to update memory {memory_id}")
|
|
586
|
+
|
|
587
|
+
sharing_data = self.shared_memories[memory_id]
|
|
588
|
+
if user_id not in sharing_data['shared_with']:
|
|
589
|
+
raise PermissionError(f"User {user_id} does not have permission to update memory {memory_id}")
|
|
590
|
+
|
|
591
|
+
user_permissions = sharing_data['permissions'].get(user_id, [])
|
|
592
|
+
if 'write' not in user_permissions:
|
|
593
|
+
raise PermissionError(f"User {user_id} does not have write permission for memory {memory_id}")
|
|
594
|
+
|
|
595
|
+
# Apply updates
|
|
596
|
+
for key, value in updates.items():
|
|
597
|
+
if key in memory_data:
|
|
598
|
+
memory_data[key] = value
|
|
599
|
+
|
|
600
|
+
memory_data['updated_at'] = datetime.now().isoformat()
|
|
601
|
+
|
|
602
|
+
# Update intelligent memory manager if content changed
|
|
603
|
+
if 'content' in updates:
|
|
604
|
+
self.intelligent_manager.update_memory(
|
|
605
|
+
memory_id=memory_id,
|
|
606
|
+
content=updates['content'],
|
|
607
|
+
metadata=memory_data.get('metadata', {})
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
logger.info(f"Updated memory {memory_id} by user {user_id}")
|
|
611
|
+
|
|
612
|
+
return {
|
|
613
|
+
'id': memory_id,
|
|
614
|
+
'memory': memory_data['content'],
|
|
615
|
+
'updated_at': memory_data['updated_at'],
|
|
616
|
+
'user_id': user_id,
|
|
617
|
+
'agent_id': agent_id,
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
except Exception as e:
|
|
621
|
+
logger.error(f"Failed to update memory {memory_id}: {e}")
|
|
622
|
+
raise
|
|
623
|
+
|
|
624
|
+
def delete_memory(
|
|
625
|
+
self,
|
|
626
|
+
memory_id: str,
|
|
627
|
+
agent_id: str
|
|
628
|
+
) -> Dict[str, Any]:
|
|
629
|
+
"""
|
|
630
|
+
Delete a memory.
|
|
631
|
+
|
|
632
|
+
Args:
|
|
633
|
+
memory_id: ID of the memory to delete
|
|
634
|
+
agent_id: ID of the user making the deletion
|
|
635
|
+
|
|
636
|
+
Returns:
|
|
637
|
+
Dictionary containing the deletion result
|
|
638
|
+
"""
|
|
639
|
+
try:
|
|
640
|
+
# Find the memory
|
|
641
|
+
memory_data = self._find_memory(memory_id)
|
|
642
|
+
if not memory_data:
|
|
643
|
+
raise ValueError(f"Memory {memory_id} not found")
|
|
644
|
+
|
|
645
|
+
user_id = self._extract_user_id(agent_id, None, None)
|
|
646
|
+
|
|
647
|
+
# Check if user owns the memory
|
|
648
|
+
if memory_data['user_id'] != user_id:
|
|
649
|
+
raise PermissionError(f"User {user_id} does not have permission to delete memory {memory_id}")
|
|
650
|
+
|
|
651
|
+
# Remove from user storage
|
|
652
|
+
user_id = memory_data['user_id']
|
|
653
|
+
memory_type = memory_data['memory_type']
|
|
654
|
+
|
|
655
|
+
if user_id in self.user_memories and memory_type in self.user_memories[user_id]:
|
|
656
|
+
if memory_id in self.user_memories[user_id][memory_type]:
|
|
657
|
+
del self.user_memories[user_id][memory_type][memory_id]
|
|
658
|
+
|
|
659
|
+
# Clean up sharing data
|
|
660
|
+
if memory_id in self.shared_memories:
|
|
661
|
+
del self.shared_memories[memory_id]
|
|
662
|
+
|
|
663
|
+
# Clean up consent records
|
|
664
|
+
for user_consents in self.consent_records.values():
|
|
665
|
+
if memory_id in user_consents:
|
|
666
|
+
del user_consents[memory_id]
|
|
667
|
+
|
|
668
|
+
logger.info(f"Deleted memory {memory_id} by user {user_id}")
|
|
669
|
+
|
|
670
|
+
return {
|
|
671
|
+
'success': True,
|
|
672
|
+
'deleted_id': memory_id,
|
|
673
|
+
'deleted_by': user_id,
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
except Exception as e:
|
|
677
|
+
logger.error(f"Failed to delete memory {memory_id}: {e}")
|
|
678
|
+
raise
|
|
679
|
+
|
|
680
|
+
def share_memory(
|
|
681
|
+
self,
|
|
682
|
+
memory_id: str,
|
|
683
|
+
from_agent: str,
|
|
684
|
+
to_agents: List[str],
|
|
685
|
+
permissions: Optional[List[str]] = None
|
|
686
|
+
) -> Dict[str, Any]:
|
|
687
|
+
"""
|
|
688
|
+
Share a memory with other users.
|
|
689
|
+
|
|
690
|
+
Args:
|
|
691
|
+
memory_id: ID of the memory to share
|
|
692
|
+
from_agent: ID of the user sharing the memory
|
|
693
|
+
to_agents: List of user IDs to share with
|
|
694
|
+
permissions: Optional list of permissions to grant
|
|
695
|
+
|
|
696
|
+
Returns:
|
|
697
|
+
Dictionary containing the sharing result
|
|
698
|
+
"""
|
|
699
|
+
try:
|
|
700
|
+
# Find the memory
|
|
701
|
+
memory_data = self._find_memory(memory_id)
|
|
702
|
+
if not memory_data:
|
|
703
|
+
raise ValueError(f"Memory {memory_id} not found")
|
|
704
|
+
|
|
705
|
+
from_user_id = self._extract_user_id(from_agent, None, None)
|
|
706
|
+
|
|
707
|
+
# Check if user owns the memory
|
|
708
|
+
if memory_data['user_id'] != from_user_id:
|
|
709
|
+
raise PermissionError(f"User {from_user_id} does not own memory {memory_id}")
|
|
710
|
+
|
|
711
|
+
# Grant shared access
|
|
712
|
+
shared_with = []
|
|
713
|
+
default_permissions = permissions or ['read']
|
|
714
|
+
|
|
715
|
+
self._grant_shared_access(memory_id, from_user_id, to_agents)
|
|
716
|
+
|
|
717
|
+
# Update sharing data with permissions
|
|
718
|
+
if memory_id in self.shared_memories:
|
|
719
|
+
for user_id in to_agents:
|
|
720
|
+
self.shared_memories[memory_id]['permissions'][user_id] = default_permissions
|
|
721
|
+
shared_with.append(user_id)
|
|
722
|
+
|
|
723
|
+
logger.info(f"Shared memory {memory_id} from {from_user_id} to {len(shared_with)} users")
|
|
724
|
+
|
|
725
|
+
return {
|
|
726
|
+
'success': True,
|
|
727
|
+
'memory_id': memory_id,
|
|
728
|
+
'shared_from': from_user_id,
|
|
729
|
+
'shared_with': shared_with,
|
|
730
|
+
'permissions': default_permissions,
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
except Exception as e:
|
|
734
|
+
logger.error(f"Failed to share memory {memory_id}: {e}")
|
|
735
|
+
raise
|
|
736
|
+
|
|
737
|
+
def get_context_info(self, agent_id: str) -> Dict[str, Any]:
|
|
738
|
+
"""
|
|
739
|
+
Get context information for a user.
|
|
740
|
+
|
|
741
|
+
Args:
|
|
742
|
+
agent_id: ID of the user
|
|
743
|
+
|
|
744
|
+
Returns:
|
|
745
|
+
Dictionary containing context information
|
|
746
|
+
"""
|
|
747
|
+
try:
|
|
748
|
+
user_id = self._extract_user_id(agent_id, None, None)
|
|
749
|
+
|
|
750
|
+
context_info = {
|
|
751
|
+
'user_id': user_id,
|
|
752
|
+
'agent_id': agent_id,
|
|
753
|
+
'memory_count': self._get_user_memory_count(user_id),
|
|
754
|
+
'shared_memories_count': self._get_shared_memories_count(user_id),
|
|
755
|
+
'active_sessions': len(self.user_sessions.get(user_id, {})),
|
|
756
|
+
'privacy_level': self.user_contexts.get(user_id, {}).get('privacy_level', PrivacyLevel.STANDARD),
|
|
757
|
+
'sharing_enabled': self.multi_user_config.cross_user_sharing,
|
|
758
|
+
'isolation_enabled': self.multi_user_config.user_isolation,
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
# Add session information
|
|
762
|
+
if user_id in self.user_sessions:
|
|
763
|
+
context_info['sessions'] = {}
|
|
764
|
+
for session_id, session_data in self.user_sessions[user_id].items():
|
|
765
|
+
context_info['sessions'][session_id] = {
|
|
766
|
+
'started_at': session_data['started_at'],
|
|
767
|
+
'memory_count': len(session_data['memories']),
|
|
768
|
+
'last_activity': session_data.get('last_activity'),
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
return context_info
|
|
772
|
+
|
|
773
|
+
except Exception as e:
|
|
774
|
+
logger.error(f"Failed to get context info for user {agent_id}: {e}")
|
|
775
|
+
raise
|
|
776
|
+
|
|
777
|
+
def update_memory_decay(self) -> Dict[str, Any]:
|
|
778
|
+
"""
|
|
779
|
+
Update memory decay based on Ebbinghaus forgetting curve.
|
|
780
|
+
|
|
781
|
+
Returns:
|
|
782
|
+
Dictionary containing the decay update results
|
|
783
|
+
"""
|
|
784
|
+
try:
|
|
785
|
+
decay_results = {
|
|
786
|
+
'updated_memories': 0,
|
|
787
|
+
'forgotten_memories': 0,
|
|
788
|
+
'reinforced_memories': 0,
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
# Update decay for all user memories
|
|
792
|
+
for user_id, user_memory_types in self.user_memories.items():
|
|
793
|
+
for memory_type in MemoryType:
|
|
794
|
+
for memory_id, memory_data in user_memory_types[memory_type].items():
|
|
795
|
+
# Update decay using intelligent memory manager
|
|
796
|
+
# Note: IntelligentMemoryManager.update_memory_decay() updates all memories
|
|
797
|
+
# We'll call it once and then process individual results
|
|
798
|
+
if not hasattr(self, '_decay_updated'):
|
|
799
|
+
self.intelligent_manager.update_memory_decay()
|
|
800
|
+
self._decay_updated = True
|
|
801
|
+
|
|
802
|
+
# For individual memory processing, we'll use a simplified approach
|
|
803
|
+
current_score = memory_data.get('retention_score', 1.0)
|
|
804
|
+
access_count = memory_data.get('access_count', 0)
|
|
805
|
+
last_accessed = memory_data.get('last_accessed')
|
|
806
|
+
|
|
807
|
+
# Simple decay calculation (this should be replaced with proper Ebbinghaus algorithm)
|
|
808
|
+
decay_rate = 0.1
|
|
809
|
+
if last_accessed:
|
|
810
|
+
# Parse ISO format string to datetime
|
|
811
|
+
if isinstance(last_accessed, str):
|
|
812
|
+
last_accessed_dt = datetime.fromisoformat(last_accessed.replace('Z', '+00:00'))
|
|
813
|
+
else:
|
|
814
|
+
last_accessed_dt = last_accessed
|
|
815
|
+
time_since_access = (datetime.now() - last_accessed_dt).total_seconds() / 3600
|
|
816
|
+
else:
|
|
817
|
+
time_since_access = 24
|
|
818
|
+
new_score = current_score * (1 - decay_rate * time_since_access / 24)
|
|
819
|
+
new_score = max(0.0, min(1.0, new_score))
|
|
820
|
+
|
|
821
|
+
decay_result = {
|
|
822
|
+
'new_score': new_score,
|
|
823
|
+
'decay_rate': decay_rate,
|
|
824
|
+
'forgotten': new_score < 0.1
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
# Update memory data
|
|
828
|
+
memory_data['retention_score'] = decay_result.get('new_score', memory_data.get('retention_score', 1.0))
|
|
829
|
+
memory_data['decay_rate'] = decay_result.get('decay_rate', 0.1)
|
|
830
|
+
|
|
831
|
+
decay_results['updated_memories'] += 1
|
|
832
|
+
|
|
833
|
+
# Check if memory should be forgotten
|
|
834
|
+
if decay_result.get('forgotten', False):
|
|
835
|
+
decay_results['forgotten_memories'] += 1
|
|
836
|
+
|
|
837
|
+
# Check if memory should be reinforced
|
|
838
|
+
if decay_result.get('reinforced', False):
|
|
839
|
+
decay_results['reinforced_memories'] += 1
|
|
840
|
+
|
|
841
|
+
logger.info(f"Updated memory decay: {decay_results}")
|
|
842
|
+
return decay_results
|
|
843
|
+
|
|
844
|
+
except Exception as e:
|
|
845
|
+
logger.error(f"Failed to update memory decay: {e}")
|
|
846
|
+
raise
|
|
847
|
+
|
|
848
|
+
def cleanup_forgotten_memories(self) -> Dict[str, Any]:
|
|
849
|
+
"""
|
|
850
|
+
Clean up memories that have been forgotten.
|
|
851
|
+
|
|
852
|
+
Returns:
|
|
853
|
+
Dictionary containing the cleanup results
|
|
854
|
+
"""
|
|
855
|
+
try:
|
|
856
|
+
cleanup_results = {
|
|
857
|
+
'cleaned_memories': 0,
|
|
858
|
+
'archived_memories': 0,
|
|
859
|
+
'deleted_memories': 0,
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
# Clean up forgotten memories for all users
|
|
863
|
+
for user_id, user_memory_types in self.user_memories.items():
|
|
864
|
+
for memory_type in MemoryType:
|
|
865
|
+
memories_to_remove = []
|
|
866
|
+
|
|
867
|
+
for memory_id, memory_data in user_memory_types[memory_type].items():
|
|
868
|
+
retention_score = memory_data.get('retention_score', 1.0)
|
|
869
|
+
|
|
870
|
+
# Check if memory should be cleaned up
|
|
871
|
+
if retention_score < 0.1: # Forgotten threshold
|
|
872
|
+
memories_to_remove.append(memory_id)
|
|
873
|
+
cleanup_results['deleted_memories'] += 1
|
|
874
|
+
elif retention_score < 0.3: # Archive threshold
|
|
875
|
+
# Archive memory instead of deleting
|
|
876
|
+
memory_data['archived'] = True
|
|
877
|
+
cleanup_results['archived_memories'] += 1
|
|
878
|
+
|
|
879
|
+
# Remove forgotten memories
|
|
880
|
+
for memory_id in memories_to_remove:
|
|
881
|
+
del user_memory_types[memory_type][memory_id]
|
|
882
|
+
cleanup_results['cleaned_memories'] += 1
|
|
883
|
+
|
|
884
|
+
logger.info(f"Cleaned up forgotten memories: {cleanup_results}")
|
|
885
|
+
return cleanup_results
|
|
886
|
+
|
|
887
|
+
except Exception as e:
|
|
888
|
+
logger.error(f"Failed to cleanup forgotten memories: {e}")
|
|
889
|
+
raise
|
|
890
|
+
|
|
891
|
+
def get_memory_statistics(self) -> Dict[str, Any]:
|
|
892
|
+
"""
|
|
893
|
+
Get statistics about the memory system.
|
|
894
|
+
|
|
895
|
+
Returns:
|
|
896
|
+
Dictionary containing memory statistics
|
|
897
|
+
"""
|
|
898
|
+
try:
|
|
899
|
+
stats = {
|
|
900
|
+
'total_memories': 0,
|
|
901
|
+
'total_users': len(self.user_memories),
|
|
902
|
+
'user_breakdown': {},
|
|
903
|
+
'type_breakdown': {},
|
|
904
|
+
'sharing_stats': {
|
|
905
|
+
'shared_memories': len(self.shared_memories),
|
|
906
|
+
'sharing_relationships': len(self.sharing_relationships),
|
|
907
|
+
'consent_requests': sum(len(consents) for consents in self.consent_records.values()),
|
|
908
|
+
},
|
|
909
|
+
'session_stats': {
|
|
910
|
+
'total_sessions': sum(len(sessions) for sessions in self.user_sessions.values()),
|
|
911
|
+
'active_users': len(self.user_sessions),
|
|
912
|
+
},
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
# Count memories by user and type
|
|
916
|
+
for user_id, user_memory_types in self.user_memories.items():
|
|
917
|
+
user_count = 0
|
|
918
|
+
for memory_type in MemoryType:
|
|
919
|
+
type_count = len(user_memory_types[memory_type])
|
|
920
|
+
user_count += type_count
|
|
921
|
+
stats['total_memories'] += type_count
|
|
922
|
+
|
|
923
|
+
# Type breakdown
|
|
924
|
+
type_key = memory_type.value
|
|
925
|
+
if type_key not in stats['type_breakdown']:
|
|
926
|
+
stats['type_breakdown'][type_key] = 0
|
|
927
|
+
stats['type_breakdown'][type_key] += type_count
|
|
928
|
+
|
|
929
|
+
stats['user_breakdown'][user_id] = user_count
|
|
930
|
+
|
|
931
|
+
return stats
|
|
932
|
+
|
|
933
|
+
except Exception as e:
|
|
934
|
+
logger.error(f"Failed to get memory statistics: {e}")
|
|
935
|
+
raise
|
|
936
|
+
|
|
937
|
+
def check_permission(
|
|
938
|
+
self,
|
|
939
|
+
agent_id: str,
|
|
940
|
+
memory_id: str,
|
|
941
|
+
permission: str
|
|
942
|
+
) -> bool:
|
|
943
|
+
"""
|
|
944
|
+
Check if a user has a specific permission for a memory.
|
|
945
|
+
|
|
946
|
+
Args:
|
|
947
|
+
agent_id: ID of the user
|
|
948
|
+
memory_id: ID of the memory
|
|
949
|
+
permission: Permission to check
|
|
950
|
+
|
|
951
|
+
Returns:
|
|
952
|
+
True if the user has the permission, False otherwise
|
|
953
|
+
"""
|
|
954
|
+
try:
|
|
955
|
+
user_id = self._extract_user_id(agent_id, None, None)
|
|
956
|
+
|
|
957
|
+
# Find the memory
|
|
958
|
+
memory_data = self._find_memory(memory_id)
|
|
959
|
+
if not memory_data:
|
|
960
|
+
return False
|
|
961
|
+
|
|
962
|
+
# Check if user owns the memory
|
|
963
|
+
if memory_data['user_id'] == user_id:
|
|
964
|
+
return True
|
|
965
|
+
|
|
966
|
+
# Check shared access
|
|
967
|
+
if memory_id in self.shared_memories:
|
|
968
|
+
sharing_data = self.shared_memories[memory_id]
|
|
969
|
+
if user_id in sharing_data['shared_with']:
|
|
970
|
+
user_permissions = sharing_data['permissions'].get(user_id, [])
|
|
971
|
+
return permission.lower() in user_permissions
|
|
972
|
+
|
|
973
|
+
return False
|
|
974
|
+
|
|
975
|
+
except Exception:
|
|
976
|
+
return False
|
|
977
|
+
|
|
978
|
+
def _get_user_memory_count(self, user_id: str) -> int:
|
|
979
|
+
"""Get the total number of memories for a user."""
|
|
980
|
+
if user_id not in self.user_memories:
|
|
981
|
+
return 0
|
|
982
|
+
|
|
983
|
+
count = 0
|
|
984
|
+
for memory_type in MemoryType:
|
|
985
|
+
count += len(self.user_memories[user_id][memory_type])
|
|
986
|
+
return count
|
|
987
|
+
|
|
988
|
+
def _get_shared_memories_count(self, user_id: str) -> int:
|
|
989
|
+
"""Get the number of memories shared with a user."""
|
|
990
|
+
count = 0
|
|
991
|
+
for sharing_data in self.shared_memories.values():
|
|
992
|
+
if user_id in sharing_data['shared_with']:
|
|
993
|
+
count += 1
|
|
994
|
+
return count
|
|
995
|
+
|
|
996
|
+
def _cleanup_old_user_memories(self, user_id: str, count_to_remove: int) -> None:
|
|
997
|
+
"""Clean up old memories for a user."""
|
|
998
|
+
# Simple implementation: remove oldest memories
|
|
999
|
+
all_memories = []
|
|
1000
|
+
|
|
1001
|
+
if user_id in self.user_memories:
|
|
1002
|
+
for memory_type in MemoryType:
|
|
1003
|
+
for memory_id, memory_data in self.user_memories[user_id][memory_type].items():
|
|
1004
|
+
all_memories.append((memory_id, memory_type, memory_data))
|
|
1005
|
+
|
|
1006
|
+
# Sort by creation date
|
|
1007
|
+
all_memories.sort(key=lambda x: x[2].get('created_at', ''))
|
|
1008
|
+
|
|
1009
|
+
# Remove oldest memories
|
|
1010
|
+
for i in range(min(count_to_remove, len(all_memories))):
|
|
1011
|
+
memory_id, memory_type, _ = all_memories[i]
|
|
1012
|
+
del self.user_memories[user_id][memory_type][memory_id]
|
|
1013
|
+
|
|
1014
|
+
def _find_memory(self, memory_id: str) -> Optional[Dict[str, Any]]:
|
|
1015
|
+
"""Find a memory by ID across all users and types."""
|
|
1016
|
+
for user_id, user_memory_types in self.user_memories.items():
|
|
1017
|
+
for memory_type in MemoryType:
|
|
1018
|
+
if memory_id in user_memory_types[memory_type]:
|
|
1019
|
+
return user_memory_types[memory_type][memory_id]
|
|
1020
|
+
return None
|