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,767 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Multi-Agent Privacy Protector
|
|
3
|
+
|
|
4
|
+
Manages privacy and data protection in the agent memory system.
|
|
5
|
+
Handles encryption, anonymization, access logging, and GDPR compliance.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
|
|
14
|
+
from typing import Any, Dict
|
|
15
|
+
from powermem.agent.types import PrivacyLevel
|
|
16
|
+
from powermem.agent.abstract.privacy import AgentPrivacyManagerBase
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PrivacyProtector(AgentPrivacyManagerBase):
|
|
22
|
+
"""
|
|
23
|
+
Multi-agent privacy protector implementation.
|
|
24
|
+
|
|
25
|
+
Manages privacy and data protection, handles encryption, anonymization,
|
|
26
|
+
access logging, and GDPR compliance.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, config: Dict[str, Any]):
|
|
30
|
+
"""
|
|
31
|
+
Initialize the privacy protector.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
config: Memory configuration object
|
|
35
|
+
"""
|
|
36
|
+
super().__init__(config.agent_memory.multi_agent_config.privacy_config)
|
|
37
|
+
self.config = config
|
|
38
|
+
self.multi_agent_config = config.agent_memory.multi_agent_config
|
|
39
|
+
|
|
40
|
+
# Privacy settings
|
|
41
|
+
self.privacy_config = self.multi_agent_config.privacy_config
|
|
42
|
+
|
|
43
|
+
# Privacy storage
|
|
44
|
+
self.memory_privacy_levels: Dict[str, PrivacyLevel] = {}
|
|
45
|
+
self.encrypted_memories: Dict[str, Dict[str, Any]] = {}
|
|
46
|
+
self.anonymized_memories: Dict[str, Dict[str, Any]] = {}
|
|
47
|
+
self.access_logs: Dict[str, List[Dict[str, Any]]] = {}
|
|
48
|
+
self.retention_policies: Dict[str, Dict[str, Any]] = {}
|
|
49
|
+
|
|
50
|
+
# GDPR compliance
|
|
51
|
+
self.consent_records: Dict[str, Dict[str, Any]] = {}
|
|
52
|
+
self.data_processing_records: Dict[str, List[Dict[str, Any]]] = {}
|
|
53
|
+
self.deletion_requests: Dict[str, Dict[str, Any]] = {}
|
|
54
|
+
|
|
55
|
+
def initialize(self) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Initialize the privacy protector.
|
|
58
|
+
"""
|
|
59
|
+
try:
|
|
60
|
+
# Initialize privacy settings
|
|
61
|
+
self._initialize_privacy_settings()
|
|
62
|
+
self.initialized = True
|
|
63
|
+
logger.info("Privacy protector initialized successfully")
|
|
64
|
+
|
|
65
|
+
except Exception as e:
|
|
66
|
+
logger.error(f"Failed to initialize privacy protector: {e}")
|
|
67
|
+
raise
|
|
68
|
+
|
|
69
|
+
def _initialize_privacy_settings(self) -> None:
|
|
70
|
+
"""Initialize privacy settings from configuration."""
|
|
71
|
+
self.enable_encryption = self.privacy_config.get('enable_encryption', False)
|
|
72
|
+
self.data_anonymization = self.privacy_config.get('data_anonymization', True)
|
|
73
|
+
self.access_logging = self.privacy_config.get('access_logging', True)
|
|
74
|
+
self.retention_policy = self.privacy_config.get('retention_policy', '30_days')
|
|
75
|
+
self.gdpr_compliance = self.privacy_config.get('gdpr_compliance', True)
|
|
76
|
+
self.default_privacy_level = self.privacy_config.get('default_privacy_level', PrivacyLevel.STANDARD)
|
|
77
|
+
|
|
78
|
+
def set_privacy_level(
|
|
79
|
+
self,
|
|
80
|
+
memory_id: str,
|
|
81
|
+
privacy_level: PrivacyLevel,
|
|
82
|
+
set_by: str
|
|
83
|
+
) -> Dict[str, Any]:
|
|
84
|
+
"""
|
|
85
|
+
Set the privacy level for a memory.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
memory_id: ID of the memory
|
|
89
|
+
privacy_level: Privacy level to set
|
|
90
|
+
set_by: ID of the agent setting the privacy level
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Dictionary containing the set result
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
# Set privacy level
|
|
97
|
+
self.memory_privacy_levels[memory_id] = privacy_level
|
|
98
|
+
|
|
99
|
+
# Apply privacy measures based on level
|
|
100
|
+
if privacy_level == PrivacyLevel.CONFIDENTIAL:
|
|
101
|
+
# Apply maximum privacy measures
|
|
102
|
+
self._apply_maximum_privacy(memory_id)
|
|
103
|
+
elif privacy_level == PrivacyLevel.SENSITIVE:
|
|
104
|
+
# Apply enhanced privacy measures
|
|
105
|
+
self._apply_enhanced_privacy(memory_id)
|
|
106
|
+
|
|
107
|
+
# Log the privacy level change
|
|
108
|
+
self._log_privacy_change(memory_id, privacy_level, set_by)
|
|
109
|
+
|
|
110
|
+
logger.info(f"Set privacy level {privacy_level.value} for memory {memory_id}")
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
'success': True,
|
|
114
|
+
'memory_id': memory_id,
|
|
115
|
+
'privacy_level': privacy_level.value,
|
|
116
|
+
'set_by': set_by,
|
|
117
|
+
'set_at': datetime.now().isoformat(),
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
except Exception as e:
|
|
121
|
+
logger.error(f"Failed to set privacy level: {e}")
|
|
122
|
+
return {
|
|
123
|
+
'success': False,
|
|
124
|
+
'error': str(e),
|
|
125
|
+
'memory_id': memory_id,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
def get_privacy_level(
|
|
129
|
+
self,
|
|
130
|
+
memory_id: str
|
|
131
|
+
) -> PrivacyLevel:
|
|
132
|
+
"""
|
|
133
|
+
Get the privacy level for a memory.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
memory_id: ID of the memory
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
PrivacyLevel enum value
|
|
140
|
+
"""
|
|
141
|
+
try:
|
|
142
|
+
return self.memory_privacy_levels.get(memory_id, self.default_privacy_level)
|
|
143
|
+
|
|
144
|
+
except Exception as e:
|
|
145
|
+
logger.error(f"Failed to get privacy level: {e}")
|
|
146
|
+
return self.default_privacy_level
|
|
147
|
+
|
|
148
|
+
def encrypt_memory(
|
|
149
|
+
self,
|
|
150
|
+
memory_id: str,
|
|
151
|
+
encryption_key: Optional[str] = None
|
|
152
|
+
) -> Dict[str, Any]:
|
|
153
|
+
"""
|
|
154
|
+
Encrypt a memory.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
memory_id: ID of the memory
|
|
158
|
+
encryption_key: Optional encryption key
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Dictionary containing the encryption result
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
if not self.enable_encryption:
|
|
165
|
+
return {
|
|
166
|
+
'success': False,
|
|
167
|
+
'error': 'Encryption is not enabled',
|
|
168
|
+
'memory_id': memory_id,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# Generate encryption key if not provided
|
|
172
|
+
if not encryption_key:
|
|
173
|
+
encryption_key = self._generate_encryption_key(memory_id)
|
|
174
|
+
|
|
175
|
+
# Store encryption information
|
|
176
|
+
self.encrypted_memories[memory_id] = {
|
|
177
|
+
'encryption_key_hash': hashlib.sha256(encryption_key.encode()).hexdigest(),
|
|
178
|
+
'encrypted_at': datetime.now().isoformat(),
|
|
179
|
+
'encryption_method': 'AES-256',
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
logger.info(f"Encrypted memory {memory_id}")
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
'success': True,
|
|
186
|
+
'memory_id': memory_id,
|
|
187
|
+
'encrypted_at': datetime.now().isoformat(),
|
|
188
|
+
'encryption_method': 'AES-256',
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
except Exception as e:
|
|
192
|
+
logger.error(f"Failed to encrypt memory: {e}")
|
|
193
|
+
return {
|
|
194
|
+
'success': False,
|
|
195
|
+
'error': str(e),
|
|
196
|
+
'memory_id': memory_id,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
def decrypt_memory(
|
|
200
|
+
self,
|
|
201
|
+
memory_id: str,
|
|
202
|
+
decryption_key: Optional[str] = None
|
|
203
|
+
) -> Dict[str, Any]:
|
|
204
|
+
"""
|
|
205
|
+
Decrypt a memory.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
memory_id: ID of the memory
|
|
209
|
+
decryption_key: Optional decryption key
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Dictionary containing the decryption result
|
|
213
|
+
"""
|
|
214
|
+
try:
|
|
215
|
+
if memory_id not in self.encrypted_memories:
|
|
216
|
+
return {
|
|
217
|
+
'success': False,
|
|
218
|
+
'error': 'Memory is not encrypted',
|
|
219
|
+
'memory_id': memory_id,
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
# Verify decryption key if provided
|
|
223
|
+
if decryption_key:
|
|
224
|
+
key_hash = hashlib.sha256(decryption_key.encode()).hexdigest()
|
|
225
|
+
stored_hash = self.encrypted_memories[memory_id]['encryption_key_hash']
|
|
226
|
+
|
|
227
|
+
if key_hash != stored_hash:
|
|
228
|
+
return {
|
|
229
|
+
'success': False,
|
|
230
|
+
'error': 'Invalid decryption key',
|
|
231
|
+
'memory_id': memory_id,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
logger.info(f"Decrypted memory {memory_id}")
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
'success': True,
|
|
238
|
+
'memory_id': memory_id,
|
|
239
|
+
'decrypted_at': datetime.now().isoformat(),
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
except Exception as e:
|
|
243
|
+
logger.error(f"Failed to decrypt memory: {e}")
|
|
244
|
+
return {
|
|
245
|
+
'success': False,
|
|
246
|
+
'error': str(e),
|
|
247
|
+
'memory_id': memory_id,
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
def anonymize_memory(
|
|
251
|
+
self,
|
|
252
|
+
memory_id: str,
|
|
253
|
+
anonymization_rules: Optional[Dict[str, Any]] = None
|
|
254
|
+
) -> Dict[str, Any]:
|
|
255
|
+
"""
|
|
256
|
+
Anonymize a memory.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
memory_id: ID of the memory
|
|
260
|
+
anonymization_rules: Optional anonymization rules
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
Dictionary containing the anonymization result
|
|
264
|
+
"""
|
|
265
|
+
try:
|
|
266
|
+
if not self.data_anonymization:
|
|
267
|
+
return {
|
|
268
|
+
'success': False,
|
|
269
|
+
'error': 'Data anonymization is not enabled',
|
|
270
|
+
'memory_id': memory_id,
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
# Apply anonymization rules
|
|
274
|
+
rules = anonymization_rules or self._get_default_anonymization_rules()
|
|
275
|
+
|
|
276
|
+
# Store anonymization information
|
|
277
|
+
self.anonymized_memories[memory_id] = {
|
|
278
|
+
'anonymization_rules': rules,
|
|
279
|
+
'anonymized_at': datetime.now().isoformat(),
|
|
280
|
+
'anonymization_method': 'pattern_based',
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
logger.info(f"Anonymized memory {memory_id}")
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
'success': True,
|
|
287
|
+
'memory_id': memory_id,
|
|
288
|
+
'anonymized_at': datetime.now().isoformat(),
|
|
289
|
+
'anonymization_rules': rules,
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
except Exception as e:
|
|
293
|
+
logger.error(f"Failed to anonymize memory: {e}")
|
|
294
|
+
return {
|
|
295
|
+
'success': False,
|
|
296
|
+
'error': str(e),
|
|
297
|
+
'memory_id': memory_id,
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
def log_access(
|
|
301
|
+
self,
|
|
302
|
+
memory_id: str,
|
|
303
|
+
agent_id: str,
|
|
304
|
+
access_type: str,
|
|
305
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
306
|
+
) -> Dict[str, Any]:
|
|
307
|
+
"""
|
|
308
|
+
Log access to a memory.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
memory_id: ID of the memory
|
|
312
|
+
agent_id: ID of the agent accessing
|
|
313
|
+
access_type: Type of access
|
|
314
|
+
metadata: Optional metadata about the access
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
Dictionary containing the log result
|
|
318
|
+
"""
|
|
319
|
+
try:
|
|
320
|
+
if not self.access_logging:
|
|
321
|
+
return {
|
|
322
|
+
'success': True,
|
|
323
|
+
'message': 'Access logging is not enabled',
|
|
324
|
+
'memory_id': memory_id,
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
# Create access log entry
|
|
328
|
+
log_entry = {
|
|
329
|
+
'timestamp': datetime.now().isoformat(),
|
|
330
|
+
'agent_id': agent_id,
|
|
331
|
+
'access_type': access_type,
|
|
332
|
+
'metadata': metadata or {},
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
# Store access log
|
|
336
|
+
if memory_id not in self.access_logs:
|
|
337
|
+
self.access_logs[memory_id] = []
|
|
338
|
+
|
|
339
|
+
self.access_logs[memory_id].append(log_entry)
|
|
340
|
+
|
|
341
|
+
# Keep only recent logs (last 1000 entries)
|
|
342
|
+
if len(self.access_logs[memory_id]) > 1000:
|
|
343
|
+
self.access_logs[memory_id] = self.access_logs[memory_id][-1000:]
|
|
344
|
+
|
|
345
|
+
logger.debug(f"Logged access to memory {memory_id} by agent {agent_id}")
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
'success': True,
|
|
349
|
+
'memory_id': memory_id,
|
|
350
|
+
'agent_id': agent_id,
|
|
351
|
+
'access_type': access_type,
|
|
352
|
+
'logged_at': log_entry['timestamp'],
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
except Exception as e:
|
|
356
|
+
logger.error(f"Failed to log access: {e}")
|
|
357
|
+
return {
|
|
358
|
+
'success': False,
|
|
359
|
+
'error': str(e),
|
|
360
|
+
'memory_id': memory_id,
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
def get_access_logs(
|
|
364
|
+
self,
|
|
365
|
+
memory_id: str,
|
|
366
|
+
agent_id: Optional[str] = None,
|
|
367
|
+
limit: Optional[int] = None
|
|
368
|
+
) -> List[Dict[str, Any]]:
|
|
369
|
+
"""
|
|
370
|
+
Get access logs for a memory.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
memory_id: ID of the memory
|
|
374
|
+
agent_id: Optional ID of the agent
|
|
375
|
+
limit: Optional limit on number of entries
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
List of access log entries
|
|
379
|
+
"""
|
|
380
|
+
try:
|
|
381
|
+
if memory_id not in self.access_logs:
|
|
382
|
+
return []
|
|
383
|
+
|
|
384
|
+
logs = self.access_logs[memory_id]
|
|
385
|
+
|
|
386
|
+
# Filter by agent if specified
|
|
387
|
+
if agent_id:
|
|
388
|
+
logs = [log for log in logs if log['agent_id'] == agent_id]
|
|
389
|
+
|
|
390
|
+
# Sort by timestamp (newest first)
|
|
391
|
+
logs.sort(key=lambda x: x['timestamp'], reverse=True)
|
|
392
|
+
|
|
393
|
+
# Apply limit if specified
|
|
394
|
+
if limit:
|
|
395
|
+
logs = logs[:limit]
|
|
396
|
+
|
|
397
|
+
return logs
|
|
398
|
+
|
|
399
|
+
except Exception as e:
|
|
400
|
+
logger.error(f"Failed to get access logs: {e}")
|
|
401
|
+
return []
|
|
402
|
+
|
|
403
|
+
def apply_retention_policy(
|
|
404
|
+
self,
|
|
405
|
+
memory_id: str,
|
|
406
|
+
retention_policy: str
|
|
407
|
+
) -> Dict[str, Any]:
|
|
408
|
+
"""
|
|
409
|
+
Apply a retention policy to a memory.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
memory_id: ID of the memory
|
|
413
|
+
retention_policy: Retention policy to apply
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
Dictionary containing the policy application result
|
|
417
|
+
"""
|
|
418
|
+
try:
|
|
419
|
+
# Parse retention policy
|
|
420
|
+
policy_days = self._parse_retention_policy(retention_policy)
|
|
421
|
+
|
|
422
|
+
# Calculate expiration date
|
|
423
|
+
expiration_date = datetime.now().timestamp() + (policy_days * 24 * 60 * 60)
|
|
424
|
+
|
|
425
|
+
# Store retention policy
|
|
426
|
+
self.retention_policies[memory_id] = {
|
|
427
|
+
'retention_policy': retention_policy,
|
|
428
|
+
'policy_days': policy_days,
|
|
429
|
+
'expiration_date': expiration_date,
|
|
430
|
+
'applied_at': datetime.now().isoformat(),
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
logger.info(f"Applied retention policy {retention_policy} to memory {memory_id}")
|
|
434
|
+
|
|
435
|
+
return {
|
|
436
|
+
'success': True,
|
|
437
|
+
'memory_id': memory_id,
|
|
438
|
+
'retention_policy': retention_policy,
|
|
439
|
+
'expiration_date': datetime.fromtimestamp(expiration_date).isoformat(),
|
|
440
|
+
'applied_at': datetime.now().isoformat(),
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
except Exception as e:
|
|
444
|
+
logger.error(f"Failed to apply retention policy: {e}")
|
|
445
|
+
return {
|
|
446
|
+
'success': False,
|
|
447
|
+
'error': str(e),
|
|
448
|
+
'memory_id': memory_id,
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
def check_gdpr_compliance(
|
|
452
|
+
self,
|
|
453
|
+
memory_id: str
|
|
454
|
+
) -> Dict[str, Any]:
|
|
455
|
+
"""
|
|
456
|
+
Check GDPR compliance for a memory.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
memory_id: ID of the memory
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
Dictionary containing compliance check results
|
|
463
|
+
"""
|
|
464
|
+
try:
|
|
465
|
+
if not self.gdpr_compliance:
|
|
466
|
+
return {
|
|
467
|
+
'compliant': True,
|
|
468
|
+
'message': 'GDPR compliance is not enabled',
|
|
469
|
+
'memory_id': memory_id,
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
compliance_checks = {
|
|
473
|
+
'data_minimization': True,
|
|
474
|
+
'purpose_limitation': True,
|
|
475
|
+
'storage_limitation': True,
|
|
476
|
+
'accuracy': True,
|
|
477
|
+
'integrity_confidentiality': True,
|
|
478
|
+
'accountability': True,
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
# Check retention policy
|
|
482
|
+
if memory_id in self.retention_policies:
|
|
483
|
+
policy = self.retention_policies[memory_id]
|
|
484
|
+
if datetime.now().timestamp() > policy['expiration_date']:
|
|
485
|
+
compliance_checks['storage_limitation'] = False
|
|
486
|
+
|
|
487
|
+
# Check access logs
|
|
488
|
+
if memory_id in self.access_logs:
|
|
489
|
+
compliance_checks['accountability'] = True
|
|
490
|
+
|
|
491
|
+
# Check privacy level
|
|
492
|
+
privacy_level = self.get_privacy_level(memory_id)
|
|
493
|
+
if privacy_level in [PrivacyLevel.SENSITIVE, PrivacyLevel.CONFIDENTIAL]:
|
|
494
|
+
compliance_checks['integrity_confidentiality'] = True
|
|
495
|
+
|
|
496
|
+
# Overall compliance
|
|
497
|
+
compliant = all(compliance_checks.values())
|
|
498
|
+
|
|
499
|
+
return {
|
|
500
|
+
'compliant': compliant,
|
|
501
|
+
'memory_id': memory_id,
|
|
502
|
+
'compliance_checks': compliance_checks,
|
|
503
|
+
'privacy_level': privacy_level.value,
|
|
504
|
+
'checked_at': datetime.now().isoformat(),
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
except Exception as e:
|
|
508
|
+
logger.error(f"Failed to check GDPR compliance: {e}")
|
|
509
|
+
return {
|
|
510
|
+
'compliant': False,
|
|
511
|
+
'error': str(e),
|
|
512
|
+
'memory_id': memory_id,
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
def export_user_data(
|
|
516
|
+
self,
|
|
517
|
+
agent_id: str,
|
|
518
|
+
export_format: str = "json"
|
|
519
|
+
) -> Dict[str, Any]:
|
|
520
|
+
"""
|
|
521
|
+
Export user data for GDPR compliance.
|
|
522
|
+
|
|
523
|
+
Args:
|
|
524
|
+
agent_id: ID of the agent/user
|
|
525
|
+
export_format: Format for the export
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
Dictionary containing the export result
|
|
529
|
+
"""
|
|
530
|
+
try:
|
|
531
|
+
if not self.gdpr_compliance:
|
|
532
|
+
return {
|
|
533
|
+
'success': False,
|
|
534
|
+
'error': 'GDPR compliance is not enabled',
|
|
535
|
+
'agent_id': agent_id,
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
# Collect user data
|
|
539
|
+
user_data = {
|
|
540
|
+
'agent_id': agent_id,
|
|
541
|
+
'exported_at': datetime.now().isoformat(),
|
|
542
|
+
'export_format': export_format,
|
|
543
|
+
'data': {
|
|
544
|
+
'access_logs': [],
|
|
545
|
+
'privacy_settings': {},
|
|
546
|
+
'consent_records': {},
|
|
547
|
+
},
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
# Collect access logs for this user
|
|
551
|
+
for memory_id, logs in self.access_logs.items():
|
|
552
|
+
user_logs = [log for log in logs if log['agent_id'] == agent_id]
|
|
553
|
+
if user_logs:
|
|
554
|
+
user_data['data']['access_logs'].append({
|
|
555
|
+
'memory_id': memory_id,
|
|
556
|
+
'logs': user_logs,
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
# Collect privacy settings
|
|
560
|
+
for memory_id, privacy_level in self.memory_privacy_levels.items():
|
|
561
|
+
user_data['data']['privacy_settings'][memory_id] = privacy_level.value
|
|
562
|
+
|
|
563
|
+
# Collect consent records
|
|
564
|
+
if agent_id in self.consent_records:
|
|
565
|
+
user_data['data']['consent_records'] = self.consent_records[agent_id]
|
|
566
|
+
|
|
567
|
+
# Format export based on requested format
|
|
568
|
+
if export_format == "json":
|
|
569
|
+
export_data = json.dumps(user_data, indent=2)
|
|
570
|
+
else:
|
|
571
|
+
export_data = str(user_data)
|
|
572
|
+
|
|
573
|
+
logger.info(f"Exported user data for agent {agent_id}")
|
|
574
|
+
|
|
575
|
+
return {
|
|
576
|
+
'success': True,
|
|
577
|
+
'agent_id': agent_id,
|
|
578
|
+
'export_format': export_format,
|
|
579
|
+
'export_data': export_data,
|
|
580
|
+
'exported_at': user_data['exported_at'],
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
except Exception as e:
|
|
584
|
+
logger.error(f"Failed to export user data: {e}")
|
|
585
|
+
return {
|
|
586
|
+
'success': False,
|
|
587
|
+
'error': str(e),
|
|
588
|
+
'agent_id': agent_id,
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
def delete_user_data(
|
|
592
|
+
self,
|
|
593
|
+
agent_id: str,
|
|
594
|
+
confirmation_token: str
|
|
595
|
+
) -> Dict[str, Any]:
|
|
596
|
+
"""
|
|
597
|
+
Delete user data for GDPR compliance.
|
|
598
|
+
|
|
599
|
+
Args:
|
|
600
|
+
agent_id: ID of the agent/user
|
|
601
|
+
confirmation_token: Confirmation token for deletion
|
|
602
|
+
|
|
603
|
+
Returns:
|
|
604
|
+
Dictionary containing the deletion result
|
|
605
|
+
"""
|
|
606
|
+
try:
|
|
607
|
+
if not self.gdpr_compliance:
|
|
608
|
+
return {
|
|
609
|
+
'success': False,
|
|
610
|
+
'error': 'GDPR compliance is not enabled',
|
|
611
|
+
'agent_id': agent_id,
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
# Verify confirmation token (simplified)
|
|
615
|
+
if not confirmation_token or len(confirmation_token) < 10:
|
|
616
|
+
return {
|
|
617
|
+
'success': False,
|
|
618
|
+
'error': 'Invalid confirmation token',
|
|
619
|
+
'agent_id': agent_id,
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
# Record deletion request
|
|
623
|
+
self.deletion_requests[agent_id] = {
|
|
624
|
+
'requested_at': datetime.now().isoformat(),
|
|
625
|
+
'confirmation_token': confirmation_token,
|
|
626
|
+
'status': 'pending',
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
# Delete user data
|
|
630
|
+
deleted_items = {
|
|
631
|
+
'access_logs': 0,
|
|
632
|
+
'privacy_settings': 0,
|
|
633
|
+
'consent_records': 0,
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
# Delete access logs
|
|
637
|
+
for memory_id, logs in self.access_logs.items():
|
|
638
|
+
original_count = len(logs)
|
|
639
|
+
self.access_logs[memory_id] = [log for log in logs if log['agent_id'] != agent_id]
|
|
640
|
+
deleted_items['access_logs'] += original_count - len(self.access_logs[memory_id])
|
|
641
|
+
|
|
642
|
+
# Delete privacy settings (only for memories owned by the user)
|
|
643
|
+
# Note: This would need to be coordinated with the memory manager
|
|
644
|
+
|
|
645
|
+
# Delete consent records
|
|
646
|
+
if agent_id in self.consent_records:
|
|
647
|
+
deleted_items['consent_records'] = len(self.consent_records[agent_id])
|
|
648
|
+
del self.consent_records[agent_id]
|
|
649
|
+
|
|
650
|
+
# Update deletion request status
|
|
651
|
+
self.deletion_requests[agent_id]['status'] = 'completed'
|
|
652
|
+
self.deletion_requests[agent_id]['completed_at'] = datetime.now().isoformat()
|
|
653
|
+
self.deletion_requests[agent_id]['deleted_items'] = deleted_items
|
|
654
|
+
|
|
655
|
+
logger.info(f"Deleted user data for agent {agent_id}")
|
|
656
|
+
|
|
657
|
+
return {
|
|
658
|
+
'success': True,
|
|
659
|
+
'agent_id': agent_id,
|
|
660
|
+
'deleted_items': deleted_items,
|
|
661
|
+
'deleted_at': datetime.now().isoformat(),
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
except Exception as e:
|
|
665
|
+
logger.error(f"Failed to delete user data: {e}")
|
|
666
|
+
return {
|
|
667
|
+
'success': False,
|
|
668
|
+
'error': str(e),
|
|
669
|
+
'agent_id': agent_id,
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
def get_privacy_statistics(self) -> Dict[str, Any]:
|
|
673
|
+
"""
|
|
674
|
+
Get privacy statistics.
|
|
675
|
+
|
|
676
|
+
Returns:
|
|
677
|
+
Dictionary containing privacy statistics
|
|
678
|
+
"""
|
|
679
|
+
try:
|
|
680
|
+
stats = {
|
|
681
|
+
'total_memories': len(self.memory_privacy_levels),
|
|
682
|
+
'privacy_level_breakdown': {},
|
|
683
|
+
'encrypted_memories': len(self.encrypted_memories),
|
|
684
|
+
'anonymized_memories': len(self.anonymized_memories),
|
|
685
|
+
'access_logs_count': sum(len(logs) for logs in self.access_logs.values()),
|
|
686
|
+
'retention_policies': len(self.retention_policies),
|
|
687
|
+
'gdpr_compliance_enabled': self.gdpr_compliance,
|
|
688
|
+
'deletion_requests': len(self.deletion_requests),
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
# Privacy level breakdown
|
|
692
|
+
for privacy_level in PrivacyLevel:
|
|
693
|
+
count = sum(1 for level in self.memory_privacy_levels.values() if level == privacy_level)
|
|
694
|
+
stats['privacy_level_breakdown'][privacy_level.value] = count
|
|
695
|
+
|
|
696
|
+
return stats
|
|
697
|
+
|
|
698
|
+
except Exception as e:
|
|
699
|
+
logger.error(f"Failed to get privacy statistics: {e}")
|
|
700
|
+
return {}
|
|
701
|
+
|
|
702
|
+
def _apply_maximum_privacy(self, memory_id: str) -> None:
|
|
703
|
+
"""Apply maximum privacy measures to a memory."""
|
|
704
|
+
# Encrypt the memory
|
|
705
|
+
self.encrypt_memory(memory_id)
|
|
706
|
+
|
|
707
|
+
# Anonymize the memory
|
|
708
|
+
self.anonymize_memory(memory_id)
|
|
709
|
+
|
|
710
|
+
# Apply strict retention policy
|
|
711
|
+
self.apply_retention_policy(memory_id, '7_days')
|
|
712
|
+
|
|
713
|
+
def _apply_enhanced_privacy(self, memory_id: str) -> None:
|
|
714
|
+
"""Apply enhanced privacy measures to a memory."""
|
|
715
|
+
# Anonymize the memory
|
|
716
|
+
self.anonymize_memory(memory_id)
|
|
717
|
+
|
|
718
|
+
# Apply standard retention policy
|
|
719
|
+
self.apply_retention_policy(memory_id, '30_days')
|
|
720
|
+
|
|
721
|
+
def _generate_encryption_key(self, memory_id: str) -> str:
|
|
722
|
+
"""Generate an encryption key for a memory."""
|
|
723
|
+
# Simple key generation based on memory ID and timestamp
|
|
724
|
+
key_data = f"{memory_id}_{datetime.now().isoformat()}"
|
|
725
|
+
return hashlib.sha256(key_data.encode()).hexdigest()[:32]
|
|
726
|
+
|
|
727
|
+
def _get_default_anonymization_rules(self) -> Dict[str, Any]:
|
|
728
|
+
"""Get default anonymization rules."""
|
|
729
|
+
return {
|
|
730
|
+
'email_patterns': True,
|
|
731
|
+
'phone_patterns': True,
|
|
732
|
+
'credit_card_patterns': True,
|
|
733
|
+
'ssn_patterns': True,
|
|
734
|
+
'ip_addresses': True,
|
|
735
|
+
'names': False, # Keep names for context
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
def _parse_retention_policy(self, policy: str) -> int:
|
|
739
|
+
"""Parse retention policy string to days."""
|
|
740
|
+
if policy.endswith('_days'):
|
|
741
|
+
return int(policy.split('_')[0])
|
|
742
|
+
elif policy.endswith('_months'):
|
|
743
|
+
return int(policy.split('_')[0]) * 30
|
|
744
|
+
elif policy.endswith('_years'):
|
|
745
|
+
return int(policy.split('_')[0]) * 365
|
|
746
|
+
else:
|
|
747
|
+
return 30 # Default to 30 days
|
|
748
|
+
|
|
749
|
+
def _log_privacy_change(
|
|
750
|
+
self,
|
|
751
|
+
memory_id: str,
|
|
752
|
+
privacy_level: PrivacyLevel,
|
|
753
|
+
set_by: str
|
|
754
|
+
) -> None:
|
|
755
|
+
"""Log privacy level change."""
|
|
756
|
+
log_entry = {
|
|
757
|
+
'timestamp': datetime.now().isoformat(),
|
|
758
|
+
'memory_id': memory_id,
|
|
759
|
+
'privacy_level': privacy_level.value,
|
|
760
|
+
'set_by': set_by,
|
|
761
|
+
'type': 'privacy_change',
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if memory_id not in self.access_logs:
|
|
765
|
+
self.access_logs[memory_id] = []
|
|
766
|
+
|
|
767
|
+
self.access_logs[memory_id].append(log_entry)
|