alma-memory 0.5.0__py3-none-any.whl → 0.7.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 (111) hide show
  1. alma/__init__.py +296 -194
  2. alma/compression/__init__.py +33 -0
  3. alma/compression/pipeline.py +980 -0
  4. alma/confidence/__init__.py +47 -47
  5. alma/confidence/engine.py +540 -540
  6. alma/confidence/types.py +351 -351
  7. alma/config/loader.py +157 -157
  8. alma/consolidation/__init__.py +23 -23
  9. alma/consolidation/engine.py +678 -678
  10. alma/consolidation/prompts.py +84 -84
  11. alma/core.py +1189 -322
  12. alma/domains/__init__.py +30 -30
  13. alma/domains/factory.py +359 -359
  14. alma/domains/schemas.py +448 -448
  15. alma/domains/types.py +272 -272
  16. alma/events/__init__.py +75 -75
  17. alma/events/emitter.py +285 -284
  18. alma/events/storage_mixin.py +246 -246
  19. alma/events/types.py +126 -126
  20. alma/events/webhook.py +425 -425
  21. alma/exceptions.py +49 -49
  22. alma/extraction/__init__.py +31 -31
  23. alma/extraction/auto_learner.py +265 -264
  24. alma/extraction/extractor.py +420 -420
  25. alma/graph/__init__.py +106 -81
  26. alma/graph/backends/__init__.py +32 -18
  27. alma/graph/backends/kuzu.py +624 -0
  28. alma/graph/backends/memgraph.py +432 -0
  29. alma/graph/backends/memory.py +236 -236
  30. alma/graph/backends/neo4j.py +417 -417
  31. alma/graph/base.py +159 -159
  32. alma/graph/extraction.py +198 -198
  33. alma/graph/store.py +860 -860
  34. alma/harness/__init__.py +35 -35
  35. alma/harness/base.py +386 -386
  36. alma/harness/domains.py +705 -705
  37. alma/initializer/__init__.py +37 -37
  38. alma/initializer/initializer.py +418 -418
  39. alma/initializer/types.py +250 -250
  40. alma/integration/__init__.py +62 -62
  41. alma/integration/claude_agents.py +444 -432
  42. alma/integration/helena.py +423 -423
  43. alma/integration/victor.py +471 -471
  44. alma/learning/__init__.py +101 -86
  45. alma/learning/decay.py +878 -0
  46. alma/learning/forgetting.py +1446 -1446
  47. alma/learning/heuristic_extractor.py +390 -390
  48. alma/learning/protocols.py +374 -374
  49. alma/learning/validation.py +346 -346
  50. alma/mcp/__init__.py +123 -45
  51. alma/mcp/__main__.py +156 -156
  52. alma/mcp/resources.py +122 -122
  53. alma/mcp/server.py +955 -591
  54. alma/mcp/tools.py +3254 -511
  55. alma/observability/__init__.py +91 -0
  56. alma/observability/config.py +302 -0
  57. alma/observability/guidelines.py +170 -0
  58. alma/observability/logging.py +424 -0
  59. alma/observability/metrics.py +583 -0
  60. alma/observability/tracing.py +440 -0
  61. alma/progress/__init__.py +21 -21
  62. alma/progress/tracker.py +607 -607
  63. alma/progress/types.py +250 -250
  64. alma/retrieval/__init__.py +134 -53
  65. alma/retrieval/budget.py +525 -0
  66. alma/retrieval/cache.py +1304 -1061
  67. alma/retrieval/embeddings.py +202 -202
  68. alma/retrieval/engine.py +850 -366
  69. alma/retrieval/modes.py +365 -0
  70. alma/retrieval/progressive.py +560 -0
  71. alma/retrieval/scoring.py +344 -344
  72. alma/retrieval/trust_scoring.py +637 -0
  73. alma/retrieval/verification.py +797 -0
  74. alma/session/__init__.py +19 -19
  75. alma/session/manager.py +442 -399
  76. alma/session/types.py +288 -288
  77. alma/storage/__init__.py +101 -61
  78. alma/storage/archive.py +233 -0
  79. alma/storage/azure_cosmos.py +1259 -1048
  80. alma/storage/base.py +1083 -525
  81. alma/storage/chroma.py +1443 -1443
  82. alma/storage/constants.py +103 -0
  83. alma/storage/file_based.py +614 -619
  84. alma/storage/migrations/__init__.py +21 -0
  85. alma/storage/migrations/base.py +321 -0
  86. alma/storage/migrations/runner.py +323 -0
  87. alma/storage/migrations/version_stores.py +337 -0
  88. alma/storage/migrations/versions/__init__.py +11 -0
  89. alma/storage/migrations/versions/v1_0_0.py +373 -0
  90. alma/storage/migrations/versions/v1_1_0_workflow_context.py +551 -0
  91. alma/storage/pinecone.py +1080 -1080
  92. alma/storage/postgresql.py +1948 -1452
  93. alma/storage/qdrant.py +1306 -1306
  94. alma/storage/sqlite_local.py +3041 -1358
  95. alma/testing/__init__.py +46 -0
  96. alma/testing/factories.py +301 -0
  97. alma/testing/mocks.py +389 -0
  98. alma/types.py +292 -264
  99. alma/utils/__init__.py +19 -0
  100. alma/utils/tokenizer.py +521 -0
  101. alma/workflow/__init__.py +83 -0
  102. alma/workflow/artifacts.py +170 -0
  103. alma/workflow/checkpoint.py +311 -0
  104. alma/workflow/context.py +228 -0
  105. alma/workflow/outcomes.py +189 -0
  106. alma/workflow/reducers.py +393 -0
  107. {alma_memory-0.5.0.dist-info → alma_memory-0.7.0.dist-info}/METADATA +244 -72
  108. alma_memory-0.7.0.dist-info/RECORD +112 -0
  109. alma_memory-0.5.0.dist-info/RECORD +0 -76
  110. {alma_memory-0.5.0.dist-info → alma_memory-0.7.0.dist-info}/WHEEL +0 -0
  111. {alma_memory-0.5.0.dist-info → alma_memory-0.7.0.dist-info}/top_level.txt +0 -0
alma/events/emitter.py CHANGED
@@ -1,284 +1,285 @@
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()
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 inspect
11
+ import logging
12
+ from concurrent.futures import ThreadPoolExecutor
13
+ from typing import Awaitable, Callable, Dict, List, Optional, Union
14
+
15
+ from alma.events.types import MemoryEvent, MemoryEventType
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ # Type aliases for callbacks
20
+ SyncCallback = Callable[[MemoryEvent], None]
21
+ AsyncCallback = Callable[[MemoryEvent], Awaitable[None]]
22
+ EventCallback = Union[SyncCallback, AsyncCallback]
23
+
24
+
25
+ class EventEmitter:
26
+ """
27
+ Event emitter for memory system events.
28
+
29
+ Supports both synchronous and asynchronous callbacks, with options
30
+ to subscribe to specific event types or all events.
31
+
32
+ The emitter is designed to be non-blocking - callbacks are executed
33
+ in a way that doesn't slow down the main storage operations.
34
+
35
+ Example:
36
+ ```python
37
+ emitter = EventEmitter()
38
+
39
+ def on_created(event: MemoryEvent):
40
+ print(f"Memory created: {event.memory_id}")
41
+
42
+ emitter.subscribe(MemoryEventType.CREATED, on_created)
43
+ emitter.emit(event)
44
+ ```
45
+ """
46
+
47
+ def __init__(self, max_workers: int = 4):
48
+ """
49
+ Initialize the event emitter.
50
+
51
+ Args:
52
+ max_workers: Maximum number of worker threads for async callback execution
53
+ """
54
+ self._subscribers: Dict[MemoryEventType, List[EventCallback]] = {}
55
+ self._global_subscribers: List[EventCallback] = []
56
+ self._executor = ThreadPoolExecutor(max_workers=max_workers)
57
+ self._enabled = True
58
+
59
+ def subscribe(
60
+ self,
61
+ event_type: MemoryEventType,
62
+ callback: EventCallback,
63
+ ) -> None:
64
+ """
65
+ Subscribe to a specific event type.
66
+
67
+ Args:
68
+ event_type: The type of event to subscribe to
69
+ callback: Function to call when event occurs (sync or async)
70
+ """
71
+ if event_type not in self._subscribers:
72
+ self._subscribers[event_type] = []
73
+
74
+ if callback not in self._subscribers[event_type]:
75
+ self._subscribers[event_type].append(callback)
76
+ callback_name = getattr(callback, "__name__", repr(callback))
77
+ logger.debug(f"Subscribed to {event_type.value}: {callback_name}")
78
+
79
+ def subscribe_all(self, callback: EventCallback) -> None:
80
+ """
81
+ Subscribe to all events.
82
+
83
+ Args:
84
+ callback: Function to call for any event
85
+ """
86
+ if callback not in self._global_subscribers:
87
+ self._global_subscribers.append(callback)
88
+ callback_name = getattr(callback, "__name__", repr(callback))
89
+ logger.debug(f"Subscribed to all events: {callback_name}")
90
+
91
+ def unsubscribe(
92
+ self,
93
+ event_type: MemoryEventType,
94
+ callback: EventCallback,
95
+ ) -> bool:
96
+ """
97
+ Unsubscribe from a specific event type.
98
+
99
+ Args:
100
+ event_type: The event type to unsubscribe from
101
+ callback: The callback to remove
102
+
103
+ Returns:
104
+ True if callback was removed, False if not found
105
+ """
106
+ if event_type in self._subscribers:
107
+ try:
108
+ self._subscribers[event_type].remove(callback)
109
+ callback_name = getattr(callback, "__name__", repr(callback))
110
+ logger.debug(f"Unsubscribed from {event_type.value}: {callback_name}")
111
+ return True
112
+ except ValueError:
113
+ pass
114
+ return False
115
+
116
+ def unsubscribe_all(self, callback: EventCallback) -> bool:
117
+ """
118
+ Unsubscribe a callback from all events.
119
+
120
+ Args:
121
+ callback: The callback to remove
122
+
123
+ Returns:
124
+ True if callback was removed, False if not found
125
+ """
126
+ try:
127
+ self._global_subscribers.remove(callback)
128
+ callback_name = getattr(callback, "__name__", repr(callback))
129
+ logger.debug(f"Unsubscribed from all events: {callback_name}")
130
+ return True
131
+ except ValueError:
132
+ return False
133
+
134
+ def has_subscribers(self, event_type: Optional[MemoryEventType] = None) -> bool:
135
+ """
136
+ Check if there are any subscribers.
137
+
138
+ Args:
139
+ event_type: Optional specific event type to check
140
+
141
+ Returns:
142
+ True if there are subscribers
143
+ """
144
+ if event_type is None:
145
+ return bool(self._global_subscribers) or any(
146
+ bool(subs) for subs in self._subscribers.values()
147
+ )
148
+ return bool(self._subscribers.get(event_type)) or bool(self._global_subscribers)
149
+
150
+ def emit(self, event: MemoryEvent) -> None:
151
+ """
152
+ Emit an event to all matching subscribers (non-blocking).
153
+
154
+ Callbacks are executed in a thread pool to avoid blocking
155
+ the main thread. Any exceptions in callbacks are logged
156
+ but do not propagate.
157
+
158
+ Args:
159
+ event: The event to emit
160
+ """
161
+ if not self._enabled:
162
+ return
163
+
164
+ callbacks = self._get_callbacks(event.event_type)
165
+ if not callbacks:
166
+ return
167
+
168
+ # Execute callbacks in thread pool (non-blocking)
169
+ for callback in callbacks:
170
+ self._executor.submit(self._safe_call, callback, event)
171
+
172
+ async def emit_async(self, event: MemoryEvent) -> None:
173
+ """
174
+ Emit an event to all matching subscribers asynchronously.
175
+
176
+ For async callbacks, awaits them directly. For sync callbacks,
177
+ runs them in the executor.
178
+
179
+ Args:
180
+ event: The event to emit
181
+ """
182
+ if not self._enabled:
183
+ return
184
+
185
+ callbacks = self._get_callbacks(event.event_type)
186
+ if not callbacks:
187
+ return
188
+
189
+ tasks = []
190
+ for callback in callbacks:
191
+ if inspect.iscoroutinefunction(callback):
192
+ tasks.append(self._safe_call_async(callback, event))
193
+ else:
194
+ # Run sync callbacks in executor
195
+ loop = asyncio.get_event_loop()
196
+ tasks.append(
197
+ loop.run_in_executor(
198
+ self._executor,
199
+ self._safe_call,
200
+ callback,
201
+ event,
202
+ )
203
+ )
204
+
205
+ if tasks:
206
+ await asyncio.gather(*tasks, return_exceptions=True)
207
+
208
+ def _get_callbacks(self, event_type: MemoryEventType) -> List[EventCallback]:
209
+ """Get all callbacks for an event type."""
210
+ callbacks = list(self._global_subscribers)
211
+ callbacks.extend(self._subscribers.get(event_type, []))
212
+ return callbacks
213
+
214
+ def _safe_call(self, callback: SyncCallback, event: MemoryEvent) -> None:
215
+ """Safely call a sync callback, catching exceptions."""
216
+ try:
217
+ callback(event)
218
+ except Exception as e:
219
+ callback_name = getattr(callback, "__name__", repr(callback))
220
+ logger.error(
221
+ f"Error in event callback {callback_name}: {e}",
222
+ exc_info=True,
223
+ )
224
+
225
+ async def _safe_call_async(
226
+ self,
227
+ callback: AsyncCallback,
228
+ event: MemoryEvent,
229
+ ) -> None:
230
+ """Safely call an async callback, catching exceptions."""
231
+ try:
232
+ await callback(event)
233
+ except Exception as e:
234
+ callback_name = getattr(callback, "__name__", repr(callback))
235
+ logger.error(
236
+ f"Error in async event callback {callback_name}: {e}",
237
+ exc_info=True,
238
+ )
239
+
240
+ def enable(self) -> None:
241
+ """Enable event emission."""
242
+ self._enabled = True
243
+
244
+ def disable(self) -> None:
245
+ """Disable event emission (events will be silently dropped)."""
246
+ self._enabled = False
247
+
248
+ def clear(self) -> None:
249
+ """Remove all subscribers."""
250
+ self._subscribers.clear()
251
+ self._global_subscribers.clear()
252
+
253
+ def shutdown(self) -> None:
254
+ """Shutdown the executor and clear subscribers."""
255
+ self.clear()
256
+ self._executor.shutdown(wait=False)
257
+
258
+
259
+ # Global emitter instance (singleton pattern)
260
+ _emitter: Optional[EventEmitter] = None
261
+
262
+
263
+ def get_emitter() -> EventEmitter:
264
+ """
265
+ Get the global event emitter instance.
266
+
267
+ Returns:
268
+ The singleton EventEmitter instance
269
+ """
270
+ global _emitter
271
+ if _emitter is None:
272
+ _emitter = EventEmitter()
273
+ return _emitter
274
+
275
+
276
+ def reset_emitter() -> None:
277
+ """
278
+ Reset the global emitter (mainly for testing).
279
+
280
+ Creates a fresh emitter instance, clearing all subscriptions.
281
+ """
282
+ global _emitter
283
+ if _emitter is not None:
284
+ _emitter.shutdown()
285
+ _emitter = EventEmitter()