alma-memory 0.5.1__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 -226
  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 -430
  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 -265
  24. alma/extraction/extractor.py +420 -420
  25. alma/graph/__init__.py +106 -106
  26. alma/graph/backends/__init__.py +32 -32
  27. alma/graph/backends/kuzu.py +624 -624
  28. alma/graph/backends/memgraph.py +432 -432
  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 -444
  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 -509
  55. alma/observability/__init__.py +91 -84
  56. alma/observability/config.py +302 -302
  57. alma/observability/guidelines.py +170 -0
  58. alma/observability/logging.py +424 -424
  59. alma/observability/metrics.py +583 -583
  60. alma/observability/tracing.py +440 -440
  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 -427
  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 -90
  78. alma/storage/archive.py +233 -0
  79. alma/storage/azure_cosmos.py +1259 -1259
  80. alma/storage/base.py +1083 -583
  81. alma/storage/chroma.py +1443 -1443
  82. alma/storage/constants.py +103 -103
  83. alma/storage/file_based.py +614 -614
  84. alma/storage/migrations/__init__.py +21 -21
  85. alma/storage/migrations/base.py +321 -321
  86. alma/storage/migrations/runner.py +323 -323
  87. alma/storage/migrations/version_stores.py +337 -337
  88. alma/storage/migrations/versions/__init__.py +11 -11
  89. alma/storage/migrations/versions/v1_0_0.py +373 -373
  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 -1559
  93. alma/storage/qdrant.py +1306 -1306
  94. alma/storage/sqlite_local.py +3041 -1457
  95. alma/testing/__init__.py +46 -46
  96. alma/testing/factories.py +301 -301
  97. alma/testing/mocks.py +389 -389
  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.1.dist-info → alma_memory-0.7.0.dist-info}/METADATA +210 -72
  108. alma_memory-0.7.0.dist-info/RECORD +112 -0
  109. alma_memory-0.5.1.dist-info/RECORD +0 -93
  110. {alma_memory-0.5.1.dist-info → alma_memory-0.7.0.dist-info}/WHEEL +0 -0
  111. {alma_memory-0.5.1.dist-info → alma_memory-0.7.0.dist-info}/top_level.txt +0 -0
@@ -1,236 +1,236 @@
1
- """
2
- ALMA Graph Memory - In-Memory Backend.
3
-
4
- In-memory implementation of the GraphBackend interface for testing and development.
5
- """
6
-
7
- import logging
8
- from typing import Dict, List, Optional, Set
9
-
10
- from alma.graph.base import GraphBackend
11
- from alma.graph.store import Entity, Relationship
12
-
13
- logger = logging.getLogger(__name__)
14
-
15
-
16
- class InMemoryBackend(GraphBackend):
17
- """
18
- In-memory graph database backend.
19
-
20
- Suitable for testing, development, and small-scale use cases
21
- where persistence is not required.
22
-
23
- No external dependencies required.
24
- """
25
-
26
- def __init__(self):
27
- """Initialize empty in-memory storage."""
28
- self._entities: Dict[str, Entity] = {}
29
- self._relationships: Dict[str, Relationship] = {}
30
- self._outgoing: Dict[str, List[str]] = {} # entity_id -> [rel_ids]
31
- self._incoming: Dict[str, List[str]] = {} # entity_id -> [rel_ids]
32
-
33
- def add_entity(self, entity: Entity) -> str:
34
- """Add or update an entity."""
35
- self._entities[entity.id] = entity
36
- if entity.id not in self._outgoing:
37
- self._outgoing[entity.id] = []
38
- if entity.id not in self._incoming:
39
- self._incoming[entity.id] = []
40
- return entity.id
41
-
42
- def add_relationship(self, relationship: Relationship) -> str:
43
- """Add or update a relationship."""
44
- self._relationships[relationship.id] = relationship
45
-
46
- if relationship.source_id not in self._outgoing:
47
- self._outgoing[relationship.source_id] = []
48
- if relationship.id not in self._outgoing[relationship.source_id]:
49
- self._outgoing[relationship.source_id].append(relationship.id)
50
-
51
- if relationship.target_id not in self._incoming:
52
- self._incoming[relationship.target_id] = []
53
- if relationship.id not in self._incoming[relationship.target_id]:
54
- self._incoming[relationship.target_id].append(relationship.id)
55
-
56
- return relationship.id
57
-
58
- def get_entity(self, entity_id: str) -> Optional[Entity]:
59
- """Get an entity by ID."""
60
- return self._entities.get(entity_id)
61
-
62
- def get_entities(
63
- self,
64
- entity_type: Optional[str] = None,
65
- project_id: Optional[str] = None,
66
- agent: Optional[str] = None,
67
- limit: int = 100,
68
- ) -> List[Entity]:
69
- """Get entities with optional filtering."""
70
- results = []
71
- for entity in self._entities.values():
72
- if entity_type and entity.entity_type != entity_type:
73
- continue
74
- if project_id and entity.properties.get("project_id") != project_id:
75
- continue
76
- if agent and entity.properties.get("agent") != agent:
77
- continue
78
- results.append(entity)
79
- if len(results) >= limit:
80
- break
81
- return results
82
-
83
- def get_relationships(self, entity_id: str) -> List[Relationship]:
84
- """Get all relationships for an entity (both directions)."""
85
- rel_ids: Set[str] = set()
86
- rel_ids.update(self._outgoing.get(entity_id, []))
87
- rel_ids.update(self._incoming.get(entity_id, []))
88
-
89
- return [
90
- self._relationships[rid] for rid in rel_ids if rid in self._relationships
91
- ]
92
-
93
- def search_entities(
94
- self,
95
- query: str,
96
- embedding: Optional[List[float]] = None,
97
- top_k: int = 10,
98
- ) -> List[Entity]:
99
- """
100
- Search for entities by name.
101
-
102
- Note: Vector similarity search is not implemented for in-memory backend.
103
- Falls back to case-insensitive text search.
104
- """
105
- query_lower = query.lower()
106
- results = []
107
- for entity in self._entities.values():
108
- if query_lower in entity.name.lower():
109
- results.append(entity)
110
- if len(results) >= top_k:
111
- break
112
- return results
113
-
114
- def delete_entity(self, entity_id: str) -> bool:
115
- """Delete an entity and all its relationships."""
116
- if entity_id not in self._entities:
117
- return False
118
-
119
- # Delete outgoing relationships
120
- for rel_id in list(self._outgoing.get(entity_id, [])):
121
- if rel_id in self._relationships:
122
- rel = self._relationships[rel_id]
123
- # Remove from target's incoming
124
- if rel.target_id in self._incoming:
125
- if rel_id in self._incoming[rel.target_id]:
126
- self._incoming[rel.target_id].remove(rel_id)
127
- del self._relationships[rel_id]
128
-
129
- # Delete incoming relationships
130
- for rel_id in list(self._incoming.get(entity_id, [])):
131
- if rel_id in self._relationships:
132
- rel = self._relationships[rel_id]
133
- # Remove from source's outgoing
134
- if rel.source_id in self._outgoing:
135
- if rel_id in self._outgoing[rel.source_id]:
136
- self._outgoing[rel.source_id].remove(rel_id)
137
- del self._relationships[rel_id]
138
-
139
- # Delete entity
140
- del self._entities[entity_id]
141
- self._outgoing.pop(entity_id, None)
142
- self._incoming.pop(entity_id, None)
143
-
144
- return True
145
-
146
- def delete_relationship(self, relationship_id: str) -> bool:
147
- """Delete a specific relationship by ID."""
148
- if relationship_id not in self._relationships:
149
- return False
150
-
151
- rel = self._relationships[relationship_id]
152
-
153
- # Remove from source's outgoing
154
- if rel.source_id in self._outgoing:
155
- if relationship_id in self._outgoing[rel.source_id]:
156
- self._outgoing[rel.source_id].remove(relationship_id)
157
-
158
- # Remove from target's incoming
159
- if rel.target_id in self._incoming:
160
- if relationship_id in self._incoming[rel.target_id]:
161
- self._incoming[rel.target_id].remove(relationship_id)
162
-
163
- del self._relationships[relationship_id]
164
- return True
165
-
166
- def close(self) -> None:
167
- """Clear all data (no-op for in-memory backend)."""
168
- # In-memory backend doesn't need explicit cleanup
169
- # but we can optionally clear data
170
- pass
171
-
172
- def clear(self) -> None:
173
- """Clear all stored data."""
174
- self._entities.clear()
175
- self._relationships.clear()
176
- self._outgoing.clear()
177
- self._incoming.clear()
178
-
179
- # Additional methods for compatibility with existing GraphStore API
180
-
181
- def find_entities(
182
- self,
183
- name: Optional[str] = None,
184
- entity_type: Optional[str] = None,
185
- limit: int = 10,
186
- ) -> List[Entity]:
187
- """
188
- Find entities by name or type.
189
-
190
- This method provides compatibility with the existing GraphStore API.
191
- """
192
- results = []
193
- for entity in self._entities.values():
194
- if name and name.lower() not in entity.name.lower():
195
- continue
196
- if entity_type and entity.entity_type != entity_type:
197
- continue
198
- results.append(entity)
199
- if len(results) >= limit:
200
- break
201
- return results
202
-
203
- def get_relationships_directional(
204
- self,
205
- entity_id: str,
206
- direction: str = "both",
207
- relation_type: Optional[str] = None,
208
- ) -> List[Relationship]:
209
- """
210
- Get relationships for an entity with direction control.
211
-
212
- This method provides compatibility with the existing GraphStore API.
213
-
214
- Args:
215
- entity_id: The entity ID.
216
- direction: "outgoing", "incoming", or "both".
217
- relation_type: Optional filter by relationship type.
218
-
219
- Returns:
220
- List of matching relationships.
221
- """
222
- rel_ids: Set[str] = set()
223
-
224
- if direction in ("outgoing", "both"):
225
- rel_ids.update(self._outgoing.get(entity_id, []))
226
- if direction in ("incoming", "both"):
227
- rel_ids.update(self._incoming.get(entity_id, []))
228
-
229
- results = []
230
- for rel_id in rel_ids:
231
- rel = self._relationships.get(rel_id)
232
- if rel:
233
- if relation_type and rel.relation_type != relation_type:
234
- continue
235
- results.append(rel)
236
- return results
1
+ """
2
+ ALMA Graph Memory - In-Memory Backend.
3
+
4
+ In-memory implementation of the GraphBackend interface for testing and development.
5
+ """
6
+
7
+ import logging
8
+ from typing import Dict, List, Optional, Set
9
+
10
+ from alma.graph.base import GraphBackend
11
+ from alma.graph.store import Entity, Relationship
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class InMemoryBackend(GraphBackend):
17
+ """
18
+ In-memory graph database backend.
19
+
20
+ Suitable for testing, development, and small-scale use cases
21
+ where persistence is not required.
22
+
23
+ No external dependencies required.
24
+ """
25
+
26
+ def __init__(self):
27
+ """Initialize empty in-memory storage."""
28
+ self._entities: Dict[str, Entity] = {}
29
+ self._relationships: Dict[str, Relationship] = {}
30
+ self._outgoing: Dict[str, List[str]] = {} # entity_id -> [rel_ids]
31
+ self._incoming: Dict[str, List[str]] = {} # entity_id -> [rel_ids]
32
+
33
+ def add_entity(self, entity: Entity) -> str:
34
+ """Add or update an entity."""
35
+ self._entities[entity.id] = entity
36
+ if entity.id not in self._outgoing:
37
+ self._outgoing[entity.id] = []
38
+ if entity.id not in self._incoming:
39
+ self._incoming[entity.id] = []
40
+ return entity.id
41
+
42
+ def add_relationship(self, relationship: Relationship) -> str:
43
+ """Add or update a relationship."""
44
+ self._relationships[relationship.id] = relationship
45
+
46
+ if relationship.source_id not in self._outgoing:
47
+ self._outgoing[relationship.source_id] = []
48
+ if relationship.id not in self._outgoing[relationship.source_id]:
49
+ self._outgoing[relationship.source_id].append(relationship.id)
50
+
51
+ if relationship.target_id not in self._incoming:
52
+ self._incoming[relationship.target_id] = []
53
+ if relationship.id not in self._incoming[relationship.target_id]:
54
+ self._incoming[relationship.target_id].append(relationship.id)
55
+
56
+ return relationship.id
57
+
58
+ def get_entity(self, entity_id: str) -> Optional[Entity]:
59
+ """Get an entity by ID."""
60
+ return self._entities.get(entity_id)
61
+
62
+ def get_entities(
63
+ self,
64
+ entity_type: Optional[str] = None,
65
+ project_id: Optional[str] = None,
66
+ agent: Optional[str] = None,
67
+ limit: int = 100,
68
+ ) -> List[Entity]:
69
+ """Get entities with optional filtering."""
70
+ results = []
71
+ for entity in self._entities.values():
72
+ if entity_type and entity.entity_type != entity_type:
73
+ continue
74
+ if project_id and entity.properties.get("project_id") != project_id:
75
+ continue
76
+ if agent and entity.properties.get("agent") != agent:
77
+ continue
78
+ results.append(entity)
79
+ if len(results) >= limit:
80
+ break
81
+ return results
82
+
83
+ def get_relationships(self, entity_id: str) -> List[Relationship]:
84
+ """Get all relationships for an entity (both directions)."""
85
+ rel_ids: Set[str] = set()
86
+ rel_ids.update(self._outgoing.get(entity_id, []))
87
+ rel_ids.update(self._incoming.get(entity_id, []))
88
+
89
+ return [
90
+ self._relationships[rid] for rid in rel_ids if rid in self._relationships
91
+ ]
92
+
93
+ def search_entities(
94
+ self,
95
+ query: str,
96
+ embedding: Optional[List[float]] = None,
97
+ top_k: int = 10,
98
+ ) -> List[Entity]:
99
+ """
100
+ Search for entities by name.
101
+
102
+ Note: Vector similarity search is not implemented for in-memory backend.
103
+ Falls back to case-insensitive text search.
104
+ """
105
+ query_lower = query.lower()
106
+ results = []
107
+ for entity in self._entities.values():
108
+ if query_lower in entity.name.lower():
109
+ results.append(entity)
110
+ if len(results) >= top_k:
111
+ break
112
+ return results
113
+
114
+ def delete_entity(self, entity_id: str) -> bool:
115
+ """Delete an entity and all its relationships."""
116
+ if entity_id not in self._entities:
117
+ return False
118
+
119
+ # Delete outgoing relationships
120
+ for rel_id in list(self._outgoing.get(entity_id, [])):
121
+ if rel_id in self._relationships:
122
+ rel = self._relationships[rel_id]
123
+ # Remove from target's incoming
124
+ if rel.target_id in self._incoming:
125
+ if rel_id in self._incoming[rel.target_id]:
126
+ self._incoming[rel.target_id].remove(rel_id)
127
+ del self._relationships[rel_id]
128
+
129
+ # Delete incoming relationships
130
+ for rel_id in list(self._incoming.get(entity_id, [])):
131
+ if rel_id in self._relationships:
132
+ rel = self._relationships[rel_id]
133
+ # Remove from source's outgoing
134
+ if rel.source_id in self._outgoing:
135
+ if rel_id in self._outgoing[rel.source_id]:
136
+ self._outgoing[rel.source_id].remove(rel_id)
137
+ del self._relationships[rel_id]
138
+
139
+ # Delete entity
140
+ del self._entities[entity_id]
141
+ self._outgoing.pop(entity_id, None)
142
+ self._incoming.pop(entity_id, None)
143
+
144
+ return True
145
+
146
+ def delete_relationship(self, relationship_id: str) -> bool:
147
+ """Delete a specific relationship by ID."""
148
+ if relationship_id not in self._relationships:
149
+ return False
150
+
151
+ rel = self._relationships[relationship_id]
152
+
153
+ # Remove from source's outgoing
154
+ if rel.source_id in self._outgoing:
155
+ if relationship_id in self._outgoing[rel.source_id]:
156
+ self._outgoing[rel.source_id].remove(relationship_id)
157
+
158
+ # Remove from target's incoming
159
+ if rel.target_id in self._incoming:
160
+ if relationship_id in self._incoming[rel.target_id]:
161
+ self._incoming[rel.target_id].remove(relationship_id)
162
+
163
+ del self._relationships[relationship_id]
164
+ return True
165
+
166
+ def close(self) -> None:
167
+ """Clear all data (no-op for in-memory backend)."""
168
+ # In-memory backend doesn't need explicit cleanup
169
+ # but we can optionally clear data
170
+ pass
171
+
172
+ def clear(self) -> None:
173
+ """Clear all stored data."""
174
+ self._entities.clear()
175
+ self._relationships.clear()
176
+ self._outgoing.clear()
177
+ self._incoming.clear()
178
+
179
+ # Additional methods for compatibility with existing GraphStore API
180
+
181
+ def find_entities(
182
+ self,
183
+ name: Optional[str] = None,
184
+ entity_type: Optional[str] = None,
185
+ limit: int = 10,
186
+ ) -> List[Entity]:
187
+ """
188
+ Find entities by name or type.
189
+
190
+ This method provides compatibility with the existing GraphStore API.
191
+ """
192
+ results = []
193
+ for entity in self._entities.values():
194
+ if name and name.lower() not in entity.name.lower():
195
+ continue
196
+ if entity_type and entity.entity_type != entity_type:
197
+ continue
198
+ results.append(entity)
199
+ if len(results) >= limit:
200
+ break
201
+ return results
202
+
203
+ def get_relationships_directional(
204
+ self,
205
+ entity_id: str,
206
+ direction: str = "both",
207
+ relation_type: Optional[str] = None,
208
+ ) -> List[Relationship]:
209
+ """
210
+ Get relationships for an entity with direction control.
211
+
212
+ This method provides compatibility with the existing GraphStore API.
213
+
214
+ Args:
215
+ entity_id: The entity ID.
216
+ direction: "outgoing", "incoming", or "both".
217
+ relation_type: Optional filter by relationship type.
218
+
219
+ Returns:
220
+ List of matching relationships.
221
+ """
222
+ rel_ids: Set[str] = set()
223
+
224
+ if direction in ("outgoing", "both"):
225
+ rel_ids.update(self._outgoing.get(entity_id, []))
226
+ if direction in ("incoming", "both"):
227
+ rel_ids.update(self._incoming.get(entity_id, []))
228
+
229
+ results = []
230
+ for rel_id in rel_ids:
231
+ rel = self._relationships.get(rel_id)
232
+ if rel:
233
+ if relation_type and rel.relation_type != relation_type:
234
+ continue
235
+ results.append(rel)
236
+ return results