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.
- alma/__init__.py +296 -226
- alma/compression/__init__.py +33 -0
- alma/compression/pipeline.py +980 -0
- alma/confidence/__init__.py +47 -47
- alma/confidence/engine.py +540 -540
- alma/confidence/types.py +351 -351
- alma/config/loader.py +157 -157
- alma/consolidation/__init__.py +23 -23
- alma/consolidation/engine.py +678 -678
- alma/consolidation/prompts.py +84 -84
- alma/core.py +1189 -430
- alma/domains/__init__.py +30 -30
- alma/domains/factory.py +359 -359
- alma/domains/schemas.py +448 -448
- alma/domains/types.py +272 -272
- alma/events/__init__.py +75 -75
- alma/events/emitter.py +285 -284
- alma/events/storage_mixin.py +246 -246
- alma/events/types.py +126 -126
- alma/events/webhook.py +425 -425
- alma/exceptions.py +49 -49
- alma/extraction/__init__.py +31 -31
- alma/extraction/auto_learner.py +265 -265
- alma/extraction/extractor.py +420 -420
- alma/graph/__init__.py +106 -106
- alma/graph/backends/__init__.py +32 -32
- alma/graph/backends/kuzu.py +624 -624
- alma/graph/backends/memgraph.py +432 -432
- alma/graph/backends/memory.py +236 -236
- alma/graph/backends/neo4j.py +417 -417
- alma/graph/base.py +159 -159
- alma/graph/extraction.py +198 -198
- alma/graph/store.py +860 -860
- alma/harness/__init__.py +35 -35
- alma/harness/base.py +386 -386
- alma/harness/domains.py +705 -705
- alma/initializer/__init__.py +37 -37
- alma/initializer/initializer.py +418 -418
- alma/initializer/types.py +250 -250
- alma/integration/__init__.py +62 -62
- alma/integration/claude_agents.py +444 -444
- alma/integration/helena.py +423 -423
- alma/integration/victor.py +471 -471
- alma/learning/__init__.py +101 -86
- alma/learning/decay.py +878 -0
- alma/learning/forgetting.py +1446 -1446
- alma/learning/heuristic_extractor.py +390 -390
- alma/learning/protocols.py +374 -374
- alma/learning/validation.py +346 -346
- alma/mcp/__init__.py +123 -45
- alma/mcp/__main__.py +156 -156
- alma/mcp/resources.py +122 -122
- alma/mcp/server.py +955 -591
- alma/mcp/tools.py +3254 -509
- alma/observability/__init__.py +91 -84
- alma/observability/config.py +302 -302
- alma/observability/guidelines.py +170 -0
- alma/observability/logging.py +424 -424
- alma/observability/metrics.py +583 -583
- alma/observability/tracing.py +440 -440
- alma/progress/__init__.py +21 -21
- alma/progress/tracker.py +607 -607
- alma/progress/types.py +250 -250
- alma/retrieval/__init__.py +134 -53
- alma/retrieval/budget.py +525 -0
- alma/retrieval/cache.py +1304 -1061
- alma/retrieval/embeddings.py +202 -202
- alma/retrieval/engine.py +850 -427
- alma/retrieval/modes.py +365 -0
- alma/retrieval/progressive.py +560 -0
- alma/retrieval/scoring.py +344 -344
- alma/retrieval/trust_scoring.py +637 -0
- alma/retrieval/verification.py +797 -0
- alma/session/__init__.py +19 -19
- alma/session/manager.py +442 -399
- alma/session/types.py +288 -288
- alma/storage/__init__.py +101 -90
- alma/storage/archive.py +233 -0
- alma/storage/azure_cosmos.py +1259 -1259
- alma/storage/base.py +1083 -583
- alma/storage/chroma.py +1443 -1443
- alma/storage/constants.py +103 -103
- alma/storage/file_based.py +614 -614
- alma/storage/migrations/__init__.py +21 -21
- alma/storage/migrations/base.py +321 -321
- alma/storage/migrations/runner.py +323 -323
- alma/storage/migrations/version_stores.py +337 -337
- alma/storage/migrations/versions/__init__.py +11 -11
- alma/storage/migrations/versions/v1_0_0.py +373 -373
- alma/storage/migrations/versions/v1_1_0_workflow_context.py +551 -0
- alma/storage/pinecone.py +1080 -1080
- alma/storage/postgresql.py +1948 -1559
- alma/storage/qdrant.py +1306 -1306
- alma/storage/sqlite_local.py +3041 -1457
- alma/testing/__init__.py +46 -46
- alma/testing/factories.py +301 -301
- alma/testing/mocks.py +389 -389
- alma/types.py +292 -264
- alma/utils/__init__.py +19 -0
- alma/utils/tokenizer.py +521 -0
- alma/workflow/__init__.py +83 -0
- alma/workflow/artifacts.py +170 -0
- alma/workflow/checkpoint.py +311 -0
- alma/workflow/context.py +228 -0
- alma/workflow/outcomes.py +189 -0
- alma/workflow/reducers.py +393 -0
- {alma_memory-0.5.1.dist-info → alma_memory-0.7.0.dist-info}/METADATA +210 -72
- alma_memory-0.7.0.dist-info/RECORD +112 -0
- alma_memory-0.5.1.dist-info/RECORD +0 -93
- {alma_memory-0.5.1.dist-info → alma_memory-0.7.0.dist-info}/WHEEL +0 -0
- {alma_memory-0.5.1.dist-info → alma_memory-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ALMA Schema Migration v1.1.0 - Workflow Context Layer.
|
|
3
|
+
|
|
4
|
+
This migration adds support for AGtestari Workflow Studio integration:
|
|
5
|
+
- Checkpoints table: Crash recovery and state persistence
|
|
6
|
+
- Workflow Outcomes table: Learning from completed workflows
|
|
7
|
+
- Artifact Links table: Connecting artifacts to memories
|
|
8
|
+
- Workflow scope columns on existing tables
|
|
9
|
+
|
|
10
|
+
Sprint 0 Task 0.2, 0.3, 0.4, 0.7
|
|
11
|
+
Designed by: @data-analyst (Dara)
|
|
12
|
+
Reviewed by: @architect (Aria)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from alma.storage.migrations.base import Migration, register_migration
|
|
18
|
+
|
|
19
|
+
# =============================================================================
|
|
20
|
+
# POSTGRESQL MIGRATIONS
|
|
21
|
+
# =============================================================================
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@register_migration(backend="postgresql")
|
|
25
|
+
class PostgreSQLWorkflowContextMigration(Migration):
|
|
26
|
+
"""
|
|
27
|
+
PostgreSQL migration for workflow context layer.
|
|
28
|
+
|
|
29
|
+
Includes pgvector support for semantic search on workflow outcomes.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
version = "1.1.0"
|
|
33
|
+
description = (
|
|
34
|
+
"Add workflow context layer (checkpoints, workflow_outcomes, artifact_links)"
|
|
35
|
+
)
|
|
36
|
+
depends_on = "1.0.0"
|
|
37
|
+
|
|
38
|
+
def upgrade(self, connection: Any) -> None:
|
|
39
|
+
"""Apply workflow context schema changes."""
|
|
40
|
+
cursor = connection.cursor()
|
|
41
|
+
|
|
42
|
+
# Get schema from connection or default to public
|
|
43
|
+
schema = getattr(connection, "_schema", "public")
|
|
44
|
+
|
|
45
|
+
# Check if pgvector is available
|
|
46
|
+
cursor.execute("""
|
|
47
|
+
SELECT EXISTS (
|
|
48
|
+
SELECT 1 FROM pg_extension WHERE extname = 'vector'
|
|
49
|
+
)
|
|
50
|
+
""")
|
|
51
|
+
has_pgvector = cursor.fetchone()[0]
|
|
52
|
+
|
|
53
|
+
# Determine embedding type
|
|
54
|
+
# Default embedding dim for all-MiniLM-L6-v2
|
|
55
|
+
embedding_dim = 384
|
|
56
|
+
embedding_type = f"VECTOR({embedding_dim})" if has_pgvector else "BYTEA"
|
|
57
|
+
|
|
58
|
+
# =====================================================================
|
|
59
|
+
# TABLE 1: Checkpoints - Crash recovery and state persistence
|
|
60
|
+
# =====================================================================
|
|
61
|
+
cursor.execute(f"""
|
|
62
|
+
CREATE TABLE IF NOT EXISTS {schema}.alma_checkpoints (
|
|
63
|
+
-- Primary key
|
|
64
|
+
id TEXT PRIMARY KEY,
|
|
65
|
+
|
|
66
|
+
-- Workflow context (required)
|
|
67
|
+
run_id TEXT NOT NULL,
|
|
68
|
+
node_id TEXT NOT NULL,
|
|
69
|
+
|
|
70
|
+
-- State data
|
|
71
|
+
state_json JSONB NOT NULL,
|
|
72
|
+
state_hash TEXT NOT NULL, -- SHA256 hash for change detection
|
|
73
|
+
|
|
74
|
+
-- Sequencing
|
|
75
|
+
sequence_number INTEGER NOT NULL,
|
|
76
|
+
|
|
77
|
+
-- Parallel execution support
|
|
78
|
+
branch_id TEXT, -- NULL for main branch
|
|
79
|
+
parent_checkpoint_id TEXT REFERENCES {schema}.alma_checkpoints(id),
|
|
80
|
+
|
|
81
|
+
-- Timestamps
|
|
82
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
83
|
+
|
|
84
|
+
-- Extensibility
|
|
85
|
+
metadata JSONB,
|
|
86
|
+
|
|
87
|
+
-- Constraints
|
|
88
|
+
CONSTRAINT uk_checkpoint_run_seq UNIQUE (run_id, sequence_number)
|
|
89
|
+
)
|
|
90
|
+
""")
|
|
91
|
+
|
|
92
|
+
# Indexes for checkpoint queries
|
|
93
|
+
cursor.execute(f"""
|
|
94
|
+
CREATE INDEX IF NOT EXISTS idx_checkpoints_run_seq
|
|
95
|
+
ON {schema}.alma_checkpoints(run_id, sequence_number DESC)
|
|
96
|
+
""")
|
|
97
|
+
cursor.execute(f"""
|
|
98
|
+
CREATE INDEX IF NOT EXISTS idx_checkpoints_run_branch
|
|
99
|
+
ON {schema}.alma_checkpoints(run_id, branch_id)
|
|
100
|
+
WHERE branch_id IS NOT NULL
|
|
101
|
+
""")
|
|
102
|
+
cursor.execute(f"""
|
|
103
|
+
CREATE INDEX IF NOT EXISTS idx_checkpoints_created
|
|
104
|
+
ON {schema}.alma_checkpoints(created_at)
|
|
105
|
+
""")
|
|
106
|
+
|
|
107
|
+
# Comment on table
|
|
108
|
+
cursor.execute(f"""
|
|
109
|
+
COMMENT ON TABLE {schema}.alma_checkpoints IS
|
|
110
|
+
'Workflow state checkpoints for crash recovery. Each checkpoint captures state after a node completes.'
|
|
111
|
+
""")
|
|
112
|
+
|
|
113
|
+
# =====================================================================
|
|
114
|
+
# TABLE 2: Workflow Outcomes - Learning from completed workflows
|
|
115
|
+
# =====================================================================
|
|
116
|
+
cursor.execute(f"""
|
|
117
|
+
CREATE TABLE IF NOT EXISTS {schema}.alma_workflow_outcomes (
|
|
118
|
+
-- Primary key
|
|
119
|
+
id TEXT PRIMARY KEY,
|
|
120
|
+
|
|
121
|
+
-- Multi-tenant hierarchy
|
|
122
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
123
|
+
workflow_id TEXT NOT NULL,
|
|
124
|
+
workflow_version TEXT DEFAULT '1.0',
|
|
125
|
+
run_id TEXT NOT NULL UNIQUE, -- One outcome per run
|
|
126
|
+
|
|
127
|
+
-- Outcome data
|
|
128
|
+
success BOOLEAN NOT NULL,
|
|
129
|
+
duration_ms INTEGER NOT NULL,
|
|
130
|
+
|
|
131
|
+
-- Node statistics
|
|
132
|
+
node_count INTEGER NOT NULL,
|
|
133
|
+
nodes_succeeded INTEGER NOT NULL DEFAULT 0,
|
|
134
|
+
nodes_failed INTEGER NOT NULL DEFAULT 0,
|
|
135
|
+
|
|
136
|
+
-- Error tracking
|
|
137
|
+
error_message TEXT,
|
|
138
|
+
|
|
139
|
+
-- Artifacts (stored as JSON array of ArtifactRef)
|
|
140
|
+
artifacts_json JSONB,
|
|
141
|
+
|
|
142
|
+
-- Learning metrics
|
|
143
|
+
learnings_extracted INTEGER DEFAULT 0,
|
|
144
|
+
|
|
145
|
+
-- Timestamps
|
|
146
|
+
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
147
|
+
|
|
148
|
+
-- Semantic search (pgvector or fallback)
|
|
149
|
+
embedding {embedding_type},
|
|
150
|
+
|
|
151
|
+
-- Extensibility
|
|
152
|
+
metadata JSONB,
|
|
153
|
+
|
|
154
|
+
-- Constraints
|
|
155
|
+
CONSTRAINT chk_nodes_count CHECK (
|
|
156
|
+
nodes_succeeded + nodes_failed <= node_count
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
""")
|
|
160
|
+
|
|
161
|
+
# Indexes for workflow outcome queries
|
|
162
|
+
cursor.execute(f"""
|
|
163
|
+
CREATE INDEX IF NOT EXISTS idx_wo_tenant
|
|
164
|
+
ON {schema}.alma_workflow_outcomes(tenant_id)
|
|
165
|
+
""")
|
|
166
|
+
cursor.execute(f"""
|
|
167
|
+
CREATE INDEX IF NOT EXISTS idx_wo_workflow
|
|
168
|
+
ON {schema}.alma_workflow_outcomes(tenant_id, workflow_id)
|
|
169
|
+
""")
|
|
170
|
+
cursor.execute(f"""
|
|
171
|
+
CREATE INDEX IF NOT EXISTS idx_wo_success
|
|
172
|
+
ON {schema}.alma_workflow_outcomes(tenant_id, success)
|
|
173
|
+
""")
|
|
174
|
+
cursor.execute(f"""
|
|
175
|
+
CREATE INDEX IF NOT EXISTS idx_wo_timestamp
|
|
176
|
+
ON {schema}.alma_workflow_outcomes(timestamp DESC)
|
|
177
|
+
""")
|
|
178
|
+
|
|
179
|
+
# pgvector index for semantic search (if available)
|
|
180
|
+
if has_pgvector:
|
|
181
|
+
try:
|
|
182
|
+
cursor.execute(f"""
|
|
183
|
+
CREATE INDEX IF NOT EXISTS idx_wo_embedding
|
|
184
|
+
ON {schema}.alma_workflow_outcomes
|
|
185
|
+
USING ivfflat (embedding vector_cosine_ops)
|
|
186
|
+
WITH (lists = 100)
|
|
187
|
+
""")
|
|
188
|
+
except Exception:
|
|
189
|
+
# IVFFlat requires data to build, skip if table is empty
|
|
190
|
+
pass
|
|
191
|
+
|
|
192
|
+
# Comment on table
|
|
193
|
+
cursor.execute(f"""
|
|
194
|
+
COMMENT ON TABLE {schema}.alma_workflow_outcomes IS
|
|
195
|
+
'Aggregated outcomes from completed workflow runs. Used for learning patterns across workflows.'
|
|
196
|
+
""")
|
|
197
|
+
|
|
198
|
+
# =====================================================================
|
|
199
|
+
# TABLE 3: Artifact Links - Connecting artifacts to memories
|
|
200
|
+
# =====================================================================
|
|
201
|
+
cursor.execute(f"""
|
|
202
|
+
CREATE TABLE IF NOT EXISTS {schema}.alma_artifact_links (
|
|
203
|
+
-- Primary key
|
|
204
|
+
id TEXT PRIMARY KEY,
|
|
205
|
+
|
|
206
|
+
-- Link to memory item
|
|
207
|
+
memory_id TEXT NOT NULL,
|
|
208
|
+
memory_type TEXT NOT NULL, -- 'heuristic', 'outcome', 'domain_knowledge', etc.
|
|
209
|
+
|
|
210
|
+
-- Artifact reference
|
|
211
|
+
artifact_id TEXT NOT NULL,
|
|
212
|
+
artifact_type TEXT NOT NULL, -- 'screenshot', 'report', 'log', etc.
|
|
213
|
+
|
|
214
|
+
-- Storage location (Cloudflare R2)
|
|
215
|
+
storage_path TEXT NOT NULL, -- e.g., 'r2://alma-artifacts/tenant/workflow/artifact.png'
|
|
216
|
+
|
|
217
|
+
-- Integrity
|
|
218
|
+
content_hash TEXT NOT NULL, -- SHA256 for verification
|
|
219
|
+
size_bytes INTEGER NOT NULL,
|
|
220
|
+
|
|
221
|
+
-- Timestamps
|
|
222
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
223
|
+
|
|
224
|
+
-- Extensibility
|
|
225
|
+
metadata JSONB,
|
|
226
|
+
|
|
227
|
+
-- Constraints
|
|
228
|
+
CONSTRAINT chk_size_positive CHECK (size_bytes > 0)
|
|
229
|
+
)
|
|
230
|
+
""")
|
|
231
|
+
|
|
232
|
+
# Indexes for artifact queries
|
|
233
|
+
cursor.execute(f"""
|
|
234
|
+
CREATE INDEX IF NOT EXISTS idx_artifact_memory
|
|
235
|
+
ON {schema}.alma_artifact_links(memory_id, memory_type)
|
|
236
|
+
""")
|
|
237
|
+
cursor.execute(f"""
|
|
238
|
+
CREATE INDEX IF NOT EXISTS idx_artifact_type
|
|
239
|
+
ON {schema}.alma_artifact_links(artifact_type)
|
|
240
|
+
""")
|
|
241
|
+
cursor.execute(f"""
|
|
242
|
+
CREATE INDEX IF NOT EXISTS idx_artifact_created
|
|
243
|
+
ON {schema}.alma_artifact_links(created_at)
|
|
244
|
+
""")
|
|
245
|
+
|
|
246
|
+
# Comment on table
|
|
247
|
+
cursor.execute(f"""
|
|
248
|
+
COMMENT ON TABLE {schema}.alma_artifact_links IS
|
|
249
|
+
'Links between memory items and external artifacts stored in Cloudflare R2.'
|
|
250
|
+
""")
|
|
251
|
+
|
|
252
|
+
# =====================================================================
|
|
253
|
+
# ALTER EXISTING TABLES: Add workflow scope columns
|
|
254
|
+
# =====================================================================
|
|
255
|
+
|
|
256
|
+
# Add workflow columns to heuristics (if they don't exist)
|
|
257
|
+
self._add_column_if_not_exists(
|
|
258
|
+
cursor, schema, "alma_heuristics", "tenant_id", "TEXT DEFAULT 'default'"
|
|
259
|
+
)
|
|
260
|
+
self._add_column_if_not_exists(
|
|
261
|
+
cursor, schema, "alma_heuristics", "workflow_id", "TEXT"
|
|
262
|
+
)
|
|
263
|
+
self._add_column_if_not_exists(
|
|
264
|
+
cursor, schema, "alma_heuristics", "run_id", "TEXT"
|
|
265
|
+
)
|
|
266
|
+
self._add_column_if_not_exists(
|
|
267
|
+
cursor, schema, "alma_heuristics", "node_id", "TEXT"
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
# Add workflow columns to outcomes
|
|
271
|
+
self._add_column_if_not_exists(
|
|
272
|
+
cursor, schema, "alma_outcomes", "tenant_id", "TEXT DEFAULT 'default'"
|
|
273
|
+
)
|
|
274
|
+
self._add_column_if_not_exists(
|
|
275
|
+
cursor, schema, "alma_outcomes", "workflow_id", "TEXT"
|
|
276
|
+
)
|
|
277
|
+
self._add_column_if_not_exists(
|
|
278
|
+
cursor, schema, "alma_outcomes", "run_id", "TEXT"
|
|
279
|
+
)
|
|
280
|
+
self._add_column_if_not_exists(
|
|
281
|
+
cursor, schema, "alma_outcomes", "node_id", "TEXT"
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
# Add workflow columns to domain_knowledge
|
|
285
|
+
self._add_column_if_not_exists(
|
|
286
|
+
cursor,
|
|
287
|
+
schema,
|
|
288
|
+
"alma_domain_knowledge",
|
|
289
|
+
"tenant_id",
|
|
290
|
+
"TEXT DEFAULT 'default'",
|
|
291
|
+
)
|
|
292
|
+
self._add_column_if_not_exists(
|
|
293
|
+
cursor, schema, "alma_domain_knowledge", "workflow_id", "TEXT"
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# Add workflow columns to anti_patterns
|
|
297
|
+
self._add_column_if_not_exists(
|
|
298
|
+
cursor, schema, "alma_anti_patterns", "tenant_id", "TEXT DEFAULT 'default'"
|
|
299
|
+
)
|
|
300
|
+
self._add_column_if_not_exists(
|
|
301
|
+
cursor, schema, "alma_anti_patterns", "workflow_id", "TEXT"
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Add indexes for scope filtering
|
|
305
|
+
cursor.execute(f"""
|
|
306
|
+
CREATE INDEX IF NOT EXISTS idx_heuristics_tenant
|
|
307
|
+
ON {schema}.alma_heuristics(tenant_id)
|
|
308
|
+
WHERE tenant_id IS NOT NULL
|
|
309
|
+
""")
|
|
310
|
+
cursor.execute(f"""
|
|
311
|
+
CREATE INDEX IF NOT EXISTS idx_heuristics_workflow
|
|
312
|
+
ON {schema}.alma_heuristics(workflow_id)
|
|
313
|
+
WHERE workflow_id IS NOT NULL
|
|
314
|
+
""")
|
|
315
|
+
cursor.execute(f"""
|
|
316
|
+
CREATE INDEX IF NOT EXISTS idx_outcomes_tenant
|
|
317
|
+
ON {schema}.alma_outcomes(tenant_id)
|
|
318
|
+
WHERE tenant_id IS NOT NULL
|
|
319
|
+
""")
|
|
320
|
+
cursor.execute(f"""
|
|
321
|
+
CREATE INDEX IF NOT EXISTS idx_outcomes_workflow
|
|
322
|
+
ON {schema}.alma_outcomes(workflow_id)
|
|
323
|
+
WHERE workflow_id IS NOT NULL
|
|
324
|
+
""")
|
|
325
|
+
|
|
326
|
+
connection.commit()
|
|
327
|
+
|
|
328
|
+
def _add_column_if_not_exists(
|
|
329
|
+
self, cursor: Any, schema: str, table: str, column: str, definition: str
|
|
330
|
+
) -> None:
|
|
331
|
+
"""Safely add a column if it doesn't exist."""
|
|
332
|
+
cursor.execute(f"""
|
|
333
|
+
DO $$
|
|
334
|
+
BEGIN
|
|
335
|
+
IF NOT EXISTS (
|
|
336
|
+
SELECT 1 FROM information_schema.columns
|
|
337
|
+
WHERE table_schema = '{schema}'
|
|
338
|
+
AND table_name = '{table}'
|
|
339
|
+
AND column_name = '{column}'
|
|
340
|
+
) THEN
|
|
341
|
+
ALTER TABLE {schema}.{table} ADD COLUMN {column} {definition};
|
|
342
|
+
END IF;
|
|
343
|
+
END
|
|
344
|
+
$$;
|
|
345
|
+
""")
|
|
346
|
+
|
|
347
|
+
def downgrade(self, connection: Any) -> None:
|
|
348
|
+
"""Revert workflow context schema changes."""
|
|
349
|
+
cursor = connection.cursor()
|
|
350
|
+
schema = getattr(connection, "_schema", "public")
|
|
351
|
+
|
|
352
|
+
# Drop new tables (in reverse dependency order)
|
|
353
|
+
cursor.execute(f"DROP TABLE IF EXISTS {schema}.alma_artifact_links CASCADE")
|
|
354
|
+
cursor.execute(f"DROP TABLE IF EXISTS {schema}.alma_workflow_outcomes CASCADE")
|
|
355
|
+
cursor.execute(f"DROP TABLE IF EXISTS {schema}.alma_checkpoints CASCADE")
|
|
356
|
+
|
|
357
|
+
# Note: We don't remove columns from existing tables in downgrade
|
|
358
|
+
# as it could cause data loss. They are nullable and won't affect existing code.
|
|
359
|
+
|
|
360
|
+
connection.commit()
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
# =============================================================================
|
|
364
|
+
# SQLITE MIGRATIONS
|
|
365
|
+
# =============================================================================
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
@register_migration(backend="sqlite")
|
|
369
|
+
class SQLiteWorkflowContextMigration(Migration):
|
|
370
|
+
"""
|
|
371
|
+
SQLite migration for workflow context layer.
|
|
372
|
+
|
|
373
|
+
Uses BLOB for embeddings (no pgvector equivalent in SQLite).
|
|
374
|
+
"""
|
|
375
|
+
|
|
376
|
+
version = "1.1.0"
|
|
377
|
+
description = (
|
|
378
|
+
"Add workflow context layer (checkpoints, workflow_outcomes, artifact_links)"
|
|
379
|
+
)
|
|
380
|
+
depends_on = "1.0.0"
|
|
381
|
+
|
|
382
|
+
def upgrade(self, connection: Any) -> None:
|
|
383
|
+
"""Apply workflow context schema changes."""
|
|
384
|
+
cursor = connection.cursor()
|
|
385
|
+
|
|
386
|
+
# =====================================================================
|
|
387
|
+
# TABLE 1: Checkpoints
|
|
388
|
+
# Matches alma.workflow.checkpoint.Checkpoint dataclass
|
|
389
|
+
# =====================================================================
|
|
390
|
+
cursor.execute("""
|
|
391
|
+
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
392
|
+
id TEXT PRIMARY KEY,
|
|
393
|
+
run_id TEXT NOT NULL,
|
|
394
|
+
node_id TEXT NOT NULL,
|
|
395
|
+
state TEXT NOT NULL,
|
|
396
|
+
sequence_number INTEGER DEFAULT 0,
|
|
397
|
+
branch_id TEXT,
|
|
398
|
+
parent_checkpoint_id TEXT,
|
|
399
|
+
state_hash TEXT,
|
|
400
|
+
metadata TEXT,
|
|
401
|
+
created_at TEXT NOT NULL
|
|
402
|
+
)
|
|
403
|
+
""")
|
|
404
|
+
|
|
405
|
+
cursor.execute("""
|
|
406
|
+
CREATE INDEX IF NOT EXISTS idx_checkpoints_run
|
|
407
|
+
ON checkpoints(run_id)
|
|
408
|
+
""")
|
|
409
|
+
cursor.execute("""
|
|
410
|
+
CREATE INDEX IF NOT EXISTS idx_checkpoints_run_branch
|
|
411
|
+
ON checkpoints(run_id, branch_id)
|
|
412
|
+
""")
|
|
413
|
+
cursor.execute("""
|
|
414
|
+
CREATE INDEX IF NOT EXISTS idx_checkpoints_run_seq
|
|
415
|
+
ON checkpoints(run_id, sequence_number DESC)
|
|
416
|
+
""")
|
|
417
|
+
|
|
418
|
+
# =====================================================================
|
|
419
|
+
# TABLE 2: Workflow Outcomes
|
|
420
|
+
# Matches alma.workflow.outcomes.WorkflowOutcome dataclass
|
|
421
|
+
# =====================================================================
|
|
422
|
+
cursor.execute("""
|
|
423
|
+
CREATE TABLE IF NOT EXISTS workflow_outcomes (
|
|
424
|
+
id TEXT PRIMARY KEY,
|
|
425
|
+
tenant_id TEXT,
|
|
426
|
+
workflow_id TEXT NOT NULL,
|
|
427
|
+
run_id TEXT NOT NULL,
|
|
428
|
+
agent TEXT NOT NULL,
|
|
429
|
+
project_id TEXT NOT NULL,
|
|
430
|
+
result TEXT NOT NULL,
|
|
431
|
+
summary TEXT,
|
|
432
|
+
strategies_used TEXT,
|
|
433
|
+
successful_patterns TEXT,
|
|
434
|
+
failed_patterns TEXT,
|
|
435
|
+
extracted_heuristics TEXT,
|
|
436
|
+
extracted_anti_patterns TEXT,
|
|
437
|
+
duration_seconds REAL,
|
|
438
|
+
node_count INTEGER,
|
|
439
|
+
error_message TEXT,
|
|
440
|
+
metadata TEXT,
|
|
441
|
+
created_at TEXT NOT NULL
|
|
442
|
+
)
|
|
443
|
+
""")
|
|
444
|
+
|
|
445
|
+
cursor.execute("""
|
|
446
|
+
CREATE INDEX IF NOT EXISTS idx_wo_tenant
|
|
447
|
+
ON workflow_outcomes(tenant_id)
|
|
448
|
+
""")
|
|
449
|
+
cursor.execute("""
|
|
450
|
+
CREATE INDEX IF NOT EXISTS idx_wo_workflow
|
|
451
|
+
ON workflow_outcomes(workflow_id)
|
|
452
|
+
""")
|
|
453
|
+
cursor.execute("""
|
|
454
|
+
CREATE INDEX IF NOT EXISTS idx_wo_project_agent
|
|
455
|
+
ON workflow_outcomes(project_id, agent)
|
|
456
|
+
""")
|
|
457
|
+
|
|
458
|
+
# =====================================================================
|
|
459
|
+
# TABLE 3: Artifact Links
|
|
460
|
+
# Matches alma.workflow.artifacts.ArtifactRef dataclass
|
|
461
|
+
# =====================================================================
|
|
462
|
+
cursor.execute("""
|
|
463
|
+
CREATE TABLE IF NOT EXISTS artifact_links (
|
|
464
|
+
id TEXT PRIMARY KEY,
|
|
465
|
+
memory_id TEXT NOT NULL,
|
|
466
|
+
artifact_type TEXT NOT NULL,
|
|
467
|
+
storage_url TEXT NOT NULL,
|
|
468
|
+
filename TEXT,
|
|
469
|
+
mime_type TEXT,
|
|
470
|
+
size_bytes INTEGER,
|
|
471
|
+
checksum TEXT,
|
|
472
|
+
metadata TEXT,
|
|
473
|
+
created_at TEXT NOT NULL
|
|
474
|
+
)
|
|
475
|
+
""")
|
|
476
|
+
|
|
477
|
+
cursor.execute("""
|
|
478
|
+
CREATE INDEX IF NOT EXISTS idx_artifact_memory
|
|
479
|
+
ON artifact_links(memory_id)
|
|
480
|
+
""")
|
|
481
|
+
|
|
482
|
+
# =====================================================================
|
|
483
|
+
# ALTER EXISTING TABLES: Add workflow scope columns
|
|
484
|
+
# =====================================================================
|
|
485
|
+
# SQLite doesn't support IF NOT EXISTS for ALTER TABLE, so we need to check
|
|
486
|
+
|
|
487
|
+
existing_tables = [
|
|
488
|
+
"heuristics",
|
|
489
|
+
"outcomes",
|
|
490
|
+
"domain_knowledge",
|
|
491
|
+
"anti_patterns",
|
|
492
|
+
]
|
|
493
|
+
new_columns = [
|
|
494
|
+
("tenant_id", "TEXT DEFAULT 'default'"),
|
|
495
|
+
("workflow_id", "TEXT"),
|
|
496
|
+
("run_id", "TEXT"),
|
|
497
|
+
("node_id", "TEXT"),
|
|
498
|
+
]
|
|
499
|
+
|
|
500
|
+
for table in existing_tables:
|
|
501
|
+
# Check which columns already exist
|
|
502
|
+
cursor.execute(f"PRAGMA table_info({table})")
|
|
503
|
+
existing_cols = {row[1] for row in cursor.fetchall()}
|
|
504
|
+
|
|
505
|
+
# Only add run_id and node_id to heuristics and outcomes
|
|
506
|
+
cols_to_add = (
|
|
507
|
+
new_columns if table in ["heuristics", "outcomes"] else new_columns[:2]
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
for col_name, col_def in cols_to_add:
|
|
511
|
+
if col_name not in existing_cols:
|
|
512
|
+
try:
|
|
513
|
+
cursor.execute(
|
|
514
|
+
f"ALTER TABLE {table} ADD COLUMN {col_name} {col_def}"
|
|
515
|
+
)
|
|
516
|
+
except Exception:
|
|
517
|
+
pass # Column might already exist
|
|
518
|
+
|
|
519
|
+
# Add scope indexes
|
|
520
|
+
cursor.execute("""
|
|
521
|
+
CREATE INDEX IF NOT EXISTS idx_heuristics_tenant
|
|
522
|
+
ON heuristics(tenant_id)
|
|
523
|
+
""")
|
|
524
|
+
cursor.execute("""
|
|
525
|
+
CREATE INDEX IF NOT EXISTS idx_heuristics_workflow
|
|
526
|
+
ON heuristics(workflow_id)
|
|
527
|
+
""")
|
|
528
|
+
cursor.execute("""
|
|
529
|
+
CREATE INDEX IF NOT EXISTS idx_outcomes_tenant
|
|
530
|
+
ON outcomes(tenant_id)
|
|
531
|
+
""")
|
|
532
|
+
cursor.execute("""
|
|
533
|
+
CREATE INDEX IF NOT EXISTS idx_outcomes_workflow
|
|
534
|
+
ON outcomes(workflow_id)
|
|
535
|
+
""")
|
|
536
|
+
|
|
537
|
+
connection.commit()
|
|
538
|
+
|
|
539
|
+
def downgrade(self, connection: Any) -> None:
|
|
540
|
+
"""Revert workflow context schema changes."""
|
|
541
|
+
cursor = connection.cursor()
|
|
542
|
+
|
|
543
|
+
# Drop new tables
|
|
544
|
+
cursor.execute("DROP TABLE IF EXISTS artifact_links")
|
|
545
|
+
cursor.execute("DROP TABLE IF EXISTS workflow_outcomes")
|
|
546
|
+
cursor.execute("DROP TABLE IF EXISTS checkpoints")
|
|
547
|
+
|
|
548
|
+
# Note: SQLite doesn't support DROP COLUMN easily
|
|
549
|
+
# The added columns will remain but won't affect existing code
|
|
550
|
+
|
|
551
|
+
connection.commit()
|