genxai-framework 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 (156) hide show
  1. cli/__init__.py +3 -0
  2. cli/commands/__init__.py +6 -0
  3. cli/commands/approval.py +85 -0
  4. cli/commands/audit.py +127 -0
  5. cli/commands/metrics.py +25 -0
  6. cli/commands/tool.py +389 -0
  7. cli/main.py +32 -0
  8. genxai/__init__.py +81 -0
  9. genxai/api/__init__.py +5 -0
  10. genxai/api/app.py +21 -0
  11. genxai/config/__init__.py +5 -0
  12. genxai/config/settings.py +37 -0
  13. genxai/connectors/__init__.py +19 -0
  14. genxai/connectors/base.py +122 -0
  15. genxai/connectors/kafka.py +92 -0
  16. genxai/connectors/postgres_cdc.py +95 -0
  17. genxai/connectors/registry.py +44 -0
  18. genxai/connectors/sqs.py +94 -0
  19. genxai/connectors/webhook.py +73 -0
  20. genxai/core/__init__.py +37 -0
  21. genxai/core/agent/__init__.py +32 -0
  22. genxai/core/agent/base.py +206 -0
  23. genxai/core/agent/config_io.py +59 -0
  24. genxai/core/agent/registry.py +98 -0
  25. genxai/core/agent/runtime.py +970 -0
  26. genxai/core/communication/__init__.py +6 -0
  27. genxai/core/communication/collaboration.py +44 -0
  28. genxai/core/communication/message_bus.py +192 -0
  29. genxai/core/communication/protocols.py +35 -0
  30. genxai/core/execution/__init__.py +22 -0
  31. genxai/core/execution/metadata.py +181 -0
  32. genxai/core/execution/queue.py +201 -0
  33. genxai/core/graph/__init__.py +30 -0
  34. genxai/core/graph/checkpoints.py +77 -0
  35. genxai/core/graph/edges.py +131 -0
  36. genxai/core/graph/engine.py +813 -0
  37. genxai/core/graph/executor.py +516 -0
  38. genxai/core/graph/nodes.py +161 -0
  39. genxai/core/graph/trigger_runner.py +40 -0
  40. genxai/core/memory/__init__.py +19 -0
  41. genxai/core/memory/base.py +72 -0
  42. genxai/core/memory/embedding.py +327 -0
  43. genxai/core/memory/episodic.py +448 -0
  44. genxai/core/memory/long_term.py +467 -0
  45. genxai/core/memory/manager.py +543 -0
  46. genxai/core/memory/persistence.py +297 -0
  47. genxai/core/memory/procedural.py +461 -0
  48. genxai/core/memory/semantic.py +526 -0
  49. genxai/core/memory/shared.py +62 -0
  50. genxai/core/memory/short_term.py +303 -0
  51. genxai/core/memory/vector_store.py +508 -0
  52. genxai/core/memory/working.py +211 -0
  53. genxai/core/state/__init__.py +6 -0
  54. genxai/core/state/manager.py +293 -0
  55. genxai/core/state/schema.py +115 -0
  56. genxai/llm/__init__.py +14 -0
  57. genxai/llm/base.py +150 -0
  58. genxai/llm/factory.py +329 -0
  59. genxai/llm/providers/__init__.py +1 -0
  60. genxai/llm/providers/anthropic.py +249 -0
  61. genxai/llm/providers/cohere.py +274 -0
  62. genxai/llm/providers/google.py +334 -0
  63. genxai/llm/providers/ollama.py +147 -0
  64. genxai/llm/providers/openai.py +257 -0
  65. genxai/llm/routing.py +83 -0
  66. genxai/observability/__init__.py +6 -0
  67. genxai/observability/logging.py +327 -0
  68. genxai/observability/metrics.py +494 -0
  69. genxai/observability/tracing.py +372 -0
  70. genxai/performance/__init__.py +39 -0
  71. genxai/performance/cache.py +256 -0
  72. genxai/performance/pooling.py +289 -0
  73. genxai/security/audit.py +304 -0
  74. genxai/security/auth.py +315 -0
  75. genxai/security/cost_control.py +528 -0
  76. genxai/security/default_policies.py +44 -0
  77. genxai/security/jwt.py +142 -0
  78. genxai/security/oauth.py +226 -0
  79. genxai/security/pii.py +366 -0
  80. genxai/security/policy_engine.py +82 -0
  81. genxai/security/rate_limit.py +341 -0
  82. genxai/security/rbac.py +247 -0
  83. genxai/security/validation.py +218 -0
  84. genxai/tools/__init__.py +21 -0
  85. genxai/tools/base.py +383 -0
  86. genxai/tools/builtin/__init__.py +131 -0
  87. genxai/tools/builtin/communication/__init__.py +15 -0
  88. genxai/tools/builtin/communication/email_sender.py +159 -0
  89. genxai/tools/builtin/communication/notification_manager.py +167 -0
  90. genxai/tools/builtin/communication/slack_notifier.py +118 -0
  91. genxai/tools/builtin/communication/sms_sender.py +118 -0
  92. genxai/tools/builtin/communication/webhook_caller.py +136 -0
  93. genxai/tools/builtin/computation/__init__.py +15 -0
  94. genxai/tools/builtin/computation/calculator.py +101 -0
  95. genxai/tools/builtin/computation/code_executor.py +183 -0
  96. genxai/tools/builtin/computation/data_validator.py +259 -0
  97. genxai/tools/builtin/computation/hash_generator.py +129 -0
  98. genxai/tools/builtin/computation/regex_matcher.py +201 -0
  99. genxai/tools/builtin/data/__init__.py +15 -0
  100. genxai/tools/builtin/data/csv_processor.py +213 -0
  101. genxai/tools/builtin/data/data_transformer.py +299 -0
  102. genxai/tools/builtin/data/json_processor.py +233 -0
  103. genxai/tools/builtin/data/text_analyzer.py +288 -0
  104. genxai/tools/builtin/data/xml_processor.py +175 -0
  105. genxai/tools/builtin/database/__init__.py +15 -0
  106. genxai/tools/builtin/database/database_inspector.py +157 -0
  107. genxai/tools/builtin/database/mongodb_query.py +196 -0
  108. genxai/tools/builtin/database/redis_cache.py +167 -0
  109. genxai/tools/builtin/database/sql_query.py +145 -0
  110. genxai/tools/builtin/database/vector_search.py +163 -0
  111. genxai/tools/builtin/file/__init__.py +17 -0
  112. genxai/tools/builtin/file/directory_scanner.py +214 -0
  113. genxai/tools/builtin/file/file_compressor.py +237 -0
  114. genxai/tools/builtin/file/file_reader.py +102 -0
  115. genxai/tools/builtin/file/file_writer.py +122 -0
  116. genxai/tools/builtin/file/image_processor.py +186 -0
  117. genxai/tools/builtin/file/pdf_parser.py +144 -0
  118. genxai/tools/builtin/test/__init__.py +15 -0
  119. genxai/tools/builtin/test/async_simulator.py +62 -0
  120. genxai/tools/builtin/test/data_transformer.py +99 -0
  121. genxai/tools/builtin/test/error_generator.py +82 -0
  122. genxai/tools/builtin/test/simple_math.py +94 -0
  123. genxai/tools/builtin/test/string_processor.py +72 -0
  124. genxai/tools/builtin/web/__init__.py +15 -0
  125. genxai/tools/builtin/web/api_caller.py +161 -0
  126. genxai/tools/builtin/web/html_parser.py +330 -0
  127. genxai/tools/builtin/web/http_client.py +187 -0
  128. genxai/tools/builtin/web/url_validator.py +162 -0
  129. genxai/tools/builtin/web/web_scraper.py +170 -0
  130. genxai/tools/custom/my_test_tool_2.py +9 -0
  131. genxai/tools/dynamic.py +105 -0
  132. genxai/tools/mcp_server.py +167 -0
  133. genxai/tools/persistence/__init__.py +6 -0
  134. genxai/tools/persistence/models.py +55 -0
  135. genxai/tools/persistence/service.py +322 -0
  136. genxai/tools/registry.py +227 -0
  137. genxai/tools/security/__init__.py +11 -0
  138. genxai/tools/security/limits.py +214 -0
  139. genxai/tools/security/policy.py +20 -0
  140. genxai/tools/security/sandbox.py +248 -0
  141. genxai/tools/templates.py +435 -0
  142. genxai/triggers/__init__.py +19 -0
  143. genxai/triggers/base.py +104 -0
  144. genxai/triggers/file_watcher.py +75 -0
  145. genxai/triggers/queue.py +68 -0
  146. genxai/triggers/registry.py +82 -0
  147. genxai/triggers/schedule.py +66 -0
  148. genxai/triggers/webhook.py +68 -0
  149. genxai/utils/__init__.py +1 -0
  150. genxai/utils/tokens.py +295 -0
  151. genxai_framework-0.1.0.dist-info/METADATA +495 -0
  152. genxai_framework-0.1.0.dist-info/RECORD +156 -0
  153. genxai_framework-0.1.0.dist-info/WHEEL +5 -0
  154. genxai_framework-0.1.0.dist-info/entry_points.txt +2 -0
  155. genxai_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
  156. genxai_framework-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,467 @@
1
+ """Long-term memory implementation with Redis backend."""
2
+
3
+ from typing import Any, Dict, List, Optional, Tuple
4
+ from datetime import datetime, timedelta
5
+ import json
6
+ import logging
7
+
8
+ from genxai.core.memory.base import Memory, MemoryType, MemoryConfig
9
+ from genxai.core.memory.persistence import (
10
+ JsonMemoryStore,
11
+ MemoryPersistenceConfig,
12
+ SqliteMemoryStore,
13
+ create_memory_store,
14
+ )
15
+ from genxai.core.memory.vector_store import VectorStore
16
+ from genxai.core.memory.embedding import EmbeddingService
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class LongTermMemory:
22
+ """Long-term memory with persistent Redis storage.
23
+
24
+ This memory type stores important memories persistently and supports
25
+ TTL-based expiration and importance-based retention.
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ config: Optional[MemoryConfig] = None,
31
+ redis_client: Optional[Any] = None,
32
+ key_prefix: str = "genxai:memory:long_term:",
33
+ vector_store: Optional[VectorStore] = None,
34
+ embedding_service: Optional[EmbeddingService] = None,
35
+ persistence: Optional[MemoryPersistenceConfig] = None,
36
+ ) -> None:
37
+ """Initialize long-term memory.
38
+
39
+ Args:
40
+ config: Memory configuration
41
+ redis_client: Redis client instance (optional, will use in-memory if not provided)
42
+ key_prefix: Prefix for Redis keys
43
+ """
44
+ self.config = config or MemoryConfig()
45
+ self._redis = redis_client
46
+ self._key_prefix = key_prefix
47
+ self._vector_store = vector_store
48
+ self._embedding_service = embedding_service
49
+ self._persistence = persistence
50
+ if persistence:
51
+ self._store = create_memory_store(persistence)
52
+ else:
53
+ self._store = None
54
+
55
+ # Fallback to in-memory storage if Redis not available
56
+ self._in_memory_storage: Dict[str, Memory] = {}
57
+ self._use_redis = redis_client is not None
58
+
59
+ if self._use_redis:
60
+ logger.info("Initialized long-term memory with Redis backend")
61
+ else:
62
+ logger.warning(
63
+ "Redis client not provided. Using in-memory storage. "
64
+ "Memories will not persist across restarts."
65
+ )
66
+
67
+ if self._store and self._persistence and self._persistence.enabled:
68
+ self._load_from_disk()
69
+
70
+ def store(
71
+ self,
72
+ memory: Memory,
73
+ ttl: Optional[int] = None,
74
+ ) -> None:
75
+ """Store a memory with optional TTL.
76
+
77
+ Args:
78
+ memory: Memory to store
79
+ ttl: Time-to-live in seconds (None for no expiration)
80
+ """
81
+ key = self._make_key(memory.id)
82
+
83
+ # Serialize memory
84
+ data = self._serialize_memory(memory)
85
+
86
+ if self._use_redis:
87
+ try:
88
+ # Store in Redis
89
+ if ttl:
90
+ self._redis.setex(key, ttl, data)
91
+ else:
92
+ self._redis.set(key, data)
93
+
94
+ # Store metadata for querying
95
+ self._store_metadata(memory)
96
+
97
+ logger.debug(f"Stored memory {memory.id} in Redis (TTL: {ttl})")
98
+ except Exception as e:
99
+ logger.error(f"Failed to store memory in Redis: {e}")
100
+ # Fallback to in-memory
101
+ self._in_memory_storage[memory.id] = memory
102
+ else:
103
+ # In-memory storage
104
+ self._in_memory_storage[memory.id] = memory
105
+ logger.debug(f"Stored memory {memory.id} in-memory")
106
+
107
+ self._persist()
108
+
109
+ def retrieve(self, memory_id: str) -> Optional[Memory]:
110
+ """Retrieve a memory by ID.
111
+
112
+ Args:
113
+ memory_id: ID of memory to retrieve
114
+
115
+ Returns:
116
+ Memory if found, None otherwise
117
+ """
118
+ if self._use_redis:
119
+ try:
120
+ key = self._make_key(memory_id)
121
+ data = self._redis.get(key)
122
+
123
+ if data:
124
+ memory = self._deserialize_memory(data)
125
+
126
+ # Update access tracking
127
+ memory.access_count += 1
128
+ memory.last_accessed = datetime.now()
129
+
130
+ # Update in storage
131
+ self.store(memory)
132
+
133
+ logger.debug(f"Retrieved memory {memory_id} from Redis")
134
+ return memory
135
+ except Exception as e:
136
+ logger.error(f"Failed to retrieve memory from Redis: {e}")
137
+
138
+ # Fallback to in-memory
139
+ if memory_id in self._in_memory_storage:
140
+ memory = self._in_memory_storage[memory_id]
141
+ memory.access_count += 1
142
+ memory.last_accessed = datetime.now()
143
+ logger.debug(f"Retrieved memory {memory_id} from in-memory storage")
144
+ return memory
145
+
146
+ return None
147
+
148
+ def retrieve_by_importance(
149
+ self,
150
+ threshold: float = 0.7,
151
+ limit: int = 10,
152
+ ) -> List[Memory]:
153
+ """Retrieve memories above an importance threshold.
154
+
155
+ Args:
156
+ threshold: Minimum importance score
157
+ limit: Maximum number of memories
158
+
159
+ Returns:
160
+ List of important memories
161
+ """
162
+ if self._use_redis:
163
+ try:
164
+ # Query metadata index
165
+ pattern = f"{self._key_prefix}*"
166
+ keys = self._redis.keys(pattern)
167
+
168
+ memories = []
169
+ for key in keys:
170
+ data = self._redis.get(key)
171
+ if data:
172
+ memory = self._deserialize_memory(data)
173
+ if memory.importance >= threshold:
174
+ memories.append(memory)
175
+
176
+ # Sort by importance
177
+ memories.sort(key=lambda m: m.importance, reverse=True)
178
+
179
+ result = memories[:limit]
180
+ logger.debug(f"Retrieved {len(result)} important memories from Redis")
181
+ return result
182
+ except Exception as e:
183
+ logger.error(f"Failed to query Redis: {e}")
184
+
185
+ # Fallback to in-memory
186
+ memories = [
187
+ m for m in self._in_memory_storage.values()
188
+ if m.importance >= threshold
189
+ ]
190
+ memories.sort(key=lambda m: m.importance, reverse=True)
191
+ result = memories[:limit]
192
+ logger.debug(f"Retrieved {len(result)} important memories from in-memory storage")
193
+ return result
194
+
195
+ def retrieve_recent(
196
+ self,
197
+ days: int = 7,
198
+ limit: int = 10,
199
+ ) -> List[Memory]:
200
+ """Retrieve recent memories within a time window.
201
+
202
+ Args:
203
+ days: Number of days to look back
204
+ limit: Maximum number of memories
205
+
206
+ Returns:
207
+ List of recent memories
208
+ """
209
+ cutoff = datetime.now() - timedelta(days=days)
210
+
211
+ if self._use_redis:
212
+ try:
213
+ pattern = f"{self._key_prefix}*"
214
+ keys = self._redis.keys(pattern)
215
+
216
+ memories = []
217
+ for key in keys:
218
+ data = self._redis.get(key)
219
+ if data:
220
+ memory = self._deserialize_memory(data)
221
+ if memory.timestamp >= cutoff:
222
+ memories.append(memory)
223
+
224
+ # Sort by timestamp (most recent first)
225
+ memories.sort(key=lambda m: m.timestamp, reverse=True)
226
+
227
+ result = memories[:limit]
228
+ logger.debug(f"Retrieved {len(result)} recent memories from Redis")
229
+ return result
230
+ except Exception as e:
231
+ logger.error(f"Failed to query Redis: {e}")
232
+
233
+ # Fallback to in-memory
234
+ memories = [
235
+ m for m in self._in_memory_storage.values()
236
+ if m.timestamp >= cutoff
237
+ ]
238
+ memories.sort(key=lambda m: m.timestamp, reverse=True)
239
+ result = memories[:limit]
240
+ logger.debug(f"Retrieved {len(result)} recent memories from in-memory storage")
241
+ return result
242
+
243
+ def delete(self, memory_id: str) -> bool:
244
+ """Delete a memory by ID.
245
+
246
+ Args:
247
+ memory_id: ID of memory to delete
248
+
249
+ Returns:
250
+ True if deleted, False if not found
251
+ """
252
+ if self._use_redis:
253
+ try:
254
+ key = self._make_key(memory_id)
255
+ deleted = self._redis.delete(key)
256
+
257
+ if deleted:
258
+ self._delete_metadata(memory_id)
259
+ logger.debug(f"Deleted memory {memory_id} from Redis")
260
+ return True
261
+ except Exception as e:
262
+ logger.error(f"Failed to delete memory from Redis: {e}")
263
+
264
+ # Fallback to in-memory
265
+ if memory_id in self._in_memory_storage:
266
+ del self._in_memory_storage[memory_id]
267
+ logger.debug(f"Deleted memory {memory_id} from in-memory storage")
268
+ self._persist()
269
+ return True
270
+
271
+ return False
272
+
273
+ def clear(self) -> None:
274
+ """Clear all memories."""
275
+ if self._use_redis:
276
+ try:
277
+ pattern = f"{self._key_prefix}*"
278
+ keys = self._redis.keys(pattern)
279
+
280
+ if keys:
281
+ self._redis.delete(*keys)
282
+
283
+ logger.info(f"Cleared {len(keys)} memories from Redis")
284
+ except Exception as e:
285
+ logger.error(f"Failed to clear Redis: {e}")
286
+
287
+ # Clear in-memory storage
288
+ count = len(self._in_memory_storage)
289
+ self._in_memory_storage.clear()
290
+ logger.info(f"Cleared {count} memories from in-memory storage")
291
+
292
+ self._persist()
293
+
294
+ def get_size(self) -> int:
295
+ """Get current number of stored memories.
296
+
297
+ Returns:
298
+ Number of memories
299
+ """
300
+ if self._use_redis:
301
+ try:
302
+ pattern = f"{self._key_prefix}*"
303
+ keys = self._redis.keys(pattern)
304
+ return len(keys)
305
+ except Exception as e:
306
+ logger.error(f"Failed to get size from Redis: {e}")
307
+
308
+ return len(self._in_memory_storage)
309
+
310
+ def get_stats(self) -> Dict[str, Any]:
311
+ """Get memory statistics.
312
+
313
+ Returns:
314
+ Statistics dictionary
315
+ """
316
+ size = self.get_size()
317
+
318
+ if size == 0:
319
+ return {
320
+ "size": 0,
321
+ "backend": "redis" if self._use_redis else "in-memory",
322
+ "avg_importance": 0.0,
323
+ "vector_store": bool(self._vector_store),
324
+ "persistence": bool(self._persistence and self._persistence.enabled),
325
+ }
326
+
327
+ # Get sample of memories for stats
328
+ if self._use_redis:
329
+ try:
330
+ pattern = f"{self._key_prefix}*"
331
+ keys = list(self._redis.keys(pattern))[:100] # Sample
332
+
333
+ memories = []
334
+ for key in keys:
335
+ data = self._redis.get(key)
336
+ if data:
337
+ memories.append(self._deserialize_memory(data))
338
+ except Exception as e:
339
+ logger.error(f"Failed to get stats from Redis: {e}")
340
+ memories = []
341
+ else:
342
+ memories = list(self._in_memory_storage.values())
343
+
344
+ if not memories:
345
+ return {
346
+ "size": size,
347
+ "backend": "redis" if self._use_redis else "in-memory",
348
+ "avg_importance": 0.0,
349
+ }
350
+
351
+ return {
352
+ "size": size,
353
+ "backend": "redis" if self._use_redis else "in-memory",
354
+ "avg_importance": sum(m.importance for m in memories) / len(memories),
355
+ "oldest_memory": min(m.timestamp for m in memories).isoformat(),
356
+ "newest_memory": max(m.timestamp for m in memories).isoformat(),
357
+ "vector_store": bool(self._vector_store),
358
+ "persistence": bool(self._persistence and self._persistence.enabled),
359
+ }
360
+
361
+ async def store_with_embedding(self, memory: Memory, ttl: Optional[int] = None) -> None:
362
+ """Store memory and push embedding to vector store if configured."""
363
+ self.store(memory, ttl)
364
+ if self._vector_store and self._embedding_service:
365
+ try:
366
+ embedding = await self._embedding_service.embed(str(memory.content))
367
+ await self._vector_store.store(memory, embedding)
368
+ except Exception as exc:
369
+ logger.error("Failed to store memory embedding: %s", exc)
370
+
371
+ async def search(
372
+ self,
373
+ query: str,
374
+ limit: int = 10,
375
+ filters: Optional[Dict[str, Any]] = None,
376
+ ) -> List[Tuple[Memory, float]]:
377
+ """Search long-term memory using vector store if available."""
378
+ if not self._vector_store or not self._embedding_service:
379
+ logger.warning("Vector search not available")
380
+ return []
381
+
382
+ try:
383
+ query_embedding = await self._embedding_service.embed(query)
384
+ return await self._vector_store.search(query_embedding, limit=limit, filters=filters)
385
+ except Exception as exc:
386
+ logger.error("Failed to search long-term memory: %s", exc)
387
+ return []
388
+
389
+ def _persist(self) -> None:
390
+ if not self._store:
391
+ return
392
+ payload = [json.loads(self._serialize_memory(memory)) for memory in self._in_memory_storage.values()]
393
+ self._store.save_list("long_term_memory.json", payload)
394
+
395
+ def _load_from_disk(self) -> None:
396
+ if not self._store:
397
+ return
398
+ data = self._store.load_list("long_term_memory.json")
399
+ if not data:
400
+ return
401
+ self._in_memory_storage = {
402
+ item["id"]: self._deserialize_memory(json.dumps(item)) for item in data
403
+ }
404
+
405
+ def _make_key(self, memory_id: str) -> str:
406
+ """Create Redis key for memory ID."""
407
+ return f"{self._key_prefix}{memory_id}"
408
+
409
+ def _serialize_memory(self, memory: Memory) -> str:
410
+ """Serialize memory to JSON string."""
411
+ data = {
412
+ "id": memory.id,
413
+ "type": memory.type.value,
414
+ "content": memory.content,
415
+ "metadata": memory.metadata,
416
+ "timestamp": memory.timestamp.isoformat(),
417
+ "importance": memory.importance,
418
+ "access_count": memory.access_count,
419
+ "last_accessed": memory.last_accessed.isoformat(),
420
+ "tags": memory.tags,
421
+ }
422
+ return json.dumps(data)
423
+
424
+ def _deserialize_memory(self, data: str) -> Memory:
425
+ """Deserialize memory from JSON string."""
426
+ obj = json.loads(data)
427
+ return Memory(
428
+ id=obj["id"],
429
+ type=MemoryType(obj["type"]),
430
+ content=obj["content"],
431
+ metadata=obj["metadata"],
432
+ timestamp=datetime.fromisoformat(obj["timestamp"]),
433
+ importance=obj["importance"],
434
+ access_count=obj["access_count"],
435
+ last_accessed=datetime.fromisoformat(obj["last_accessed"]),
436
+ tags=obj["tags"],
437
+ )
438
+
439
+ def _store_metadata(self, memory: Memory) -> None:
440
+ """Store memory metadata for querying (placeholder)."""
441
+ if not self._store:
442
+ return
443
+ if isinstance(self._store, SqliteMemoryStore):
444
+ self._store.store_long_term_metadata(
445
+ memory_id=memory.id,
446
+ memory_type=memory.type.value,
447
+ importance=memory.importance,
448
+ timestamp=memory.timestamp.isoformat(),
449
+ tags=memory.tags,
450
+ metadata=memory.metadata,
451
+ )
452
+
453
+ def _delete_metadata(self, memory_id: str) -> None:
454
+ """Delete memory metadata (placeholder)."""
455
+ if not self._store:
456
+ return
457
+ if isinstance(self._store, SqliteMemoryStore):
458
+ self._store.delete_long_term_metadata(memory_id)
459
+
460
+ def __len__(self) -> int:
461
+ """Get number of stored memories."""
462
+ return self.get_size()
463
+
464
+ def __repr__(self) -> str:
465
+ """String representation."""
466
+ backend = "Redis" if self._use_redis else "In-Memory"
467
+ return f"LongTermMemory(backend={backend}, size={self.get_size()})"