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