alma-memory 0.5.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.
- alma/__init__.py +33 -1
- alma/core.py +124 -16
- alma/extraction/auto_learner.py +4 -3
- alma/graph/__init__.py +26 -1
- alma/graph/backends/__init__.py +14 -0
- alma/graph/backends/kuzu.py +624 -0
- alma/graph/backends/memgraph.py +432 -0
- alma/integration/claude_agents.py +22 -10
- alma/learning/protocols.py +3 -3
- alma/mcp/tools.py +9 -11
- alma/observability/__init__.py +84 -0
- alma/observability/config.py +302 -0
- alma/observability/logging.py +424 -0
- alma/observability/metrics.py +583 -0
- alma/observability/tracing.py +440 -0
- alma/retrieval/engine.py +65 -4
- alma/storage/__init__.py +29 -0
- alma/storage/azure_cosmos.py +343 -132
- alma/storage/base.py +58 -0
- alma/storage/constants.py +103 -0
- alma/storage/file_based.py +3 -8
- alma/storage/migrations/__init__.py +21 -0
- alma/storage/migrations/base.py +321 -0
- alma/storage/migrations/runner.py +323 -0
- alma/storage/migrations/version_stores.py +337 -0
- alma/storage/migrations/versions/__init__.py +11 -0
- alma/storage/migrations/versions/v1_0_0.py +373 -0
- alma/storage/postgresql.py +185 -78
- alma/storage/sqlite_local.py +149 -50
- alma/testing/__init__.py +46 -0
- alma/testing/factories.py +301 -0
- alma/testing/mocks.py +389 -0
- {alma_memory-0.5.0.dist-info → alma_memory-0.5.1.dist-info}/METADATA +42 -8
- {alma_memory-0.5.0.dist-info → alma_memory-0.5.1.dist-info}/RECORD +36 -19
- {alma_memory-0.5.0.dist-info → alma_memory-0.5.1.dist-info}/WHEEL +0 -0
- {alma_memory-0.5.0.dist-info → alma_memory-0.5.1.dist-info}/top_level.txt +0 -0
alma/storage/postgresql.py
CHANGED
|
@@ -27,6 +27,7 @@ except ImportError:
|
|
|
27
27
|
NUMPY_AVAILABLE = False
|
|
28
28
|
|
|
29
29
|
from alma.storage.base import StorageBackend
|
|
30
|
+
from alma.storage.constants import POSTGRESQL_TABLE_NAMES, MemoryType
|
|
30
31
|
from alma.types import (
|
|
31
32
|
AntiPattern,
|
|
32
33
|
DomainKnowledge,
|
|
@@ -57,7 +58,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
57
58
|
Uses native PostgreSQL vector operations for efficient similarity search.
|
|
58
59
|
Falls back to application-level cosine similarity if pgvector is not installed.
|
|
59
60
|
|
|
60
|
-
Database schema:
|
|
61
|
+
Database schema (uses canonical memory type names with alma_ prefix):
|
|
61
62
|
- alma_heuristics: id, agent, project_id, condition, strategy, ...
|
|
62
63
|
- alma_outcomes: id, agent, project_id, task_type, ...
|
|
63
64
|
- alma_preferences: id, user_id, category, preference, ...
|
|
@@ -67,8 +68,14 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
67
68
|
Vector search:
|
|
68
69
|
- Uses pgvector extension if available
|
|
69
70
|
- Embeddings stored as VECTOR type with cosine distance operator (<=>)
|
|
71
|
+
|
|
72
|
+
Table names are derived from alma.storage.constants.POSTGRESQL_TABLE_NAMES
|
|
73
|
+
for consistency across all storage backends.
|
|
70
74
|
"""
|
|
71
75
|
|
|
76
|
+
# Table names from constants for consistent naming
|
|
77
|
+
TABLE_NAMES = POSTGRESQL_TABLE_NAMES
|
|
78
|
+
|
|
72
79
|
def __init__(
|
|
73
80
|
self,
|
|
74
81
|
host: str,
|
|
@@ -80,6 +87,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
80
87
|
pool_size: int = 10,
|
|
81
88
|
schema: str = "public",
|
|
82
89
|
ssl_mode: str = "prefer",
|
|
90
|
+
auto_migrate: bool = True,
|
|
83
91
|
):
|
|
84
92
|
"""
|
|
85
93
|
Initialize PostgreSQL storage.
|
|
@@ -94,6 +102,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
94
102
|
pool_size: Connection pool size
|
|
95
103
|
schema: Database schema (default: public)
|
|
96
104
|
ssl_mode: SSL mode (disable, allow, prefer, require, verify-ca, verify-full)
|
|
105
|
+
auto_migrate: If True, automatically apply pending migrations on startup
|
|
97
106
|
"""
|
|
98
107
|
if not PSYCOPG_AVAILABLE:
|
|
99
108
|
raise ImportError(
|
|
@@ -104,6 +113,10 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
104
113
|
self.schema = schema
|
|
105
114
|
self._pgvector_available = False
|
|
106
115
|
|
|
116
|
+
# Migration support (lazy-loaded)
|
|
117
|
+
self._migration_runner = None
|
|
118
|
+
self._version_store = None
|
|
119
|
+
|
|
107
120
|
# Build connection string
|
|
108
121
|
conninfo = (
|
|
109
122
|
f"host={host} port={port} dbname={database} "
|
|
@@ -121,6 +134,10 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
121
134
|
# Initialize database
|
|
122
135
|
self._init_database()
|
|
123
136
|
|
|
137
|
+
# Auto-migrate if enabled
|
|
138
|
+
if auto_migrate:
|
|
139
|
+
self._ensure_migrated()
|
|
140
|
+
|
|
124
141
|
@classmethod
|
|
125
142
|
def from_config(cls, config: Dict[str, Any]) -> "PostgreSQLStorage":
|
|
126
143
|
"""Create instance from configuration."""
|
|
@@ -176,8 +193,9 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
176
193
|
)
|
|
177
194
|
|
|
178
195
|
# Heuristics table
|
|
196
|
+
heuristics_table = self.TABLE_NAMES[MemoryType.HEURISTICS]
|
|
179
197
|
conn.execute(f"""
|
|
180
|
-
CREATE TABLE IF NOT EXISTS {self.schema}.
|
|
198
|
+
CREATE TABLE IF NOT EXISTS {self.schema}.{heuristics_table} (
|
|
181
199
|
id TEXT PRIMARY KEY,
|
|
182
200
|
agent TEXT NOT NULL,
|
|
183
201
|
project_id TEXT NOT NULL,
|
|
@@ -194,12 +212,18 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
194
212
|
""")
|
|
195
213
|
conn.execute(f"""
|
|
196
214
|
CREATE INDEX IF NOT EXISTS idx_heuristics_project_agent
|
|
197
|
-
ON {self.schema}.
|
|
215
|
+
ON {self.schema}.{heuristics_table}(project_id, agent)
|
|
216
|
+
""")
|
|
217
|
+
# Confidence index for efficient filtering by confidence score
|
|
218
|
+
conn.execute(f"""
|
|
219
|
+
CREATE INDEX IF NOT EXISTS idx_heuristics_confidence
|
|
220
|
+
ON {self.schema}.{heuristics_table}(project_id, confidence DESC)
|
|
198
221
|
""")
|
|
199
222
|
|
|
200
223
|
# Outcomes table
|
|
224
|
+
outcomes_table = self.TABLE_NAMES[MemoryType.OUTCOMES]
|
|
201
225
|
conn.execute(f"""
|
|
202
|
-
CREATE TABLE IF NOT EXISTS {self.schema}.
|
|
226
|
+
CREATE TABLE IF NOT EXISTS {self.schema}.{outcomes_table} (
|
|
203
227
|
id TEXT PRIMARY KEY,
|
|
204
228
|
agent TEXT NOT NULL,
|
|
205
229
|
project_id TEXT NOT NULL,
|
|
@@ -217,20 +241,21 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
217
241
|
""")
|
|
218
242
|
conn.execute(f"""
|
|
219
243
|
CREATE INDEX IF NOT EXISTS idx_outcomes_project_agent
|
|
220
|
-
ON {self.schema}.
|
|
244
|
+
ON {self.schema}.{outcomes_table}(project_id, agent)
|
|
221
245
|
""")
|
|
222
246
|
conn.execute(f"""
|
|
223
247
|
CREATE INDEX IF NOT EXISTS idx_outcomes_task_type
|
|
224
|
-
ON {self.schema}.
|
|
248
|
+
ON {self.schema}.{outcomes_table}(project_id, agent, task_type)
|
|
225
249
|
""")
|
|
226
250
|
conn.execute(f"""
|
|
227
251
|
CREATE INDEX IF NOT EXISTS idx_outcomes_timestamp
|
|
228
|
-
ON {self.schema}.
|
|
252
|
+
ON {self.schema}.{outcomes_table}(project_id, timestamp DESC)
|
|
229
253
|
""")
|
|
230
254
|
|
|
231
255
|
# User preferences table
|
|
256
|
+
preferences_table = self.TABLE_NAMES[MemoryType.PREFERENCES]
|
|
232
257
|
conn.execute(f"""
|
|
233
|
-
CREATE TABLE IF NOT EXISTS {self.schema}.
|
|
258
|
+
CREATE TABLE IF NOT EXISTS {self.schema}.{preferences_table} (
|
|
234
259
|
id TEXT PRIMARY KEY,
|
|
235
260
|
user_id TEXT NOT NULL,
|
|
236
261
|
category TEXT,
|
|
@@ -243,12 +268,13 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
243
268
|
""")
|
|
244
269
|
conn.execute(f"""
|
|
245
270
|
CREATE INDEX IF NOT EXISTS idx_preferences_user
|
|
246
|
-
ON {self.schema}.
|
|
271
|
+
ON {self.schema}.{preferences_table}(user_id)
|
|
247
272
|
""")
|
|
248
273
|
|
|
249
274
|
# Domain knowledge table
|
|
275
|
+
domain_knowledge_table = self.TABLE_NAMES[MemoryType.DOMAIN_KNOWLEDGE]
|
|
250
276
|
conn.execute(f"""
|
|
251
|
-
CREATE TABLE IF NOT EXISTS {self.schema}.
|
|
277
|
+
CREATE TABLE IF NOT EXISTS {self.schema}.{domain_knowledge_table} (
|
|
252
278
|
id TEXT PRIMARY KEY,
|
|
253
279
|
agent TEXT NOT NULL,
|
|
254
280
|
project_id TEXT NOT NULL,
|
|
@@ -263,12 +289,18 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
263
289
|
""")
|
|
264
290
|
conn.execute(f"""
|
|
265
291
|
CREATE INDEX IF NOT EXISTS idx_domain_knowledge_project_agent
|
|
266
|
-
ON {self.schema}.
|
|
292
|
+
ON {self.schema}.{domain_knowledge_table}(project_id, agent)
|
|
293
|
+
""")
|
|
294
|
+
# Confidence index for efficient filtering by confidence score
|
|
295
|
+
conn.execute(f"""
|
|
296
|
+
CREATE INDEX IF NOT EXISTS idx_domain_knowledge_confidence
|
|
297
|
+
ON {self.schema}.{domain_knowledge_table}(project_id, confidence DESC)
|
|
267
298
|
""")
|
|
268
299
|
|
|
269
300
|
# Anti-patterns table
|
|
301
|
+
anti_patterns_table = self.TABLE_NAMES[MemoryType.ANTI_PATTERNS]
|
|
270
302
|
conn.execute(f"""
|
|
271
|
-
CREATE TABLE IF NOT EXISTS {self.schema}.
|
|
303
|
+
CREATE TABLE IF NOT EXISTS {self.schema}.{anti_patterns_table} (
|
|
272
304
|
id TEXT PRIMARY KEY,
|
|
273
305
|
agent TEXT NOT NULL,
|
|
274
306
|
project_id TEXT NOT NULL,
|
|
@@ -284,19 +316,18 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
284
316
|
""")
|
|
285
317
|
conn.execute(f"""
|
|
286
318
|
CREATE INDEX IF NOT EXISTS idx_anti_patterns_project_agent
|
|
287
|
-
ON {self.schema}.
|
|
319
|
+
ON {self.schema}.{anti_patterns_table}(project_id, agent)
|
|
288
320
|
""")
|
|
289
321
|
|
|
290
322
|
# Create vector indexes if pgvector available
|
|
291
323
|
# Using HNSW instead of IVFFlat because HNSW can be built on empty tables
|
|
292
324
|
# IVFFlat requires existing data to build, which causes silent failures on fresh databases
|
|
293
325
|
if self._pgvector_available:
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
]:
|
|
326
|
+
# Vector-enabled tables use canonical memory type names
|
|
327
|
+
vector_tables = [
|
|
328
|
+
self.TABLE_NAMES[mt] for mt in MemoryType.VECTOR_ENABLED
|
|
329
|
+
]
|
|
330
|
+
for table in vector_tables:
|
|
300
331
|
try:
|
|
301
332
|
conn.execute(f"""
|
|
302
333
|
CREATE INDEX IF NOT EXISTS idx_{table}_embedding
|
|
@@ -359,7 +390,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
359
390
|
with self._get_connection() as conn:
|
|
360
391
|
conn.execute(
|
|
361
392
|
f"""
|
|
362
|
-
INSERT INTO {self.schema}.
|
|
393
|
+
INSERT INTO {self.schema}.{self.TABLE_NAMES[MemoryType.HEURISTICS]}
|
|
363
394
|
(id, agent, project_id, condition, strategy, confidence,
|
|
364
395
|
occurrence_count, success_count, last_validated, created_at, metadata, embedding)
|
|
365
396
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
@@ -398,7 +429,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
398
429
|
with self._get_connection() as conn:
|
|
399
430
|
conn.execute(
|
|
400
431
|
f"""
|
|
401
|
-
INSERT INTO {self.schema}.
|
|
432
|
+
INSERT INTO {self.schema}.{self.TABLE_NAMES[MemoryType.OUTCOMES]}
|
|
402
433
|
(id, agent, project_id, task_type, task_description, success,
|
|
403
434
|
strategy_used, duration_ms, error_message, user_feedback, timestamp, metadata, embedding)
|
|
404
435
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
@@ -438,7 +469,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
438
469
|
with self._get_connection() as conn:
|
|
439
470
|
conn.execute(
|
|
440
471
|
f"""
|
|
441
|
-
INSERT INTO {self.schema}.
|
|
472
|
+
INSERT INTO {self.schema}.{self.TABLE_NAMES[MemoryType.PREFERENCES]}
|
|
442
473
|
(id, user_id, category, preference, source, confidence, timestamp, metadata)
|
|
443
474
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
|
444
475
|
ON CONFLICT (id) DO UPDATE SET
|
|
@@ -468,7 +499,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
468
499
|
with self._get_connection() as conn:
|
|
469
500
|
conn.execute(
|
|
470
501
|
f"""
|
|
471
|
-
INSERT INTO {self.schema}.
|
|
502
|
+
INSERT INTO {self.schema}.{self.TABLE_NAMES[MemoryType.DOMAIN_KNOWLEDGE]}
|
|
472
503
|
(id, agent, project_id, domain, fact, source, confidence, last_verified, metadata, embedding)
|
|
473
504
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
474
505
|
ON CONFLICT (id) DO UPDATE SET
|
|
@@ -502,7 +533,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
502
533
|
with self._get_connection() as conn:
|
|
503
534
|
conn.execute(
|
|
504
535
|
f"""
|
|
505
|
-
INSERT INTO {self.schema}.
|
|
536
|
+
INSERT INTO {self.schema}.{self.TABLE_NAMES[MemoryType.ANTI_PATTERNS]}
|
|
506
537
|
(id, agent, project_id, pattern, why_bad, better_alternative,
|
|
507
538
|
occurrence_count, last_seen, created_at, metadata, embedding)
|
|
508
539
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
@@ -548,7 +579,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
548
579
|
with self._get_connection() as conn:
|
|
549
580
|
conn.executemany(
|
|
550
581
|
f"""
|
|
551
|
-
INSERT INTO {self.schema}.
|
|
582
|
+
INSERT INTO {self.schema}.{self.TABLE_NAMES[MemoryType.HEURISTICS]}
|
|
552
583
|
(id, agent, project_id, condition, strategy, confidence,
|
|
553
584
|
occurrence_count, success_count, last_validated, created_at, metadata, embedding)
|
|
554
585
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
@@ -593,7 +624,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
593
624
|
with self._get_connection() as conn:
|
|
594
625
|
conn.executemany(
|
|
595
626
|
f"""
|
|
596
|
-
INSERT INTO {self.schema}.
|
|
627
|
+
INSERT INTO {self.schema}.{self.TABLE_NAMES[MemoryType.OUTCOMES]}
|
|
597
628
|
(id, agent, project_id, task_type, task_description, success,
|
|
598
629
|
strategy_used, duration_ms, error_message, user_feedback, timestamp, metadata, embedding)
|
|
599
630
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
@@ -641,7 +672,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
641
672
|
with self._get_connection() as conn:
|
|
642
673
|
conn.executemany(
|
|
643
674
|
f"""
|
|
644
|
-
INSERT INTO {self.schema}.
|
|
675
|
+
INSERT INTO {self.schema}.{self.TABLE_NAMES[MemoryType.DOMAIN_KNOWLEDGE]}
|
|
645
676
|
(id, agent, project_id, domain, fact, source, confidence, last_verified, metadata, embedding)
|
|
646
677
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
647
678
|
ON CONFLICT (id) DO UPDATE SET
|
|
@@ -689,7 +720,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
689
720
|
# Use pgvector similarity search
|
|
690
721
|
query = f"""
|
|
691
722
|
SELECT *, 1 - (embedding <=> %s::vector) as similarity
|
|
692
|
-
FROM {self.schema}.
|
|
723
|
+
FROM {self.schema}.{self.TABLE_NAMES[MemoryType.HEURISTICS]}
|
|
693
724
|
WHERE project_id = %s AND confidence >= %s
|
|
694
725
|
"""
|
|
695
726
|
params: List[Any] = [
|
|
@@ -708,7 +739,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
708
739
|
# Standard query
|
|
709
740
|
query = f"""
|
|
710
741
|
SELECT *
|
|
711
|
-
FROM {self.schema}.
|
|
742
|
+
FROM {self.schema}.{self.TABLE_NAMES[MemoryType.HEURISTICS]}
|
|
712
743
|
WHERE project_id = %s AND confidence >= %s
|
|
713
744
|
"""
|
|
714
745
|
params = [project_id, min_confidence]
|
|
@@ -745,14 +776,14 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
745
776
|
if embedding and self._pgvector_available:
|
|
746
777
|
query = f"""
|
|
747
778
|
SELECT *, 1 - (embedding <=> %s::vector) as similarity
|
|
748
|
-
FROM {self.schema}.
|
|
779
|
+
FROM {self.schema}.{self.TABLE_NAMES[MemoryType.OUTCOMES]}
|
|
749
780
|
WHERE project_id = %s
|
|
750
781
|
"""
|
|
751
782
|
params: List[Any] = [self._embedding_to_db(embedding), project_id]
|
|
752
783
|
else:
|
|
753
784
|
query = f"""
|
|
754
785
|
SELECT *
|
|
755
|
-
FROM {self.schema}.
|
|
786
|
+
FROM {self.schema}.{self.TABLE_NAMES[MemoryType.OUTCOMES]}
|
|
756
787
|
WHERE project_id = %s
|
|
757
788
|
"""
|
|
758
789
|
params = [project_id]
|
|
@@ -791,7 +822,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
791
822
|
) -> List[UserPreference]:
|
|
792
823
|
"""Get user preferences."""
|
|
793
824
|
with self._get_connection() as conn:
|
|
794
|
-
query = f"SELECT * FROM {self.schema}.
|
|
825
|
+
query = f"SELECT * FROM {self.schema}.{self.TABLE_NAMES[MemoryType.PREFERENCES]} WHERE user_id = %s"
|
|
795
826
|
params: List[Any] = [user_id]
|
|
796
827
|
|
|
797
828
|
if category:
|
|
@@ -816,14 +847,14 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
816
847
|
if embedding and self._pgvector_available:
|
|
817
848
|
query = f"""
|
|
818
849
|
SELECT *, 1 - (embedding <=> %s::vector) as similarity
|
|
819
|
-
FROM {self.schema}.
|
|
850
|
+
FROM {self.schema}.{self.TABLE_NAMES[MemoryType.DOMAIN_KNOWLEDGE]}
|
|
820
851
|
WHERE project_id = %s
|
|
821
852
|
"""
|
|
822
853
|
params: List[Any] = [self._embedding_to_db(embedding), project_id]
|
|
823
854
|
else:
|
|
824
855
|
query = f"""
|
|
825
856
|
SELECT *
|
|
826
|
-
FROM {self.schema}.
|
|
857
|
+
FROM {self.schema}.{self.TABLE_NAMES[MemoryType.DOMAIN_KNOWLEDGE]}
|
|
827
858
|
WHERE project_id = %s
|
|
828
859
|
"""
|
|
829
860
|
params = [project_id]
|
|
@@ -864,14 +895,14 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
864
895
|
if embedding and self._pgvector_available:
|
|
865
896
|
query = f"""
|
|
866
897
|
SELECT *, 1 - (embedding <=> %s::vector) as similarity
|
|
867
|
-
FROM {self.schema}.
|
|
898
|
+
FROM {self.schema}.{self.TABLE_NAMES[MemoryType.ANTI_PATTERNS]}
|
|
868
899
|
WHERE project_id = %s
|
|
869
900
|
"""
|
|
870
901
|
params: List[Any] = [self._embedding_to_db(embedding), project_id]
|
|
871
902
|
else:
|
|
872
903
|
query = f"""
|
|
873
904
|
SELECT *
|
|
874
|
-
FROM {self.schema}.
|
|
905
|
+
FROM {self.schema}.{self.TABLE_NAMES[MemoryType.ANTI_PATTERNS]}
|
|
875
906
|
WHERE project_id = %s
|
|
876
907
|
"""
|
|
877
908
|
params = [project_id]
|
|
@@ -934,7 +965,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
934
965
|
if embedding and self._pgvector_available:
|
|
935
966
|
query = f"""
|
|
936
967
|
SELECT *, 1 - (embedding <=> %s::vector) as similarity
|
|
937
|
-
FROM {self.schema}.
|
|
968
|
+
FROM {self.schema}.{self.TABLE_NAMES[MemoryType.HEURISTICS]}
|
|
938
969
|
WHERE project_id = %s AND confidence >= %s AND agent = ANY(%s)
|
|
939
970
|
ORDER BY similarity DESC LIMIT %s
|
|
940
971
|
"""
|
|
@@ -948,7 +979,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
948
979
|
else:
|
|
949
980
|
query = f"""
|
|
950
981
|
SELECT *
|
|
951
|
-
FROM {self.schema}.
|
|
982
|
+
FROM {self.schema}.{self.TABLE_NAMES[MemoryType.HEURISTICS]}
|
|
952
983
|
WHERE project_id = %s AND confidence >= %s AND agent = ANY(%s)
|
|
953
984
|
ORDER BY confidence DESC LIMIT %s
|
|
954
985
|
"""
|
|
@@ -983,7 +1014,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
983
1014
|
if embedding and self._pgvector_available:
|
|
984
1015
|
query = f"""
|
|
985
1016
|
SELECT *, 1 - (embedding <=> %s::vector) as similarity
|
|
986
|
-
FROM {self.schema}.
|
|
1017
|
+
FROM {self.schema}.{self.TABLE_NAMES[MemoryType.OUTCOMES]}
|
|
987
1018
|
WHERE project_id = %s AND agent = ANY(%s)
|
|
988
1019
|
"""
|
|
989
1020
|
params: List[Any] = [
|
|
@@ -994,7 +1025,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
994
1025
|
else:
|
|
995
1026
|
query = f"""
|
|
996
1027
|
SELECT *
|
|
997
|
-
FROM {self.schema}.
|
|
1028
|
+
FROM {self.schema}.{self.TABLE_NAMES[MemoryType.OUTCOMES]}
|
|
998
1029
|
WHERE project_id = %s AND agent = ANY(%s)
|
|
999
1030
|
"""
|
|
1000
1031
|
params = [project_id, agents]
|
|
@@ -1040,7 +1071,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
1040
1071
|
if embedding and self._pgvector_available:
|
|
1041
1072
|
query = f"""
|
|
1042
1073
|
SELECT *, 1 - (embedding <=> %s::vector) as similarity
|
|
1043
|
-
FROM {self.schema}.
|
|
1074
|
+
FROM {self.schema}.{self.TABLE_NAMES[MemoryType.DOMAIN_KNOWLEDGE]}
|
|
1044
1075
|
WHERE project_id = %s AND agent = ANY(%s)
|
|
1045
1076
|
"""
|
|
1046
1077
|
params: List[Any] = [
|
|
@@ -1051,7 +1082,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
1051
1082
|
else:
|
|
1052
1083
|
query = f"""
|
|
1053
1084
|
SELECT *
|
|
1054
|
-
FROM {self.schema}.
|
|
1085
|
+
FROM {self.schema}.{self.TABLE_NAMES[MemoryType.DOMAIN_KNOWLEDGE]}
|
|
1055
1086
|
WHERE project_id = %s AND agent = ANY(%s)
|
|
1056
1087
|
"""
|
|
1057
1088
|
params = [project_id, agents]
|
|
@@ -1093,7 +1124,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
1093
1124
|
if embedding and self._pgvector_available:
|
|
1094
1125
|
query = f"""
|
|
1095
1126
|
SELECT *, 1 - (embedding <=> %s::vector) as similarity
|
|
1096
|
-
FROM {self.schema}.
|
|
1127
|
+
FROM {self.schema}.{self.TABLE_NAMES[MemoryType.ANTI_PATTERNS]}
|
|
1097
1128
|
WHERE project_id = %s AND agent = ANY(%s)
|
|
1098
1129
|
"""
|
|
1099
1130
|
params: List[Any] = [
|
|
@@ -1104,7 +1135,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
1104
1135
|
else:
|
|
1105
1136
|
query = f"""
|
|
1106
1137
|
SELECT *
|
|
1107
|
-
FROM {self.schema}.
|
|
1138
|
+
FROM {self.schema}.{self.TABLE_NAMES[MemoryType.ANTI_PATTERNS]}
|
|
1108
1139
|
WHERE project_id = %s AND agent = ANY(%s)
|
|
1109
1140
|
"""
|
|
1110
1141
|
params = [project_id, agents]
|
|
@@ -1150,7 +1181,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
1150
1181
|
|
|
1151
1182
|
with self._get_connection() as conn:
|
|
1152
1183
|
cursor = conn.execute(
|
|
1153
|
-
f"UPDATE {self.schema}.
|
|
1184
|
+
f"UPDATE {self.schema}.{self.TABLE_NAMES[MemoryType.HEURISTICS]} SET {', '.join(set_clauses)} WHERE id = %s",
|
|
1154
1185
|
params,
|
|
1155
1186
|
)
|
|
1156
1187
|
conn.commit()
|
|
@@ -1166,7 +1197,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
1166
1197
|
if success:
|
|
1167
1198
|
cursor = conn.execute(
|
|
1168
1199
|
f"""
|
|
1169
|
-
UPDATE {self.schema}.
|
|
1200
|
+
UPDATE {self.schema}.{self.TABLE_NAMES[MemoryType.HEURISTICS]}
|
|
1170
1201
|
SET occurrence_count = occurrence_count + 1,
|
|
1171
1202
|
success_count = success_count + 1,
|
|
1172
1203
|
last_validated = %s
|
|
@@ -1177,7 +1208,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
1177
1208
|
else:
|
|
1178
1209
|
cursor = conn.execute(
|
|
1179
1210
|
f"""
|
|
1180
|
-
UPDATE {self.schema}.
|
|
1211
|
+
UPDATE {self.schema}.{self.TABLE_NAMES[MemoryType.HEURISTICS]}
|
|
1181
1212
|
SET occurrence_count = occurrence_count + 1,
|
|
1182
1213
|
last_validated = %s
|
|
1183
1214
|
WHERE id = %s
|
|
@@ -1195,7 +1226,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
1195
1226
|
"""Update confidence score for a heuristic."""
|
|
1196
1227
|
with self._get_connection() as conn:
|
|
1197
1228
|
cursor = conn.execute(
|
|
1198
|
-
f"UPDATE {self.schema}.
|
|
1229
|
+
f"UPDATE {self.schema}.{self.TABLE_NAMES[MemoryType.HEURISTICS]} SET confidence = %s WHERE id = %s",
|
|
1199
1230
|
(new_confidence, heuristic_id),
|
|
1200
1231
|
)
|
|
1201
1232
|
conn.commit()
|
|
@@ -1209,7 +1240,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
1209
1240
|
"""Update confidence score for domain knowledge."""
|
|
1210
1241
|
with self._get_connection() as conn:
|
|
1211
1242
|
cursor = conn.execute(
|
|
1212
|
-
f"UPDATE {self.schema}.
|
|
1243
|
+
f"UPDATE {self.schema}.{self.TABLE_NAMES[MemoryType.DOMAIN_KNOWLEDGE]} SET confidence = %s WHERE id = %s",
|
|
1213
1244
|
(new_confidence, knowledge_id),
|
|
1214
1245
|
)
|
|
1215
1246
|
conn.commit()
|
|
@@ -1221,7 +1252,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
1221
1252
|
"""Delete a heuristic by ID."""
|
|
1222
1253
|
with self._get_connection() as conn:
|
|
1223
1254
|
cursor = conn.execute(
|
|
1224
|
-
f"DELETE FROM {self.schema}.
|
|
1255
|
+
f"DELETE FROM {self.schema}.{self.TABLE_NAMES[MemoryType.HEURISTICS]} WHERE id = %s",
|
|
1225
1256
|
(heuristic_id,),
|
|
1226
1257
|
)
|
|
1227
1258
|
conn.commit()
|
|
@@ -1231,7 +1262,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
1231
1262
|
"""Delete an outcome by ID."""
|
|
1232
1263
|
with self._get_connection() as conn:
|
|
1233
1264
|
cursor = conn.execute(
|
|
1234
|
-
f"DELETE FROM {self.schema}.
|
|
1265
|
+
f"DELETE FROM {self.schema}.{self.TABLE_NAMES[MemoryType.OUTCOMES]} WHERE id = %s",
|
|
1235
1266
|
(outcome_id,),
|
|
1236
1267
|
)
|
|
1237
1268
|
conn.commit()
|
|
@@ -1241,7 +1272,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
1241
1272
|
"""Delete domain knowledge by ID."""
|
|
1242
1273
|
with self._get_connection() as conn:
|
|
1243
1274
|
cursor = conn.execute(
|
|
1244
|
-
f"DELETE FROM {self.schema}.
|
|
1275
|
+
f"DELETE FROM {self.schema}.{self.TABLE_NAMES[MemoryType.DOMAIN_KNOWLEDGE]} WHERE id = %s",
|
|
1245
1276
|
(knowledge_id,),
|
|
1246
1277
|
)
|
|
1247
1278
|
conn.commit()
|
|
@@ -1251,7 +1282,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
1251
1282
|
"""Delete an anti-pattern by ID."""
|
|
1252
1283
|
with self._get_connection() as conn:
|
|
1253
1284
|
cursor = conn.execute(
|
|
1254
|
-
f"DELETE FROM {self.schema}.
|
|
1285
|
+
f"DELETE FROM {self.schema}.{self.TABLE_NAMES[MemoryType.ANTI_PATTERNS]} WHERE id = %s",
|
|
1255
1286
|
(anti_pattern_id,),
|
|
1256
1287
|
)
|
|
1257
1288
|
conn.commit()
|
|
@@ -1265,7 +1296,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
1265
1296
|
) -> int:
|
|
1266
1297
|
"""Delete old outcomes."""
|
|
1267
1298
|
with self._get_connection() as conn:
|
|
1268
|
-
query = f"DELETE FROM {self.schema}.
|
|
1299
|
+
query = f"DELETE FROM {self.schema}.{self.TABLE_NAMES[MemoryType.OUTCOMES]} WHERE project_id = %s AND timestamp < %s"
|
|
1269
1300
|
params: List[Any] = [project_id, older_than]
|
|
1270
1301
|
|
|
1271
1302
|
if agent:
|
|
@@ -1287,7 +1318,7 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
1287
1318
|
) -> int:
|
|
1288
1319
|
"""Delete low-confidence heuristics."""
|
|
1289
1320
|
with self._get_connection() as conn:
|
|
1290
|
-
query = f"DELETE FROM {self.schema}.
|
|
1321
|
+
query = f"DELETE FROM {self.schema}.{self.TABLE_NAMES[MemoryType.HEURISTICS]} WHERE project_id = %s AND confidence < %s"
|
|
1291
1322
|
params: List[Any] = [project_id, below_confidence]
|
|
1292
1323
|
|
|
1293
1324
|
if agent:
|
|
@@ -1317,29 +1348,25 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
1317
1348
|
}
|
|
1318
1349
|
|
|
1319
1350
|
with self._get_connection() as conn:
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
query
|
|
1332
|
-
params
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
f"SELECT COUNT(*) as count FROM {self.schema}.alma_preferences"
|
|
1340
|
-
)
|
|
1341
|
-
row = cursor.fetchone()
|
|
1342
|
-
stats["preferences_count"] = row["count"] if row else 0
|
|
1351
|
+
# Use canonical memory types for stats
|
|
1352
|
+
for memory_type in MemoryType.ALL:
|
|
1353
|
+
table = self.TABLE_NAMES[memory_type]
|
|
1354
|
+
if memory_type == MemoryType.PREFERENCES:
|
|
1355
|
+
# Preferences don't have project_id
|
|
1356
|
+
cursor = conn.execute(
|
|
1357
|
+
f"SELECT COUNT(*) as count FROM {self.schema}.{table}"
|
|
1358
|
+
)
|
|
1359
|
+
row = cursor.fetchone()
|
|
1360
|
+
stats[f"{memory_type}_count"] = row["count"] if row else 0
|
|
1361
|
+
else:
|
|
1362
|
+
query = f"SELECT COUNT(*) as count FROM {self.schema}.{table} WHERE project_id = %s"
|
|
1363
|
+
params: List[Any] = [project_id]
|
|
1364
|
+
if agent:
|
|
1365
|
+
query += " AND agent = %s"
|
|
1366
|
+
params.append(agent)
|
|
1367
|
+
cursor = conn.execute(query, params)
|
|
1368
|
+
row = cursor.fetchone()
|
|
1369
|
+
stats[f"{memory_type}_count"] = row["count"] if row else 0
|
|
1343
1370
|
|
|
1344
1371
|
stats["total_count"] = sum(
|
|
1345
1372
|
stats.get(k, 0) for k in stats if k.endswith("_count")
|
|
@@ -1450,3 +1477,83 @@ class PostgreSQLStorage(StorageBackend):
|
|
|
1450
1477
|
"""Close connection pool."""
|
|
1451
1478
|
if self._pool:
|
|
1452
1479
|
self._pool.close()
|
|
1480
|
+
|
|
1481
|
+
# ==================== MIGRATION SUPPORT ====================
|
|
1482
|
+
|
|
1483
|
+
def _get_version_store(self):
|
|
1484
|
+
"""Get or create the version store."""
|
|
1485
|
+
if self._version_store is None:
|
|
1486
|
+
from alma.storage.migrations.version_stores import PostgreSQLVersionStore
|
|
1487
|
+
|
|
1488
|
+
self._version_store = PostgreSQLVersionStore(self._pool, self.schema)
|
|
1489
|
+
return self._version_store
|
|
1490
|
+
|
|
1491
|
+
def _get_migration_runner(self):
|
|
1492
|
+
"""Get or create the migration runner."""
|
|
1493
|
+
if self._migration_runner is None:
|
|
1494
|
+
from alma.storage.migrations.runner import MigrationRunner
|
|
1495
|
+
from alma.storage.migrations.versions import v1_0_0 # noqa: F401
|
|
1496
|
+
|
|
1497
|
+
self._migration_runner = MigrationRunner(
|
|
1498
|
+
version_store=self._get_version_store(),
|
|
1499
|
+
backend="postgresql",
|
|
1500
|
+
)
|
|
1501
|
+
return self._migration_runner
|
|
1502
|
+
|
|
1503
|
+
def _ensure_migrated(self) -> None:
|
|
1504
|
+
"""Ensure database is migrated to latest version."""
|
|
1505
|
+
runner = self._get_migration_runner()
|
|
1506
|
+
if runner.needs_migration():
|
|
1507
|
+
with self._get_connection() as conn:
|
|
1508
|
+
applied = runner.migrate(conn)
|
|
1509
|
+
if applied:
|
|
1510
|
+
logger.info(f"Applied {len(applied)} migrations: {applied}")
|
|
1511
|
+
|
|
1512
|
+
def get_schema_version(self) -> Optional[str]:
|
|
1513
|
+
"""Get the current schema version."""
|
|
1514
|
+
return self._get_version_store().get_current_version()
|
|
1515
|
+
|
|
1516
|
+
def get_migration_status(self) -> Dict[str, Any]:
|
|
1517
|
+
"""Get migration status information."""
|
|
1518
|
+
runner = self._get_migration_runner()
|
|
1519
|
+
status = runner.get_status()
|
|
1520
|
+
status["migration_supported"] = True
|
|
1521
|
+
return status
|
|
1522
|
+
|
|
1523
|
+
def migrate(
|
|
1524
|
+
self,
|
|
1525
|
+
target_version: Optional[str] = None,
|
|
1526
|
+
dry_run: bool = False,
|
|
1527
|
+
) -> List[str]:
|
|
1528
|
+
"""
|
|
1529
|
+
Apply pending schema migrations.
|
|
1530
|
+
|
|
1531
|
+
Args:
|
|
1532
|
+
target_version: Optional target version (applies all if not specified)
|
|
1533
|
+
dry_run: If True, show what would be done without making changes
|
|
1534
|
+
|
|
1535
|
+
Returns:
|
|
1536
|
+
List of applied migration versions
|
|
1537
|
+
"""
|
|
1538
|
+
runner = self._get_migration_runner()
|
|
1539
|
+
with self._get_connection() as conn:
|
|
1540
|
+
return runner.migrate(conn, target_version=target_version, dry_run=dry_run)
|
|
1541
|
+
|
|
1542
|
+
def rollback(
|
|
1543
|
+
self,
|
|
1544
|
+
target_version: str,
|
|
1545
|
+
dry_run: bool = False,
|
|
1546
|
+
) -> List[str]:
|
|
1547
|
+
"""
|
|
1548
|
+
Roll back schema to a previous version.
|
|
1549
|
+
|
|
1550
|
+
Args:
|
|
1551
|
+
target_version: Version to roll back to
|
|
1552
|
+
dry_run: If True, show what would be done without making changes
|
|
1553
|
+
|
|
1554
|
+
Returns:
|
|
1555
|
+
List of rolled back migration versions
|
|
1556
|
+
"""
|
|
1557
|
+
runner = self._get_migration_runner()
|
|
1558
|
+
with self._get_connection() as conn:
|
|
1559
|
+
return runner.rollback(conn, target_version=target_version, dry_run=dry_run)
|