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,526 @@
1
+ """Semantic memory implementation for storing facts and knowledge."""
2
+
3
+ from typing import Any, Dict, List, Optional, Set, Tuple
4
+ from datetime import datetime
5
+ import logging
6
+ import uuid
7
+
8
+ from genxai.core.memory.persistence import (
9
+ JsonMemoryStore,
10
+ MemoryPersistenceConfig,
11
+ SqliteMemoryStore,
12
+ create_memory_store,
13
+ )
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class Fact:
19
+ """Represents a single fact in semantic memory."""
20
+
21
+ def __init__(
22
+ self,
23
+ id: str,
24
+ subject: str,
25
+ predicate: str,
26
+ object: str,
27
+ confidence: float = 1.0,
28
+ source: Optional[str] = None,
29
+ timestamp: Optional[datetime] = None,
30
+ metadata: Optional[Dict[str, Any]] = None,
31
+ ) -> None:
32
+ """Initialize fact.
33
+
34
+ Args:
35
+ id: Unique fact ID
36
+ subject: Subject entity
37
+ predicate: Relationship/property
38
+ object: Object entity/value
39
+ confidence: Confidence score (0.0 to 1.0)
40
+ source: Source of the fact
41
+ timestamp: When fact was learned
42
+ metadata: Additional metadata
43
+ """
44
+ self.id = id
45
+ self.subject = subject
46
+ self.predicate = predicate
47
+ self.object = object
48
+ self.confidence = confidence
49
+ self.source = source
50
+ self.timestamp = timestamp or datetime.now()
51
+ self.metadata = metadata or {}
52
+
53
+ def to_triple(self) -> Tuple[str, str, str]:
54
+ """Convert to RDF-style triple."""
55
+ return (self.subject, self.predicate, self.object)
56
+
57
+ def to_dict(self) -> Dict[str, Any]:
58
+ """Convert to dictionary."""
59
+ return {
60
+ "id": self.id,
61
+ "subject": self.subject,
62
+ "predicate": self.predicate,
63
+ "object": self.object,
64
+ "confidence": self.confidence,
65
+ "source": self.source,
66
+ "timestamp": self.timestamp.isoformat(),
67
+ "metadata": self.metadata,
68
+ }
69
+
70
+ @classmethod
71
+ def from_dict(cls, data: Dict[str, Any]) -> "Fact":
72
+ """Create fact from dictionary."""
73
+ return cls(
74
+ id=data["id"],
75
+ subject=data["subject"],
76
+ predicate=data["predicate"],
77
+ object=data["object"],
78
+ confidence=data.get("confidence", 1.0),
79
+ source=data.get("source"),
80
+ timestamp=datetime.fromisoformat(data["timestamp"]) if data.get("timestamp") else None,
81
+ metadata=data.get("metadata", {}),
82
+ )
83
+
84
+ def __repr__(self) -> str:
85
+ """String representation."""
86
+ return f"Fact({self.subject} {self.predicate} {self.object})"
87
+
88
+
89
+ class SemanticMemory:
90
+ """Semantic memory for storing facts and knowledge.
91
+
92
+ Stores structured knowledge as subject-predicate-object triples:
93
+ - Facts about entities
94
+ - Relationships between entities
95
+ - Properties and attributes
96
+ - General knowledge
97
+ """
98
+
99
+ def __init__(
100
+ self,
101
+ graph_db: Optional[Any] = None,
102
+ persistence: Optional[MemoryPersistenceConfig] = None,
103
+ ) -> None:
104
+ """Initialize semantic memory.
105
+
106
+ Args:
107
+ graph_db: Graph database client (Neo4j, etc.)
108
+ """
109
+ self._graph_db = graph_db
110
+ self._use_graph = graph_db is not None
111
+ self._persistence = persistence
112
+ if persistence:
113
+ self._store = create_memory_store(persistence)
114
+ else:
115
+ self._store = None
116
+
117
+ # Fallback to in-memory storage
118
+ self._facts: Dict[str, Fact] = {}
119
+ self._subject_index: Dict[str, Set[str]] = {} # subject -> fact_ids
120
+ self._predicate_index: Dict[str, Set[str]] = {} # predicate -> fact_ids
121
+ self._object_index: Dict[str, Set[str]] = {} # object -> fact_ids
122
+
123
+ if self._use_graph:
124
+ logger.info("Initialized semantic memory with graph database")
125
+ else:
126
+ logger.warning(
127
+ "Graph database not provided. Using in-memory storage. "
128
+ "Facts will not persist across restarts."
129
+ )
130
+
131
+ if self._store and self._persistence and self._persistence.enabled:
132
+ self._load_from_disk()
133
+
134
+ async def store_fact(
135
+ self,
136
+ subject: str,
137
+ predicate: str,
138
+ object: str,
139
+ confidence: float = 1.0,
140
+ source: Optional[str] = None,
141
+ metadata: Optional[Dict[str, Any]] = None,
142
+ ) -> Fact:
143
+ """Store a new fact.
144
+
145
+ Args:
146
+ subject: Subject entity
147
+ predicate: Relationship/property
148
+ object: Object entity/value
149
+ confidence: Confidence score
150
+ source: Source of the fact
151
+ metadata: Additional metadata
152
+
153
+ Returns:
154
+ Created fact
155
+ """
156
+ # Check if fact already exists
157
+ existing = await self._find_exact_fact(subject, predicate, object)
158
+ if existing:
159
+ # Update confidence if higher
160
+ if confidence > existing.confidence:
161
+ existing.confidence = confidence
162
+ existing.timestamp = datetime.now()
163
+ logger.debug(f"Updated fact confidence: {existing}")
164
+ return existing
165
+
166
+ # Create new fact
167
+ fact = Fact(
168
+ id=str(uuid.uuid4()),
169
+ subject=subject,
170
+ predicate=predicate,
171
+ object=object,
172
+ confidence=confidence,
173
+ source=source,
174
+ metadata=metadata,
175
+ )
176
+
177
+ if self._use_graph:
178
+ await self._store_in_graph(fact)
179
+ else:
180
+ # In-memory storage
181
+ self._facts[fact.id] = fact
182
+
183
+ # Update indexes
184
+ if subject not in self._subject_index:
185
+ self._subject_index[subject] = set()
186
+ self._subject_index[subject].add(fact.id)
187
+
188
+ if predicate not in self._predicate_index:
189
+ self._predicate_index[predicate] = set()
190
+ self._predicate_index[predicate].add(fact.id)
191
+
192
+ if object not in self._object_index:
193
+ self._object_index[object] = set()
194
+ self._object_index[object].add(fact.id)
195
+
196
+ self._persist()
197
+
198
+ logger.debug(f"Stored fact: {fact}")
199
+ return fact
200
+
201
+ async def retrieve_by_subject(
202
+ self,
203
+ subject: str,
204
+ predicate: Optional[str] = None,
205
+ ) -> List[Fact]:
206
+ """Retrieve facts about a subject.
207
+
208
+ Args:
209
+ subject: Subject entity
210
+ predicate: Optional predicate filter
211
+
212
+ Returns:
213
+ List of facts
214
+ """
215
+ if self._use_graph:
216
+ return await self._retrieve_by_subject_from_graph(subject, predicate)
217
+
218
+ # In-memory retrieval
219
+ fact_ids = self._subject_index.get(subject, set())
220
+ facts = [self._facts[fid] for fid in fact_ids]
221
+
222
+ if predicate:
223
+ facts = [f for f in facts if f.predicate == predicate]
224
+
225
+ return facts
226
+
227
+ async def retrieve_by_predicate(
228
+ self,
229
+ predicate: str,
230
+ subject: Optional[str] = None,
231
+ object: Optional[str] = None,
232
+ ) -> List[Fact]:
233
+ """Retrieve facts with a specific predicate.
234
+
235
+ Args:
236
+ predicate: Predicate/relationship
237
+ subject: Optional subject filter
238
+ object: Optional object filter
239
+
240
+ Returns:
241
+ List of facts
242
+ """
243
+ if self._use_graph:
244
+ return await self._retrieve_by_predicate_from_graph(
245
+ predicate, subject, object
246
+ )
247
+
248
+ # In-memory retrieval
249
+ fact_ids = self._predicate_index.get(predicate, set())
250
+ facts = [self._facts[fid] for fid in fact_ids]
251
+
252
+ if subject:
253
+ facts = [f for f in facts if f.subject == subject]
254
+ if object:
255
+ facts = [f for f in facts if f.object == object]
256
+
257
+ return facts
258
+
259
+ async def retrieve_by_object(
260
+ self,
261
+ object: str,
262
+ predicate: Optional[str] = None,
263
+ ) -> List[Fact]:
264
+ """Retrieve facts with a specific object.
265
+
266
+ Args:
267
+ object: Object entity/value
268
+ predicate: Optional predicate filter
269
+
270
+ Returns:
271
+ List of facts
272
+ """
273
+ if self._use_graph:
274
+ return await self._retrieve_by_object_from_graph(object, predicate)
275
+
276
+ # In-memory retrieval
277
+ fact_ids = self._object_index.get(object, set())
278
+ facts = [self._facts[fid] for fid in fact_ids]
279
+
280
+ if predicate:
281
+ facts = [f for f in facts if f.predicate == predicate]
282
+
283
+ return facts
284
+
285
+ async def query(
286
+ self,
287
+ subject: Optional[str] = None,
288
+ predicate: Optional[str] = None,
289
+ object: Optional[str] = None,
290
+ min_confidence: float = 0.0,
291
+ ) -> List[Fact]:
292
+ """Query facts with flexible filters.
293
+
294
+ Args:
295
+ subject: Optional subject filter
296
+ predicate: Optional predicate filter
297
+ object: Optional object filter
298
+ min_confidence: Minimum confidence threshold
299
+
300
+ Returns:
301
+ List of matching facts
302
+ """
303
+ facts = list(self._facts.values())
304
+
305
+ # Apply filters
306
+ if subject:
307
+ facts = [f for f in facts if f.subject == subject]
308
+ if predicate:
309
+ facts = [f for f in facts if f.predicate == predicate]
310
+ if object:
311
+ facts = [f for f in facts if f.object == object]
312
+ if min_confidence > 0.0:
313
+ facts = [f for f in facts if f.confidence >= min_confidence]
314
+
315
+ return facts
316
+
317
+ async def get_related_entities(
318
+ self,
319
+ entity: str,
320
+ max_depth: int = 2,
321
+ ) -> Set[str]:
322
+ """Get entities related to a given entity.
323
+
324
+ Args:
325
+ entity: Starting entity
326
+ max_depth: Maximum relationship depth
327
+
328
+ Returns:
329
+ Set of related entities
330
+ """
331
+ related = set()
332
+ to_explore = {entity}
333
+ explored = set()
334
+
335
+ for _ in range(max_depth):
336
+ if not to_explore:
337
+ break
338
+
339
+ current = to_explore.pop()
340
+ explored.add(current)
341
+
342
+ # Get facts where entity is subject
343
+ subject_facts = await self.retrieve_by_subject(current)
344
+ for fact in subject_facts:
345
+ related.add(fact.object)
346
+ if fact.object not in explored:
347
+ to_explore.add(fact.object)
348
+
349
+ # Get facts where entity is object
350
+ object_facts = await self.retrieve_by_object(current)
351
+ for fact in object_facts:
352
+ related.add(fact.subject)
353
+ if fact.subject not in explored:
354
+ to_explore.add(fact.subject)
355
+
356
+ return related - {entity}
357
+
358
+ async def get_entity_properties(
359
+ self,
360
+ entity: str,
361
+ ) -> Dict[str, Any]:
362
+ """Get all properties of an entity.
363
+
364
+ Args:
365
+ entity: Entity to get properties for
366
+
367
+ Returns:
368
+ Dictionary of properties
369
+ """
370
+ facts = await self.retrieve_by_subject(entity)
371
+
372
+ properties = {}
373
+ for fact in facts:
374
+ if fact.predicate not in properties:
375
+ properties[fact.predicate] = []
376
+ properties[fact.predicate].append({
377
+ "value": fact.object,
378
+ "confidence": fact.confidence,
379
+ "source": fact.source,
380
+ })
381
+
382
+ return properties
383
+
384
+ async def delete_fact(self, fact_id: str) -> bool:
385
+ """Delete a fact by ID.
386
+
387
+ Args:
388
+ fact_id: Fact ID
389
+
390
+ Returns:
391
+ True if deleted, False if not found
392
+ """
393
+ if fact_id not in self._facts:
394
+ return False
395
+
396
+ fact = self._facts[fact_id]
397
+
398
+ # Remove from indexes
399
+ self._subject_index[fact.subject].discard(fact_id)
400
+ self._predicate_index[fact.predicate].discard(fact_id)
401
+ self._object_index[fact.object].discard(fact_id)
402
+
403
+ # Remove fact
404
+ del self._facts[fact_id]
405
+
406
+ self._persist()
407
+
408
+ logger.debug(f"Deleted fact: {fact}")
409
+ return True
410
+
411
+ async def clear(self) -> None:
412
+ """Clear all facts."""
413
+ self._facts.clear()
414
+ self._subject_index.clear()
415
+ self._predicate_index.clear()
416
+ self._object_index.clear()
417
+ logger.info("Cleared all facts")
418
+
419
+ self._persist()
420
+
421
+ async def get_stats(self) -> Dict[str, Any]:
422
+ """Get semantic memory statistics.
423
+
424
+ Returns:
425
+ Statistics dictionary
426
+ """
427
+ if not self._facts:
428
+ return {
429
+ "total_facts": 0,
430
+ "backend": "graph" if self._use_graph else "in-memory",
431
+ "persistence": bool(self._persistence and self._persistence.enabled),
432
+ }
433
+
434
+ facts = list(self._facts.values())
435
+
436
+ return {
437
+ "total_facts": len(facts),
438
+ "unique_subjects": len(self._subject_index),
439
+ "unique_predicates": len(self._predicate_index),
440
+ "unique_objects": len(self._object_index),
441
+ "avg_confidence": sum(f.confidence for f in facts) / len(facts),
442
+ "oldest_fact": min(f.timestamp for f in facts).isoformat(),
443
+ "newest_fact": max(f.timestamp for f in facts).isoformat(),
444
+ "backend": "graph" if self._use_graph else "in-memory",
445
+ "persistence": bool(self._persistence and self._persistence.enabled),
446
+ }
447
+
448
+ def _persist(self) -> None:
449
+ if not self._store:
450
+ return
451
+ self._store.save_list("semantic_memory.json", [fact.to_dict() for fact in self._facts.values()])
452
+
453
+ def _load_from_disk(self) -> None:
454
+ if not self._store:
455
+ return
456
+ data = self._store.load_list("semantic_memory.json")
457
+ if not data:
458
+ return
459
+ self._facts = {}
460
+ self._subject_index = {}
461
+ self._predicate_index = {}
462
+ self._object_index = {}
463
+ for item in data:
464
+ fact = Fact.from_dict(item)
465
+ self._facts[fact.id] = fact
466
+ self._subject_index.setdefault(fact.subject, set()).add(fact.id)
467
+ self._predicate_index.setdefault(fact.predicate, set()).add(fact.id)
468
+ self._object_index.setdefault(fact.object, set()).add(fact.id)
469
+
470
+ async def _find_exact_fact(
471
+ self,
472
+ subject: str,
473
+ predicate: str,
474
+ object: str,
475
+ ) -> Optional[Fact]:
476
+ """Find exact matching fact."""
477
+ for fact in self._facts.values():
478
+ if (fact.subject == subject and
479
+ fact.predicate == predicate and
480
+ fact.object == object):
481
+ return fact
482
+ return None
483
+
484
+ async def _store_in_graph(self, fact: Fact) -> None:
485
+ """Store fact in graph database (placeholder)."""
486
+ # TODO: Implement Neo4j storage
487
+ logger.warning("Graph database storage not yet implemented")
488
+ # Fallback to in-memory
489
+ self._facts[fact.id] = fact
490
+
491
+ async def _retrieve_by_subject_from_graph(
492
+ self,
493
+ subject: str,
494
+ predicate: Optional[str],
495
+ ) -> List[Fact]:
496
+ """Retrieve from graph database (placeholder)."""
497
+ # TODO: Implement Neo4j query
498
+ return await self.retrieve_by_subject(subject, predicate)
499
+
500
+ async def _retrieve_by_predicate_from_graph(
501
+ self,
502
+ predicate: str,
503
+ subject: Optional[str],
504
+ object: Optional[str],
505
+ ) -> List[Fact]:
506
+ """Retrieve from graph database (placeholder)."""
507
+ # TODO: Implement Neo4j query
508
+ return await self.retrieve_by_predicate(predicate, subject, object)
509
+
510
+ async def _retrieve_by_object_from_graph(
511
+ self,
512
+ object: str,
513
+ predicate: Optional[str],
514
+ ) -> List[Fact]:
515
+ """Retrieve from graph database (placeholder)."""
516
+ # TODO: Implement Neo4j query
517
+ return await self.retrieve_by_object(object, predicate)
518
+
519
+ def __len__(self) -> int:
520
+ """Get number of stored facts."""
521
+ return len(self._facts)
522
+
523
+ def __repr__(self) -> str:
524
+ """String representation."""
525
+ backend = "Graph" if self._use_graph else "In-Memory"
526
+ return f"SemanticMemory(backend={backend}, facts={len(self._facts)})"
@@ -0,0 +1,62 @@
1
+ """Shared memory bus for cross-agent collaboration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from datetime import datetime
7
+ from typing import Any, Dict, List, Optional, Callable, Awaitable
8
+ import asyncio
9
+ import logging
10
+
11
+ from genxai.security.rbac import get_current_user, Permission
12
+ from genxai.security.policy_engine import get_policy_engine
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ @dataclass
18
+ class SharedMemoryEntry:
19
+ key: str
20
+ value: Any
21
+ updated_at: datetime = field(default_factory=datetime.utcnow)
22
+ metadata: Dict[str, Any] = field(default_factory=dict)
23
+
24
+
25
+ class SharedMemoryBus:
26
+ """In-memory shared memory store with pub/sub hooks."""
27
+
28
+ def __init__(self) -> None:
29
+ self._store: Dict[str, SharedMemoryEntry] = {}
30
+ self._subscribers: Dict[str, List[Callable[[SharedMemoryEntry], Awaitable[None]]]] = {}
31
+ self._lock = asyncio.Lock()
32
+
33
+ async def set(self, key: str, value: Any, metadata: Optional[Dict[str, Any]] = None) -> None:
34
+ user = get_current_user()
35
+ if user is not None:
36
+ get_policy_engine().check(user, f"memory:{key}", Permission.MEMORY_WRITE)
37
+ async with self._lock:
38
+ entry = SharedMemoryEntry(key=key, value=value, metadata=metadata or {})
39
+ self._store[key] = entry
40
+ await self._notify(key, entry)
41
+
42
+ def get(self, key: str, default: Any = None) -> Any:
43
+ user = get_current_user()
44
+ if user is not None:
45
+ get_policy_engine().check(user, f"memory:{key}", Permission.MEMORY_READ)
46
+ entry = self._store.get(key)
47
+ return entry.value if entry else default
48
+
49
+ def list_keys(self) -> List[str]:
50
+ return list(self._store.keys())
51
+
52
+ def subscribe(
53
+ self, key: str, callback: Callable[[SharedMemoryEntry], Awaitable[None]]
54
+ ) -> None:
55
+ self._subscribers.setdefault(key, []).append(callback)
56
+
57
+ async def _notify(self, key: str, entry: SharedMemoryEntry) -> None:
58
+ for callback in self._subscribers.get(key, []):
59
+ try:
60
+ await callback(entry)
61
+ except Exception as exc:
62
+ logger.error("Shared memory notify error for %s: %s", key, exc)