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.
Files changed (123) hide show
  1. powermem/__init__.py +103 -0
  2. powermem/agent/__init__.py +35 -0
  3. powermem/agent/abstract/__init__.py +22 -0
  4. powermem/agent/abstract/collaboration.py +259 -0
  5. powermem/agent/abstract/context.py +187 -0
  6. powermem/agent/abstract/manager.py +232 -0
  7. powermem/agent/abstract/permission.py +217 -0
  8. powermem/agent/abstract/privacy.py +267 -0
  9. powermem/agent/abstract/scope.py +199 -0
  10. powermem/agent/agent.py +791 -0
  11. powermem/agent/components/__init__.py +18 -0
  12. powermem/agent/components/collaboration_coordinator.py +645 -0
  13. powermem/agent/components/permission_controller.py +586 -0
  14. powermem/agent/components/privacy_protector.py +767 -0
  15. powermem/agent/components/scope_controller.py +685 -0
  16. powermem/agent/factories/__init__.py +16 -0
  17. powermem/agent/factories/agent_factory.py +266 -0
  18. powermem/agent/factories/config_factory.py +308 -0
  19. powermem/agent/factories/memory_factory.py +229 -0
  20. powermem/agent/implementations/__init__.py +16 -0
  21. powermem/agent/implementations/hybrid.py +728 -0
  22. powermem/agent/implementations/multi_agent.py +1040 -0
  23. powermem/agent/implementations/multi_user.py +1020 -0
  24. powermem/agent/types.py +53 -0
  25. powermem/agent/wrappers/__init__.py +14 -0
  26. powermem/agent/wrappers/agent_memory_wrapper.py +427 -0
  27. powermem/agent/wrappers/compatibility_wrapper.py +520 -0
  28. powermem/config_loader.py +318 -0
  29. powermem/configs.py +249 -0
  30. powermem/core/__init__.py +19 -0
  31. powermem/core/async_memory.py +1493 -0
  32. powermem/core/audit.py +258 -0
  33. powermem/core/base.py +165 -0
  34. powermem/core/memory.py +1567 -0
  35. powermem/core/setup.py +162 -0
  36. powermem/core/telemetry.py +215 -0
  37. powermem/integrations/__init__.py +17 -0
  38. powermem/integrations/embeddings/__init__.py +13 -0
  39. powermem/integrations/embeddings/aws_bedrock.py +100 -0
  40. powermem/integrations/embeddings/azure_openai.py +55 -0
  41. powermem/integrations/embeddings/base.py +31 -0
  42. powermem/integrations/embeddings/config/base.py +132 -0
  43. powermem/integrations/embeddings/configs.py +31 -0
  44. powermem/integrations/embeddings/factory.py +48 -0
  45. powermem/integrations/embeddings/gemini.py +39 -0
  46. powermem/integrations/embeddings/huggingface.py +41 -0
  47. powermem/integrations/embeddings/langchain.py +35 -0
  48. powermem/integrations/embeddings/lmstudio.py +29 -0
  49. powermem/integrations/embeddings/mock.py +11 -0
  50. powermem/integrations/embeddings/ollama.py +53 -0
  51. powermem/integrations/embeddings/openai.py +49 -0
  52. powermem/integrations/embeddings/qwen.py +102 -0
  53. powermem/integrations/embeddings/together.py +31 -0
  54. powermem/integrations/embeddings/vertexai.py +54 -0
  55. powermem/integrations/llm/__init__.py +18 -0
  56. powermem/integrations/llm/anthropic.py +87 -0
  57. powermem/integrations/llm/base.py +132 -0
  58. powermem/integrations/llm/config/anthropic.py +56 -0
  59. powermem/integrations/llm/config/azure.py +56 -0
  60. powermem/integrations/llm/config/base.py +62 -0
  61. powermem/integrations/llm/config/deepseek.py +56 -0
  62. powermem/integrations/llm/config/ollama.py +56 -0
  63. powermem/integrations/llm/config/openai.py +79 -0
  64. powermem/integrations/llm/config/qwen.py +68 -0
  65. powermem/integrations/llm/config/qwen_asr.py +46 -0
  66. powermem/integrations/llm/config/vllm.py +56 -0
  67. powermem/integrations/llm/configs.py +26 -0
  68. powermem/integrations/llm/deepseek.py +106 -0
  69. powermem/integrations/llm/factory.py +118 -0
  70. powermem/integrations/llm/gemini.py +201 -0
  71. powermem/integrations/llm/langchain.py +65 -0
  72. powermem/integrations/llm/ollama.py +106 -0
  73. powermem/integrations/llm/openai.py +166 -0
  74. powermem/integrations/llm/openai_structured.py +80 -0
  75. powermem/integrations/llm/qwen.py +207 -0
  76. powermem/integrations/llm/qwen_asr.py +171 -0
  77. powermem/integrations/llm/vllm.py +106 -0
  78. powermem/integrations/rerank/__init__.py +20 -0
  79. powermem/integrations/rerank/base.py +43 -0
  80. powermem/integrations/rerank/config/__init__.py +7 -0
  81. powermem/integrations/rerank/config/base.py +27 -0
  82. powermem/integrations/rerank/configs.py +23 -0
  83. powermem/integrations/rerank/factory.py +68 -0
  84. powermem/integrations/rerank/qwen.py +159 -0
  85. powermem/intelligence/__init__.py +17 -0
  86. powermem/intelligence/ebbinghaus_algorithm.py +354 -0
  87. powermem/intelligence/importance_evaluator.py +361 -0
  88. powermem/intelligence/intelligent_memory_manager.py +284 -0
  89. powermem/intelligence/manager.py +148 -0
  90. powermem/intelligence/plugin.py +229 -0
  91. powermem/prompts/__init__.py +29 -0
  92. powermem/prompts/graph/graph_prompts.py +217 -0
  93. powermem/prompts/graph/graph_tools_prompts.py +469 -0
  94. powermem/prompts/importance_evaluation.py +246 -0
  95. powermem/prompts/intelligent_memory_prompts.py +163 -0
  96. powermem/prompts/templates.py +193 -0
  97. powermem/storage/__init__.py +14 -0
  98. powermem/storage/adapter.py +896 -0
  99. powermem/storage/base.py +109 -0
  100. powermem/storage/config/base.py +13 -0
  101. powermem/storage/config/oceanbase.py +58 -0
  102. powermem/storage/config/pgvector.py +52 -0
  103. powermem/storage/config/sqlite.py +27 -0
  104. powermem/storage/configs.py +159 -0
  105. powermem/storage/factory.py +59 -0
  106. powermem/storage/migration_manager.py +438 -0
  107. powermem/storage/oceanbase/__init__.py +8 -0
  108. powermem/storage/oceanbase/constants.py +162 -0
  109. powermem/storage/oceanbase/oceanbase.py +1384 -0
  110. powermem/storage/oceanbase/oceanbase_graph.py +1441 -0
  111. powermem/storage/pgvector/__init__.py +7 -0
  112. powermem/storage/pgvector/pgvector.py +420 -0
  113. powermem/storage/sqlite/__init__.py +0 -0
  114. powermem/storage/sqlite/sqlite.py +218 -0
  115. powermem/storage/sqlite/sqlite_vector_store.py +311 -0
  116. powermem/utils/__init__.py +35 -0
  117. powermem/utils/utils.py +605 -0
  118. powermem/version.py +23 -0
  119. powermem-0.1.0.dist-info/METADATA +187 -0
  120. powermem-0.1.0.dist-info/RECORD +123 -0
  121. powermem-0.1.0.dist-info/WHEEL +5 -0
  122. powermem-0.1.0.dist-info/licenses/LICENSE +206 -0
  123. 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