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
@@ -0,0 +1,440 @@
1
+ """
2
+ ALMA Distributed Tracing.
3
+
4
+ Provides distributed tracing using OpenTelemetry with fallback
5
+ to logging when OTel is not available.
6
+ """
7
+
8
+ import functools
9
+ import logging
10
+ from contextlib import contextmanager
11
+ from enum import Enum
12
+ from typing import Any, Callable, Dict, Optional, TypeVar, Union
13
+
14
+ # Try to import OpenTelemetry
15
+ _otel_available = False
16
+ _NoOpSpan = None
17
+ _NoOpTracer = None
18
+
19
+ try:
20
+ from opentelemetry import trace
21
+ from opentelemetry.trace import SpanKind as OTelSpanKind
22
+ from opentelemetry.trace import Status, StatusCode
23
+
24
+ _otel_available = True
25
+ except ImportError:
26
+ pass
27
+
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+ # Type variable for decorated functions
32
+ F = TypeVar("F", bound=Callable[..., Any])
33
+
34
+
35
+ class SpanKind(Enum):
36
+ """Span kind enum (mirrors OpenTelemetry SpanKind)."""
37
+
38
+ INTERNAL = "internal"
39
+ SERVER = "server"
40
+ CLIENT = "client"
41
+ PRODUCER = "producer"
42
+ CONSUMER = "consumer"
43
+
44
+
45
+ class NoOpSpan:
46
+ """No-op span implementation when OpenTelemetry is not available."""
47
+
48
+ def __init__(self, name: str, attributes: Optional[Dict[str, Any]] = None):
49
+ self.name = name
50
+ self.attributes = attributes or {}
51
+ self._logger = logging.getLogger(f"alma.trace.{name}")
52
+
53
+ def set_attribute(self, key: str, value: Any):
54
+ """Set a span attribute."""
55
+ self.attributes[key] = value
56
+
57
+ def set_attributes(self, attributes: Dict[str, Any]):
58
+ """Set multiple span attributes."""
59
+ self.attributes.update(attributes)
60
+
61
+ def add_event(self, name: str, attributes: Optional[Dict[str, Any]] = None):
62
+ """Add an event to the span."""
63
+ self._logger.debug(f"Event: {name}", extra={"event_attributes": attributes})
64
+
65
+ def set_status(self, status: Any, description: Optional[str] = None):
66
+ """Set span status."""
67
+ pass
68
+
69
+ def record_exception(
70
+ self, exception: BaseException, attributes: Optional[Dict[str, Any]] = None
71
+ ):
72
+ """Record an exception."""
73
+ self._logger.error(f"Exception in span {self.name}: {exception}", exc_info=True)
74
+
75
+ def end(self, end_time: Optional[int] = None):
76
+ """End the span."""
77
+ pass
78
+
79
+ def __enter__(self):
80
+ return self
81
+
82
+ def __exit__(self, exc_type, exc_val, exc_tb):
83
+ if exc_type is not None:
84
+ self.record_exception(exc_val)
85
+ return False
86
+
87
+
88
+ class NoOpTracer:
89
+ """No-op tracer implementation when OpenTelemetry is not available."""
90
+
91
+ def __init__(self, name: str):
92
+ self.name = name
93
+
94
+ def start_span(
95
+ self,
96
+ name: str,
97
+ context: Optional[Any] = None,
98
+ kind: SpanKind = SpanKind.INTERNAL,
99
+ attributes: Optional[Dict[str, Any]] = None,
100
+ start_time: Optional[int] = None,
101
+ ) -> NoOpSpan:
102
+ """Start a new span."""
103
+ return NoOpSpan(name, attributes)
104
+
105
+ @contextmanager
106
+ def start_as_current_span(
107
+ self,
108
+ name: str,
109
+ context: Optional[Any] = None,
110
+ kind: SpanKind = SpanKind.INTERNAL,
111
+ attributes: Optional[Dict[str, Any]] = None,
112
+ start_time: Optional[int] = None,
113
+ ):
114
+ """Start a span as the current span."""
115
+ span = NoOpSpan(name, attributes)
116
+ try:
117
+ yield span
118
+ except Exception as e:
119
+ span.record_exception(e)
120
+ raise
121
+ finally:
122
+ span.end()
123
+
124
+
125
+ class TracingContext:
126
+ """
127
+ Context for managing trace propagation and span creation.
128
+
129
+ Provides a unified interface for tracing regardless of
130
+ whether OpenTelemetry is available.
131
+ """
132
+
133
+ def __init__(self, tracer_name: str = "alma"):
134
+ """
135
+ Initialize tracing context.
136
+
137
+ Args:
138
+ tracer_name: Name for the tracer
139
+ """
140
+ self.tracer_name = tracer_name
141
+ self._tracer = None
142
+
143
+ @property
144
+ def tracer(self):
145
+ """Get the tracer (lazy initialization)."""
146
+ if self._tracer is None:
147
+ self._tracer = get_tracer(self.tracer_name)
148
+ return self._tracer
149
+
150
+ @contextmanager
151
+ def span(
152
+ self,
153
+ name: str,
154
+ kind: SpanKind = SpanKind.INTERNAL,
155
+ attributes: Optional[Dict[str, Any]] = None,
156
+ ):
157
+ """
158
+ Create a span context manager.
159
+
160
+ Args:
161
+ name: Span name
162
+ kind: Span kind
163
+ attributes: Initial span attributes
164
+
165
+ Yields:
166
+ The created span
167
+ """
168
+ if _otel_available:
169
+ otel_kind = _map_span_kind(kind)
170
+ with self.tracer.start_as_current_span(
171
+ name,
172
+ kind=otel_kind,
173
+ attributes=attributes,
174
+ ) as span:
175
+ yield span
176
+ else:
177
+ with self.tracer.start_as_current_span(
178
+ name,
179
+ kind=kind,
180
+ attributes=attributes,
181
+ ) as span:
182
+ yield span
183
+
184
+ def create_span(
185
+ self,
186
+ name: str,
187
+ kind: SpanKind = SpanKind.INTERNAL,
188
+ attributes: Optional[Dict[str, Any]] = None,
189
+ ):
190
+ """
191
+ Create a span (not automatically set as current).
192
+
193
+ Args:
194
+ name: Span name
195
+ kind: Span kind
196
+ attributes: Initial span attributes
197
+
198
+ Returns:
199
+ The created span
200
+ """
201
+ if _otel_available:
202
+ otel_kind = _map_span_kind(kind)
203
+ return self.tracer.start_span(
204
+ name,
205
+ kind=otel_kind,
206
+ attributes=attributes,
207
+ )
208
+ else:
209
+ return self.tracer.start_span(
210
+ name,
211
+ kind=kind,
212
+ attributes=attributes,
213
+ )
214
+
215
+
216
+ def _map_span_kind(kind: SpanKind):
217
+ """Map our SpanKind to OpenTelemetry SpanKind."""
218
+ if not _otel_available:
219
+ return kind
220
+
221
+ mapping = {
222
+ SpanKind.INTERNAL: OTelSpanKind.INTERNAL,
223
+ SpanKind.SERVER: OTelSpanKind.SERVER,
224
+ SpanKind.CLIENT: OTelSpanKind.CLIENT,
225
+ SpanKind.PRODUCER: OTelSpanKind.PRODUCER,
226
+ SpanKind.CONSUMER: OTelSpanKind.CONSUMER,
227
+ }
228
+ return mapping.get(kind, OTelSpanKind.INTERNAL)
229
+
230
+
231
+ def get_tracer(name: str = "alma") -> Union["NoOpTracer", Any]:
232
+ """
233
+ Get a tracer for the given name.
234
+
235
+ Uses OpenTelemetry tracer if available, otherwise returns
236
+ a no-op tracer that logs span information.
237
+
238
+ Args:
239
+ name: Tracer name (typically module name)
240
+
241
+ Returns:
242
+ Tracer instance
243
+ """
244
+ if _otel_available:
245
+ return trace.get_tracer(name)
246
+ return NoOpTracer(name)
247
+
248
+
249
+ def get_current_span():
250
+ """
251
+ Get the current span.
252
+
253
+ Returns:
254
+ Current span or NoOpSpan if no span is active
255
+ """
256
+ if _otel_available:
257
+ return trace.get_current_span()
258
+ return NoOpSpan("current")
259
+
260
+
261
+ def trace_method(
262
+ name: Optional[str] = None,
263
+ kind: SpanKind = SpanKind.INTERNAL,
264
+ record_args: bool = True,
265
+ record_result: bool = False,
266
+ ) -> Callable[[F], F]:
267
+ """
268
+ Decorator to trace a synchronous method.
269
+
270
+ Args:
271
+ name: Span name (defaults to function name)
272
+ kind: Span kind
273
+ record_args: Whether to record function arguments as attributes
274
+ record_result: Whether to record the return value
275
+
276
+ Usage:
277
+ @trace_method(name="my_operation")
278
+ def my_function(arg1, arg2):
279
+ return result
280
+ """
281
+
282
+ def decorator(func: F) -> F:
283
+ span_name = name or func.__qualname__
284
+
285
+ @functools.wraps(func)
286
+ def wrapper(*args, **kwargs):
287
+ tracer = get_tracer(func.__module__)
288
+
289
+ attributes: Dict[str, Any] = {
290
+ "code.function": func.__name__,
291
+ "code.namespace": func.__module__,
292
+ }
293
+
294
+ if record_args:
295
+ # Record positional args (skip 'self' for methods)
296
+ arg_names = func.__code__.co_varnames[: func.__code__.co_argcount]
297
+ start_idx = 1 if arg_names and arg_names[0] in ("self", "cls") else 0
298
+ for i, arg in enumerate(args[start_idx:], start=start_idx):
299
+ if i < len(arg_names):
300
+ arg_val = _safe_attribute_value(arg)
301
+ if arg_val is not None:
302
+ attributes[f"arg.{arg_names[i]}"] = arg_val
303
+
304
+ # Record keyword args
305
+ for key, value in kwargs.items():
306
+ arg_val = _safe_attribute_value(value)
307
+ if arg_val is not None:
308
+ attributes[f"arg.{key}"] = arg_val
309
+
310
+ if _otel_available:
311
+ otel_kind = _map_span_kind(kind)
312
+ with tracer.start_as_current_span(
313
+ span_name,
314
+ kind=otel_kind,
315
+ attributes=attributes,
316
+ ) as span:
317
+ try:
318
+ result = func(*args, **kwargs)
319
+ if record_result:
320
+ result_val = _safe_attribute_value(result)
321
+ if result_val is not None:
322
+ span.set_attribute("result", result_val)
323
+ return result
324
+ except Exception as e:
325
+ span.record_exception(e)
326
+ span.set_status(Status(StatusCode.ERROR, str(e)))
327
+ raise
328
+ else:
329
+ with tracer.start_as_current_span(
330
+ span_name,
331
+ kind=kind,
332
+ attributes=attributes,
333
+ ) as span:
334
+ return func(*args, **kwargs)
335
+
336
+ return wrapper # type: ignore
337
+
338
+ return decorator
339
+
340
+
341
+ def trace_async(
342
+ name: Optional[str] = None,
343
+ kind: SpanKind = SpanKind.INTERNAL,
344
+ record_args: bool = True,
345
+ record_result: bool = False,
346
+ ) -> Callable[[F], F]:
347
+ """
348
+ Decorator to trace an async method.
349
+
350
+ Args:
351
+ name: Span name (defaults to function name)
352
+ kind: Span kind
353
+ record_args: Whether to record function arguments as attributes
354
+ record_result: Whether to record the return value
355
+
356
+ Usage:
357
+ @trace_async(name="my_async_operation")
358
+ async def my_async_function(arg1, arg2):
359
+ return result
360
+ """
361
+
362
+ def decorator(func: F) -> F:
363
+ span_name = name or func.__qualname__
364
+
365
+ @functools.wraps(func)
366
+ async def wrapper(*args, **kwargs):
367
+ tracer = get_tracer(func.__module__)
368
+
369
+ attributes: Dict[str, Any] = {
370
+ "code.function": func.__name__,
371
+ "code.namespace": func.__module__,
372
+ }
373
+
374
+ if record_args:
375
+ # Record positional args (skip 'self' for methods)
376
+ arg_names = func.__code__.co_varnames[: func.__code__.co_argcount]
377
+ start_idx = 1 if arg_names and arg_names[0] in ("self", "cls") else 0
378
+ for i, arg in enumerate(args[start_idx:], start=start_idx):
379
+ if i < len(arg_names):
380
+ arg_val = _safe_attribute_value(arg)
381
+ if arg_val is not None:
382
+ attributes[f"arg.{arg_names[i]}"] = arg_val
383
+
384
+ # Record keyword args
385
+ for key, value in kwargs.items():
386
+ arg_val = _safe_attribute_value(value)
387
+ if arg_val is not None:
388
+ attributes[f"arg.{key}"] = arg_val
389
+
390
+ if _otel_available:
391
+ otel_kind = _map_span_kind(kind)
392
+ with tracer.start_as_current_span(
393
+ span_name,
394
+ kind=otel_kind,
395
+ attributes=attributes,
396
+ ) as span:
397
+ try:
398
+ result = await func(*args, **kwargs)
399
+ if record_result:
400
+ result_val = _safe_attribute_value(result)
401
+ if result_val is not None:
402
+ span.set_attribute("result", result_val)
403
+ return result
404
+ except Exception as e:
405
+ span.record_exception(e)
406
+ span.set_status(Status(StatusCode.ERROR, str(e)))
407
+ raise
408
+ else:
409
+ with tracer.start_as_current_span(
410
+ span_name,
411
+ kind=kind,
412
+ attributes=attributes,
413
+ ) as span:
414
+ return await func(*args, **kwargs)
415
+
416
+ return wrapper # type: ignore
417
+
418
+ return decorator
419
+
420
+
421
+ def _safe_attribute_value(value: Any) -> Optional[Union[str, int, float, bool]]:
422
+ """
423
+ Convert a value to a safe attribute value for tracing.
424
+
425
+ OpenTelemetry only supports certain types for attributes.
426
+ """
427
+ if value is None:
428
+ return None
429
+ if isinstance(value, (str, int, float, bool)):
430
+ return value
431
+ if isinstance(value, (list, tuple)):
432
+ if len(value) <= 10: # Limit list size
433
+ return str(value)
434
+ return f"[{len(value)} items]"
435
+ if isinstance(value, dict):
436
+ if len(value) <= 5: # Limit dict size
437
+ return str(value)
438
+ return f"{{{len(value)} items}}"
439
+ # For complex objects, return type and id
440
+ return f"<{type(value).__name__}>"
alma/progress/__init__.py CHANGED
@@ -4,13 +4,13 @@ ALMA Progress Tracking Module.
4
4
  Track work items, progress, and suggest next actions.
5
5
  """
6
6
 
7
+ from alma.progress.tracker import ProgressTracker
7
8
  from alma.progress.types import (
8
- WorkItem,
9
- WorkItemStatus,
10
9
  ProgressLog,
11
10
  ProgressSummary,
11
+ WorkItem,
12
+ WorkItemStatus,
12
13
  )
13
- from alma.progress.tracker import ProgressTracker
14
14
 
15
15
  __all__ = [
16
16
  "WorkItem",
alma/progress/tracker.py CHANGED
@@ -5,27 +5,25 @@ Manages work items and provides progress tracking functionality.
5
5
  """
6
6
 
7
7
  import logging
8
- import uuid
9
8
  from datetime import datetime, timezone
10
- from typing import Optional, List, Dict, Any, Literal
9
+ from typing import Any, Dict, List, Literal, Optional
11
10
 
12
11
  from alma.progress.types import (
13
- WorkItem,
14
- WorkItemStatus,
15
12
  ProgressLog,
16
13
  ProgressSummary,
14
+ WorkItem,
15
+ WorkItemStatus,
17
16
  )
18
17
  from alma.storage.base import StorageBackend
19
18
 
20
-
21
19
  logger = logging.getLogger(__name__)
22
20
 
23
21
 
24
22
  SelectionStrategy = Literal[
25
- "priority", # Highest priority first
23
+ "priority", # Highest priority first
26
24
  "blocked_unblock", # Items that unblock others
27
- "quick_win", # Smallest/easiest first
28
- "fifo", # First in, first out
25
+ "quick_win", # Smallest/easiest first
26
+ "fifo", # First in, first out
29
27
  ]
30
28
 
31
29
 
@@ -176,12 +174,14 @@ class ProgressTracker:
176
174
  if notes:
177
175
  if "status_notes" not in item.metadata:
178
176
  item.metadata["status_notes"] = []
179
- item.metadata["status_notes"].append({
180
- "from": old_status,
181
- "to": status,
182
- "notes": notes,
183
- "timestamp": datetime.now(timezone.utc).isoformat(),
184
- })
177
+ item.metadata["status_notes"].append(
178
+ {
179
+ "from": old_status,
180
+ "to": status,
181
+ "notes": notes,
182
+ "timestamp": datetime.now(timezone.utc).isoformat(),
183
+ }
184
+ )
185
185
 
186
186
  logger.info(f"Status updated: {item_id} {old_status} -> {status}")
187
187
  return item
@@ -262,7 +262,8 @@ class ProgressTracker:
262
262
  ) -> List[WorkItem]:
263
263
  """Get items that can be worked on (not blocked, not done)."""
264
264
  return [
265
- item for item in self._work_items.values()
265
+ item
266
+ for item in self._work_items.values()
266
267
  if item.is_actionable()
267
268
  and (agent is None or item.agent == agent or item.agent is None)
268
269
  ]
@@ -312,7 +313,8 @@ class ProgressTracker:
312
313
  unblock_counts = {}
313
314
  for item in actionable:
314
315
  count = sum(
315
- 1 for other in self._work_items.values()
316
+ 1
317
+ for other in self._work_items.values()
316
318
  if item.id in other.blocked_by
317
319
  )
318
320
  unblock_counts[item.id] = count
@@ -439,9 +441,9 @@ class ProgressTracker:
439
441
  logs = self._progress_logs
440
442
 
441
443
  if agent:
442
- logs = [l for l in logs if l.agent == agent]
444
+ logs = [log for log in logs if log.agent == agent]
443
445
  if session_id:
444
- logs = [l for l in logs if l.session_id == session_id]
446
+ logs = [log for log in logs if log.session_id == session_id]
445
447
 
446
448
  # Sort by created_at descending and limit
447
449
  logs.sort(key=lambda x: x.created_at, reverse=True)
@@ -530,8 +532,12 @@ class ProgressTracker:
530
532
  "attempt_count": item.attempt_count,
531
533
  "created_at": item.created_at.isoformat(),
532
534
  "updated_at": item.updated_at.isoformat(),
533
- "started_at": item.started_at.isoformat() if item.started_at else None,
534
- "completed_at": item.completed_at.isoformat() if item.completed_at else None,
535
+ "started_at": (
536
+ item.started_at.isoformat() if item.started_at else None
537
+ ),
538
+ "completed_at": (
539
+ item.completed_at.isoformat() if item.completed_at else None
540
+ ),
535
541
  "metadata": item.metadata,
536
542
  }
537
543
  for item in self._work_items.values()
alma/progress/types.py CHANGED
@@ -4,19 +4,18 @@ Progress Tracking Types.
4
4
  Data models for tracking work items and progress.
5
5
  """
6
6
 
7
+ import uuid
7
8
  from dataclasses import dataclass, field
8
9
  from datetime import datetime, timezone
9
- from typing import Optional, List, Dict, Any, Literal
10
- import uuid
11
-
10
+ from typing import Any, Dict, List, Literal, Optional
12
11
 
13
12
  WorkItemStatus = Literal[
14
- "pending", # Not started
13
+ "pending", # Not started
15
14
  "in_progress", # Currently being worked on
16
- "blocked", # Waiting on something
17
- "review", # Completed, awaiting review
18
- "done", # Completed and verified
19
- "failed", # Could not complete
15
+ "blocked", # Waiting on something
16
+ "review", # Completed, awaiting review
17
+ "done", # Completed and verified
18
+ "failed", # Could not complete
20
19
  ]
21
20
 
22
21
 
@@ -124,10 +123,7 @@ class WorkItem:
124
123
 
125
124
  def is_actionable(self) -> bool:
126
125
  """Check if work item can be worked on."""
127
- return (
128
- self.status in ("pending", "in_progress")
129
- and len(self.blocked_by) == 0
130
- )
126
+ return self.status in ("pending", "in_progress") and len(self.blocked_by) == 0
131
127
 
132
128
 
133
129
  @dataclass
alma/py.typed ADDED
File without changes
@@ -4,29 +4,29 @@ ALMA Retrieval Engine.
4
4
  Provides semantic search, scoring, and caching for memory retrieval.
5
5
  """
6
6
 
7
- from alma.retrieval.engine import RetrievalEngine
8
- from alma.retrieval.scoring import (
9
- MemoryScorer,
10
- ScoringWeights,
11
- ScoredItem,
12
- compute_composite_score,
13
- )
14
7
  from alma.retrieval.cache import (
15
8
  CacheBackend,
16
- RetrievalCache,
17
- RedisCache,
18
- NullCache,
19
9
  CacheEntry,
20
10
  CacheStats,
11
+ NullCache,
21
12
  PerformanceMetrics,
13
+ RedisCache,
14
+ RetrievalCache,
22
15
  create_cache,
23
16
  )
24
17
  from alma.retrieval.embeddings import (
18
+ AzureEmbedder,
25
19
  EmbeddingProvider,
26
20
  LocalEmbedder,
27
- AzureEmbedder,
28
21
  MockEmbedder,
29
22
  )
23
+ from alma.retrieval.engine import RetrievalEngine
24
+ from alma.retrieval.scoring import (
25
+ MemoryScorer,
26
+ ScoredItem,
27
+ ScoringWeights,
28
+ compute_composite_score,
29
+ )
30
30
 
31
31
  __all__ = [
32
32
  # Engine