alma-memory 0.4.0__py3-none-any.whl → 0.5.1__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 (94) hide show
  1. alma/__init__.py +121 -45
  2. alma/confidence/__init__.py +1 -1
  3. alma/confidence/engine.py +92 -58
  4. alma/confidence/types.py +34 -14
  5. alma/config/loader.py +3 -2
  6. alma/consolidation/__init__.py +23 -0
  7. alma/consolidation/engine.py +678 -0
  8. alma/consolidation/prompts.py +84 -0
  9. alma/core.py +136 -28
  10. alma/domains/__init__.py +6 -6
  11. alma/domains/factory.py +12 -9
  12. alma/domains/schemas.py +17 -3
  13. alma/domains/types.py +8 -4
  14. alma/events/__init__.py +75 -0
  15. alma/events/emitter.py +284 -0
  16. alma/events/storage_mixin.py +246 -0
  17. alma/events/types.py +126 -0
  18. alma/events/webhook.py +425 -0
  19. alma/exceptions.py +49 -0
  20. alma/extraction/__init__.py +31 -0
  21. alma/extraction/auto_learner.py +265 -0
  22. alma/extraction/extractor.py +420 -0
  23. alma/graph/__init__.py +106 -0
  24. alma/graph/backends/__init__.py +32 -0
  25. alma/graph/backends/kuzu.py +624 -0
  26. alma/graph/backends/memgraph.py +432 -0
  27. alma/graph/backends/memory.py +236 -0
  28. alma/graph/backends/neo4j.py +417 -0
  29. alma/graph/base.py +159 -0
  30. alma/graph/extraction.py +198 -0
  31. alma/graph/store.py +860 -0
  32. alma/harness/__init__.py +4 -4
  33. alma/harness/base.py +18 -9
  34. alma/harness/domains.py +27 -11
  35. alma/initializer/__init__.py +1 -1
  36. alma/initializer/initializer.py +51 -43
  37. alma/initializer/types.py +25 -17
  38. alma/integration/__init__.py +9 -9
  39. alma/integration/claude_agents.py +32 -20
  40. alma/integration/helena.py +32 -22
  41. alma/integration/victor.py +57 -33
  42. alma/learning/__init__.py +27 -27
  43. alma/learning/forgetting.py +198 -148
  44. alma/learning/heuristic_extractor.py +40 -24
  45. alma/learning/protocols.py +65 -17
  46. alma/learning/validation.py +7 -2
  47. alma/mcp/__init__.py +4 -4
  48. alma/mcp/__main__.py +2 -1
  49. alma/mcp/resources.py +17 -16
  50. alma/mcp/server.py +102 -44
  51. alma/mcp/tools.py +180 -45
  52. alma/observability/__init__.py +84 -0
  53. alma/observability/config.py +302 -0
  54. alma/observability/logging.py +424 -0
  55. alma/observability/metrics.py +583 -0
  56. alma/observability/tracing.py +440 -0
  57. alma/progress/__init__.py +3 -3
  58. alma/progress/tracker.py +26 -20
  59. alma/progress/types.py +8 -12
  60. alma/py.typed +0 -0
  61. alma/retrieval/__init__.py +11 -11
  62. alma/retrieval/cache.py +20 -21
  63. alma/retrieval/embeddings.py +4 -4
  64. alma/retrieval/engine.py +179 -39
  65. alma/retrieval/scoring.py +73 -63
  66. alma/session/__init__.py +2 -2
  67. alma/session/manager.py +5 -5
  68. alma/session/types.py +5 -4
  69. alma/storage/__init__.py +70 -0
  70. alma/storage/azure_cosmos.py +414 -133
  71. alma/storage/base.py +215 -4
  72. alma/storage/chroma.py +1443 -0
  73. alma/storage/constants.py +103 -0
  74. alma/storage/file_based.py +59 -28
  75. alma/storage/migrations/__init__.py +21 -0
  76. alma/storage/migrations/base.py +321 -0
  77. alma/storage/migrations/runner.py +323 -0
  78. alma/storage/migrations/version_stores.py +337 -0
  79. alma/storage/migrations/versions/__init__.py +11 -0
  80. alma/storage/migrations/versions/v1_0_0.py +373 -0
  81. alma/storage/pinecone.py +1080 -0
  82. alma/storage/postgresql.py +1559 -0
  83. alma/storage/qdrant.py +1306 -0
  84. alma/storage/sqlite_local.py +504 -60
  85. alma/testing/__init__.py +46 -0
  86. alma/testing/factories.py +301 -0
  87. alma/testing/mocks.py +389 -0
  88. alma/types.py +62 -14
  89. alma_memory-0.5.1.dist-info/METADATA +939 -0
  90. alma_memory-0.5.1.dist-info/RECORD +93 -0
  91. {alma_memory-0.4.0.dist-info → alma_memory-0.5.1.dist-info}/WHEEL +1 -1
  92. alma_memory-0.4.0.dist-info/METADATA +0 -488
  93. alma_memory-0.4.0.dist-info/RECORD +0 -52
  94. {alma_memory-0.4.0.dist-info → alma_memory-0.5.1.dist-info}/top_level.txt +0 -0
alma/events/emitter.py ADDED
@@ -0,0 +1,284 @@
1
+ """
2
+ ALMA Event Emitter.
3
+
4
+ Provides a pub/sub mechanism for memory events, allowing components
5
+ and external systems to subscribe to and receive notifications about
6
+ memory changes.
7
+ """
8
+
9
+ import asyncio
10
+ import logging
11
+ from concurrent.futures import ThreadPoolExecutor
12
+ from typing import Awaitable, Callable, Dict, List, Optional, Union
13
+
14
+ from alma.events.types import MemoryEvent, MemoryEventType
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # Type aliases for callbacks
19
+ SyncCallback = Callable[[MemoryEvent], None]
20
+ AsyncCallback = Callable[[MemoryEvent], Awaitable[None]]
21
+ EventCallback = Union[SyncCallback, AsyncCallback]
22
+
23
+
24
+ class EventEmitter:
25
+ """
26
+ Event emitter for memory system events.
27
+
28
+ Supports both synchronous and asynchronous callbacks, with options
29
+ to subscribe to specific event types or all events.
30
+
31
+ The emitter is designed to be non-blocking - callbacks are executed
32
+ in a way that doesn't slow down the main storage operations.
33
+
34
+ Example:
35
+ ```python
36
+ emitter = EventEmitter()
37
+
38
+ def on_created(event: MemoryEvent):
39
+ print(f"Memory created: {event.memory_id}")
40
+
41
+ emitter.subscribe(MemoryEventType.CREATED, on_created)
42
+ emitter.emit(event)
43
+ ```
44
+ """
45
+
46
+ def __init__(self, max_workers: int = 4):
47
+ """
48
+ Initialize the event emitter.
49
+
50
+ Args:
51
+ max_workers: Maximum number of worker threads for async callback execution
52
+ """
53
+ self._subscribers: Dict[MemoryEventType, List[EventCallback]] = {}
54
+ self._global_subscribers: List[EventCallback] = []
55
+ self._executor = ThreadPoolExecutor(max_workers=max_workers)
56
+ self._enabled = True
57
+
58
+ def subscribe(
59
+ self,
60
+ event_type: MemoryEventType,
61
+ callback: EventCallback,
62
+ ) -> None:
63
+ """
64
+ Subscribe to a specific event type.
65
+
66
+ Args:
67
+ event_type: The type of event to subscribe to
68
+ callback: Function to call when event occurs (sync or async)
69
+ """
70
+ if event_type not in self._subscribers:
71
+ self._subscribers[event_type] = []
72
+
73
+ if callback not in self._subscribers[event_type]:
74
+ self._subscribers[event_type].append(callback)
75
+ callback_name = getattr(callback, "__name__", repr(callback))
76
+ logger.debug(f"Subscribed to {event_type.value}: {callback_name}")
77
+
78
+ def subscribe_all(self, callback: EventCallback) -> None:
79
+ """
80
+ Subscribe to all events.
81
+
82
+ Args:
83
+ callback: Function to call for any event
84
+ """
85
+ if callback not in self._global_subscribers:
86
+ self._global_subscribers.append(callback)
87
+ callback_name = getattr(callback, "__name__", repr(callback))
88
+ logger.debug(f"Subscribed to all events: {callback_name}")
89
+
90
+ def unsubscribe(
91
+ self,
92
+ event_type: MemoryEventType,
93
+ callback: EventCallback,
94
+ ) -> bool:
95
+ """
96
+ Unsubscribe from a specific event type.
97
+
98
+ Args:
99
+ event_type: The event type to unsubscribe from
100
+ callback: The callback to remove
101
+
102
+ Returns:
103
+ True if callback was removed, False if not found
104
+ """
105
+ if event_type in self._subscribers:
106
+ try:
107
+ self._subscribers[event_type].remove(callback)
108
+ callback_name = getattr(callback, "__name__", repr(callback))
109
+ logger.debug(f"Unsubscribed from {event_type.value}: {callback_name}")
110
+ return True
111
+ except ValueError:
112
+ pass
113
+ return False
114
+
115
+ def unsubscribe_all(self, callback: EventCallback) -> bool:
116
+ """
117
+ Unsubscribe a callback from all events.
118
+
119
+ Args:
120
+ callback: The callback to remove
121
+
122
+ Returns:
123
+ True if callback was removed, False if not found
124
+ """
125
+ try:
126
+ self._global_subscribers.remove(callback)
127
+ callback_name = getattr(callback, "__name__", repr(callback))
128
+ logger.debug(f"Unsubscribed from all events: {callback_name}")
129
+ return True
130
+ except ValueError:
131
+ return False
132
+
133
+ def has_subscribers(self, event_type: Optional[MemoryEventType] = None) -> bool:
134
+ """
135
+ Check if there are any subscribers.
136
+
137
+ Args:
138
+ event_type: Optional specific event type to check
139
+
140
+ Returns:
141
+ True if there are subscribers
142
+ """
143
+ if event_type is None:
144
+ return bool(self._global_subscribers) or any(
145
+ bool(subs) for subs in self._subscribers.values()
146
+ )
147
+ return bool(self._subscribers.get(event_type)) or bool(self._global_subscribers)
148
+
149
+ def emit(self, event: MemoryEvent) -> None:
150
+ """
151
+ Emit an event to all matching subscribers (non-blocking).
152
+
153
+ Callbacks are executed in a thread pool to avoid blocking
154
+ the main thread. Any exceptions in callbacks are logged
155
+ but do not propagate.
156
+
157
+ Args:
158
+ event: The event to emit
159
+ """
160
+ if not self._enabled:
161
+ return
162
+
163
+ callbacks = self._get_callbacks(event.event_type)
164
+ if not callbacks:
165
+ return
166
+
167
+ # Execute callbacks in thread pool (non-blocking)
168
+ for callback in callbacks:
169
+ self._executor.submit(self._safe_call, callback, event)
170
+
171
+ async def emit_async(self, event: MemoryEvent) -> None:
172
+ """
173
+ Emit an event to all matching subscribers asynchronously.
174
+
175
+ For async callbacks, awaits them directly. For sync callbacks,
176
+ runs them in the executor.
177
+
178
+ Args:
179
+ event: The event to emit
180
+ """
181
+ if not self._enabled:
182
+ return
183
+
184
+ callbacks = self._get_callbacks(event.event_type)
185
+ if not callbacks:
186
+ return
187
+
188
+ tasks = []
189
+ for callback in callbacks:
190
+ if asyncio.iscoroutinefunction(callback):
191
+ tasks.append(self._safe_call_async(callback, event))
192
+ else:
193
+ # Run sync callbacks in executor
194
+ loop = asyncio.get_event_loop()
195
+ tasks.append(
196
+ loop.run_in_executor(
197
+ self._executor,
198
+ self._safe_call,
199
+ callback,
200
+ event,
201
+ )
202
+ )
203
+
204
+ if tasks:
205
+ await asyncio.gather(*tasks, return_exceptions=True)
206
+
207
+ def _get_callbacks(self, event_type: MemoryEventType) -> List[EventCallback]:
208
+ """Get all callbacks for an event type."""
209
+ callbacks = list(self._global_subscribers)
210
+ callbacks.extend(self._subscribers.get(event_type, []))
211
+ return callbacks
212
+
213
+ def _safe_call(self, callback: SyncCallback, event: MemoryEvent) -> None:
214
+ """Safely call a sync callback, catching exceptions."""
215
+ try:
216
+ callback(event)
217
+ except Exception as e:
218
+ callback_name = getattr(callback, "__name__", repr(callback))
219
+ logger.error(
220
+ f"Error in event callback {callback_name}: {e}",
221
+ exc_info=True,
222
+ )
223
+
224
+ async def _safe_call_async(
225
+ self,
226
+ callback: AsyncCallback,
227
+ event: MemoryEvent,
228
+ ) -> None:
229
+ """Safely call an async callback, catching exceptions."""
230
+ try:
231
+ await callback(event)
232
+ except Exception as e:
233
+ callback_name = getattr(callback, "__name__", repr(callback))
234
+ logger.error(
235
+ f"Error in async event callback {callback_name}: {e}",
236
+ exc_info=True,
237
+ )
238
+
239
+ def enable(self) -> None:
240
+ """Enable event emission."""
241
+ self._enabled = True
242
+
243
+ def disable(self) -> None:
244
+ """Disable event emission (events will be silently dropped)."""
245
+ self._enabled = False
246
+
247
+ def clear(self) -> None:
248
+ """Remove all subscribers."""
249
+ self._subscribers.clear()
250
+ self._global_subscribers.clear()
251
+
252
+ def shutdown(self) -> None:
253
+ """Shutdown the executor and clear subscribers."""
254
+ self.clear()
255
+ self._executor.shutdown(wait=False)
256
+
257
+
258
+ # Global emitter instance (singleton pattern)
259
+ _emitter: Optional[EventEmitter] = None
260
+
261
+
262
+ def get_emitter() -> EventEmitter:
263
+ """
264
+ Get the global event emitter instance.
265
+
266
+ Returns:
267
+ The singleton EventEmitter instance
268
+ """
269
+ global _emitter
270
+ if _emitter is None:
271
+ _emitter = EventEmitter()
272
+ return _emitter
273
+
274
+
275
+ def reset_emitter() -> None:
276
+ """
277
+ Reset the global emitter (mainly for testing).
278
+
279
+ Creates a fresh emitter instance, clearing all subscriptions.
280
+ """
281
+ global _emitter
282
+ if _emitter is not None:
283
+ _emitter.shutdown()
284
+ _emitter = EventEmitter()
@@ -0,0 +1,246 @@
1
+ """
2
+ ALMA Event-Aware Storage Mixin.
3
+
4
+ Provides event emission capabilities for storage backends.
5
+ This is a mixin class that can be used by any storage implementation
6
+ to emit events when memory operations occur.
7
+ """
8
+
9
+ import logging
10
+ from dataclasses import asdict
11
+ from typing import Any, Dict, Optional
12
+
13
+ from alma.events.emitter import EventEmitter, get_emitter
14
+ from alma.events.types import MemoryEventType, create_memory_event
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class EventAwareStorageMixin:
20
+ """
21
+ Mixin that adds event emission to storage backends.
22
+
23
+ This mixin provides helper methods to emit events for various
24
+ storage operations. Events are only emitted if there are subscribers,
25
+ making the overhead negligible when events are not used.
26
+
27
+ Usage:
28
+ class MyStorage(StorageBackend, EventAwareStorageMixin):
29
+ def save_heuristic(self, heuristic):
30
+ result_id = super().save_heuristic(heuristic)
31
+ self._emit_memory_event(
32
+ event_type=MemoryEventType.CREATED,
33
+ agent=heuristic.agent,
34
+ project_id=heuristic.project_id,
35
+ memory_type="heuristics",
36
+ memory_id=result_id,
37
+ payload=self._heuristic_to_dict(heuristic),
38
+ )
39
+ return result_id
40
+ """
41
+
42
+ _events_enabled: bool = True
43
+ _custom_emitter: Optional[EventEmitter] = None
44
+
45
+ def enable_events(self) -> None:
46
+ """Enable event emission for this storage instance."""
47
+ self._events_enabled = True
48
+
49
+ def disable_events(self) -> None:
50
+ """Disable event emission for this storage instance."""
51
+ self._events_enabled = False
52
+
53
+ def set_emitter(self, emitter: EventEmitter) -> None:
54
+ """
55
+ Set a custom event emitter for this storage instance.
56
+
57
+ Args:
58
+ emitter: The event emitter to use
59
+ """
60
+ self._custom_emitter = emitter
61
+
62
+ def _get_emitter(self) -> EventEmitter:
63
+ """Get the event emitter to use."""
64
+ if self._custom_emitter is not None:
65
+ return self._custom_emitter
66
+ return get_emitter()
67
+
68
+ def _should_emit(self, event_type: MemoryEventType) -> bool:
69
+ """
70
+ Check if events should be emitted.
71
+
72
+ Returns False if:
73
+ - Events are disabled for this storage instance
74
+ - There are no subscribers for this event type
75
+
76
+ This optimization ensures event creation overhead is avoided
77
+ when no one is listening.
78
+
79
+ Args:
80
+ event_type: The type of event being considered
81
+
82
+ Returns:
83
+ True if an event should be emitted
84
+ """
85
+ if not self._events_enabled:
86
+ return False
87
+
88
+ emitter = self._get_emitter()
89
+ return emitter.has_subscribers(event_type)
90
+
91
+ def _emit_memory_event(
92
+ self,
93
+ event_type: MemoryEventType,
94
+ agent: str,
95
+ project_id: str,
96
+ memory_type: str,
97
+ memory_id: str,
98
+ payload: Dict[str, Any],
99
+ metadata: Optional[Dict[str, Any]] = None,
100
+ ) -> None:
101
+ """
102
+ Emit a memory event if there are subscribers.
103
+
104
+ This is a convenience method that checks for subscribers before
105
+ creating and emitting the event.
106
+
107
+ Args:
108
+ event_type: Type of event
109
+ agent: Agent name
110
+ project_id: Project identifier
111
+ memory_type: Type of memory
112
+ memory_id: Memory identifier
113
+ payload: Event-specific data
114
+ metadata: Optional additional context
115
+ """
116
+ if not self._should_emit(event_type):
117
+ return
118
+
119
+ event = create_memory_event(
120
+ event_type=event_type,
121
+ agent=agent,
122
+ project_id=project_id,
123
+ memory_type=memory_type,
124
+ memory_id=memory_id,
125
+ payload=payload,
126
+ metadata=metadata,
127
+ )
128
+
129
+ emitter = self._get_emitter()
130
+ emitter.emit(event)
131
+
132
+ logger.debug(f"Emitted {event_type.value} for {memory_type}/{memory_id}")
133
+
134
+ def _emit_created_event(
135
+ self,
136
+ agent: str,
137
+ project_id: str,
138
+ memory_type: str,
139
+ memory_id: str,
140
+ payload: Dict[str, Any],
141
+ ) -> None:
142
+ """Emit a CREATED event."""
143
+ self._emit_memory_event(
144
+ event_type=MemoryEventType.CREATED,
145
+ agent=agent,
146
+ project_id=project_id,
147
+ memory_type=memory_type,
148
+ memory_id=memory_id,
149
+ payload=payload,
150
+ )
151
+
152
+ def _emit_updated_event(
153
+ self,
154
+ agent: str,
155
+ project_id: str,
156
+ memory_type: str,
157
+ memory_id: str,
158
+ payload: Dict[str, Any],
159
+ ) -> None:
160
+ """Emit an UPDATED event."""
161
+ self._emit_memory_event(
162
+ event_type=MemoryEventType.UPDATED,
163
+ agent=agent,
164
+ project_id=project_id,
165
+ memory_type=memory_type,
166
+ memory_id=memory_id,
167
+ payload=payload,
168
+ )
169
+
170
+ def _emit_deleted_event(
171
+ self,
172
+ agent: str,
173
+ project_id: str,
174
+ memory_type: str,
175
+ memory_id: str,
176
+ ) -> None:
177
+ """Emit a DELETED event."""
178
+ self._emit_memory_event(
179
+ event_type=MemoryEventType.DELETED,
180
+ agent=agent,
181
+ project_id=project_id,
182
+ memory_type=memory_type,
183
+ memory_id=memory_id,
184
+ payload={"deleted_id": memory_id},
185
+ )
186
+
187
+
188
+ def emit_on_save(
189
+ memory_type: str, event_type: MemoryEventType = MemoryEventType.CREATED
190
+ ):
191
+ """
192
+ Decorator to emit events when save methods are called.
193
+
194
+ This decorator can be used on storage methods that save memories.
195
+ It will emit an event after the save completes successfully.
196
+
197
+ Args:
198
+ memory_type: The type of memory being saved (e.g., "heuristics")
199
+ event_type: The event type to emit (default: CREATED)
200
+
201
+ Example:
202
+ @emit_on_save("heuristics")
203
+ def save_heuristic(self, heuristic: Heuristic) -> str:
204
+ # Original implementation
205
+ ...
206
+ """
207
+
208
+ def decorator(func):
209
+ def wrapper(self, memory_item):
210
+ # Call the original method
211
+ result_id = func(self, memory_item)
212
+
213
+ # Emit event if storage supports events
214
+ if hasattr(self, "_emit_memory_event") and hasattr(self, "_should_emit"):
215
+ if self._should_emit(event_type):
216
+ # Extract common fields
217
+ agent = getattr(memory_item, "agent", "unknown")
218
+ project_id = getattr(memory_item, "project_id", "unknown")
219
+
220
+ # Convert to dict, handling dataclasses
221
+ try:
222
+ if hasattr(memory_item, "__dataclass_fields__"):
223
+ payload = {
224
+ k: v
225
+ for k, v in asdict(memory_item).items()
226
+ if k != "embedding" # Exclude large embedding vectors
227
+ }
228
+ else:
229
+ payload = {"id": result_id}
230
+ except Exception:
231
+ payload = {"id": result_id}
232
+
233
+ self._emit_memory_event(
234
+ event_type=event_type,
235
+ agent=agent,
236
+ project_id=project_id,
237
+ memory_type=memory_type,
238
+ memory_id=result_id,
239
+ payload=payload,
240
+ )
241
+
242
+ return result_id
243
+
244
+ return wrapper
245
+
246
+ return decorator
alma/events/types.py ADDED
@@ -0,0 +1,126 @@
1
+ """
2
+ ALMA Event Types.
3
+
4
+ Defines event types and the MemoryEvent dataclass for the event system.
5
+ """
6
+
7
+ from dataclasses import dataclass, field
8
+ from datetime import datetime, timezone
9
+ from enum import Enum
10
+ from typing import Any, Dict, Optional
11
+
12
+
13
+ class MemoryEventType(Enum):
14
+ """Types of events that can be emitted by the memory system."""
15
+
16
+ # Core memory operations
17
+ CREATED = "memory.created"
18
+ UPDATED = "memory.updated"
19
+ DELETED = "memory.deleted"
20
+ CONSOLIDATED = "memory.consolidated"
21
+
22
+ # Learning-specific events
23
+ HEURISTIC_FORMED = "heuristic.formed"
24
+ ANTIPATTERN_DETECTED = "antipattern.detected"
25
+ PREFERENCE_ADDED = "preference.added"
26
+ KNOWLEDGE_ADDED = "knowledge.added"
27
+
28
+ # Confidence events
29
+ CONFIDENCE_UPDATED = "confidence.updated"
30
+ CONFIDENCE_DECAYED = "confidence.decayed"
31
+
32
+
33
+ @dataclass
34
+ class MemoryEvent:
35
+ """
36
+ Represents an event in the memory system.
37
+
38
+ Events are emitted when memory operations occur, allowing external
39
+ systems to react to changes through callbacks or webhooks.
40
+
41
+ Attributes:
42
+ event_type: The type of event that occurred
43
+ agent: Name of the agent associated with the event
44
+ project_id: Project identifier
45
+ memory_type: Type of memory (heuristics, outcomes, etc.)
46
+ memory_id: Unique identifier of the affected memory
47
+ timestamp: When the event occurred
48
+ payload: Event-specific data (e.g., the created memory)
49
+ metadata: Optional additional context
50
+ """
51
+
52
+ event_type: MemoryEventType
53
+ agent: str
54
+ project_id: str
55
+ memory_type: (
56
+ str # heuristics, outcomes, preferences, domain_knowledge, anti_patterns
57
+ )
58
+ memory_id: str
59
+ timestamp: datetime
60
+ payload: Dict[str, Any]
61
+ metadata: Optional[Dict[str, Any]] = field(default_factory=dict)
62
+
63
+ def to_dict(self) -> Dict[str, Any]:
64
+ """Convert event to dictionary for serialization."""
65
+ return {
66
+ "event_type": self.event_type.value,
67
+ "agent": self.agent,
68
+ "project_id": self.project_id,
69
+ "memory_type": self.memory_type,
70
+ "memory_id": self.memory_id,
71
+ "timestamp": self.timestamp.isoformat(),
72
+ "payload": self.payload,
73
+ "metadata": self.metadata or {},
74
+ }
75
+
76
+ @classmethod
77
+ def from_dict(cls, data: Dict[str, Any]) -> "MemoryEvent":
78
+ """Create event from dictionary."""
79
+ return cls(
80
+ event_type=MemoryEventType(data["event_type"]),
81
+ agent=data["agent"],
82
+ project_id=data["project_id"],
83
+ memory_type=data["memory_type"],
84
+ memory_id=data["memory_id"],
85
+ timestamp=datetime.fromisoformat(data["timestamp"]),
86
+ payload=data["payload"],
87
+ metadata=data.get("metadata"),
88
+ )
89
+
90
+
91
+ def create_memory_event(
92
+ event_type: MemoryEventType,
93
+ agent: str,
94
+ project_id: str,
95
+ memory_type: str,
96
+ memory_id: str,
97
+ payload: Dict[str, Any],
98
+ metadata: Optional[Dict[str, Any]] = None,
99
+ *,
100
+ default_metadata: bool = True,
101
+ ) -> MemoryEvent:
102
+ """
103
+ Factory function to create a MemoryEvent with current timestamp.
104
+
105
+ Args:
106
+ event_type: Type of event
107
+ agent: Agent name
108
+ project_id: Project identifier
109
+ memory_type: Type of memory
110
+ memory_id: Memory identifier
111
+ payload: Event-specific data
112
+ metadata: Optional additional context
113
+
114
+ Returns:
115
+ A new MemoryEvent instance
116
+ """
117
+ return MemoryEvent(
118
+ event_type=event_type,
119
+ agent=agent,
120
+ project_id=project_id,
121
+ memory_type=memory_type,
122
+ memory_id=memory_id,
123
+ timestamp=datetime.now(timezone.utc),
124
+ payload=payload,
125
+ metadata=metadata if metadata is not None else {},
126
+ )