superlocalmemory 3.2.3 → 3.3.1

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 (51) hide show
  1. package/CHANGELOG.md +43 -1
  2. package/README.md +106 -71
  3. package/package.json +1 -2
  4. package/pyproject.toml +16 -1
  5. package/src/superlocalmemory/cli/commands.py +419 -15
  6. package/src/superlocalmemory/cli/main.py +44 -0
  7. package/src/superlocalmemory/core/config.py +276 -4
  8. package/src/superlocalmemory/core/consolidation_engine.py +37 -0
  9. package/src/superlocalmemory/core/engine.py +21 -0
  10. package/src/superlocalmemory/core/engine_wiring.py +58 -8
  11. package/src/superlocalmemory/dynamics/activation_guided_quantization.py +374 -0
  12. package/src/superlocalmemory/dynamics/eap_scheduler.py +276 -0
  13. package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +171 -0
  14. package/src/superlocalmemory/encoding/cognitive_consolidator.py +804 -0
  15. package/src/superlocalmemory/hooks/auto_invoker.py +46 -8
  16. package/src/superlocalmemory/hooks/auto_parameterize.py +147 -0
  17. package/src/superlocalmemory/infra/heartbeat_monitor.py +140 -0
  18. package/src/superlocalmemory/infra/pid_manager.py +193 -0
  19. package/src/superlocalmemory/infra/process_reaper.py +572 -0
  20. package/src/superlocalmemory/learning/consolidation_quantization_worker.py +115 -0
  21. package/src/superlocalmemory/learning/forgetting_scheduler.py +263 -0
  22. package/src/superlocalmemory/learning/quantization_scheduler.py +320 -0
  23. package/src/superlocalmemory/math/ebbinghaus.py +309 -0
  24. package/src/superlocalmemory/math/fisher_quantized.py +251 -0
  25. package/src/superlocalmemory/math/hopfield.py +279 -0
  26. package/src/superlocalmemory/math/polar_quant.py +379 -0
  27. package/src/superlocalmemory/math/qjl.py +115 -0
  28. package/src/superlocalmemory/mcp/server.py +2 -0
  29. package/src/superlocalmemory/mcp/tools_v3.py +10 -0
  30. package/src/superlocalmemory/mcp/tools_v33.py +351 -0
  31. package/src/superlocalmemory/parameterization/__init__.py +47 -0
  32. package/src/superlocalmemory/parameterization/pattern_extractor.py +534 -0
  33. package/src/superlocalmemory/parameterization/pii_filter.py +106 -0
  34. package/src/superlocalmemory/parameterization/prompt_injector.py +216 -0
  35. package/src/superlocalmemory/parameterization/prompt_lifecycle.py +275 -0
  36. package/src/superlocalmemory/parameterization/soft_prompt_generator.py +425 -0
  37. package/src/superlocalmemory/retrieval/engine.py +21 -3
  38. package/src/superlocalmemory/retrieval/forgetting_filter.py +145 -0
  39. package/src/superlocalmemory/retrieval/hopfield_channel.py +335 -0
  40. package/src/superlocalmemory/retrieval/quantization_aware_search.py +133 -0
  41. package/src/superlocalmemory/retrieval/strategy.py +16 -6
  42. package/src/superlocalmemory/server/routes/agents.py +68 -8
  43. package/src/superlocalmemory/server/routes/learning.py +18 -1
  44. package/src/superlocalmemory/server/routes/lifecycle.py +36 -17
  45. package/src/superlocalmemory/server/routes/v3_api.py +503 -1
  46. package/src/superlocalmemory/storage/database.py +206 -0
  47. package/src/superlocalmemory/storage/embedding_migrator.py +178 -0
  48. package/src/superlocalmemory/storage/migration_v33.py +140 -0
  49. package/src/superlocalmemory/storage/quantized_store.py +261 -0
  50. package/src/superlocalmemory/storage/schema_v32.py +137 -0
  51. package/conftest.py +0 -5
@@ -38,24 +38,29 @@ async def lifecycle_status():
38
38
  conn = sqlite3.connect(str(DB_PATH))
39
39
  conn.row_factory = sqlite3.Row
40
40
 
41
- # Try V3 schema first (atomic_facts with lifecycle column)
41
+ # V3.3: Use fact_retention.lifecycle_zone (Ebbinghaus-driven, authoritative)
42
+ # Falls back to atomic_facts.lifecycle for pre-3.3 databases
42
43
  states = {}
43
44
  try:
44
45
  rows = conn.execute(
45
- "SELECT lifecycle, COUNT(*) as cnt "
46
- "FROM atomic_facts WHERE profile_id = ? GROUP BY lifecycle",
46
+ "SELECT lifecycle_zone, COUNT(*) as cnt "
47
+ "FROM fact_retention WHERE profile_id = ? GROUP BY lifecycle_zone",
47
48
  (profile,),
48
49
  ).fetchall()
49
- states = {
50
- (row['lifecycle'] or 'active'): row['cnt']
51
- for row in rows
52
- }
50
+ if rows:
51
+ states = {
52
+ row['lifecycle_zone']: row['cnt']
53
+ for row in rows
54
+ }
53
55
  except sqlite3.OperationalError:
54
- # V2 fallback: memories table
56
+ pass
57
+
58
+ if not states:
59
+ # Fallback: V3.0-3.2 schema (atomic_facts.lifecycle column)
55
60
  try:
56
61
  rows = conn.execute(
57
62
  "SELECT lifecycle, COUNT(*) as cnt "
58
- "FROM memories WHERE profile = ? GROUP BY lifecycle",
63
+ "FROM atomic_facts WHERE profile_id = ? GROUP BY lifecycle",
59
64
  (profile,),
60
65
  ).fetchall()
61
66
  states = {
@@ -63,8 +68,20 @@ async def lifecycle_status():
63
68
  for row in rows
64
69
  }
65
70
  except sqlite3.OperationalError:
66
- # No lifecycle column at all — count everything as active
67
- total = conn.execute(
71
+ # V2 fallback: memories table
72
+ try:
73
+ rows = conn.execute(
74
+ "SELECT lifecycle, COUNT(*) as cnt "
75
+ "FROM memories WHERE profile = ? GROUP BY lifecycle",
76
+ (profile,),
77
+ ).fetchall()
78
+ states = {
79
+ (row['lifecycle'] or 'active'): row['cnt']
80
+ for row in rows
81
+ }
82
+ except sqlite3.OperationalError:
83
+ # No lifecycle column at all — count everything as active
84
+ total = conn.execute(
68
85
  "SELECT COUNT(*) FROM atomic_facts WHERE profile_id = ?",
69
86
  (profile,),
70
87
  ).fetchone()[0]
@@ -72,15 +89,17 @@ async def lifecycle_status():
72
89
 
73
90
  total = sum(states.values())
74
91
 
75
- # Age distribution per state
92
+ # Age distribution per state (V3.3: join fact_retention with atomic_facts)
76
93
  age_stats = {}
77
- for state in ('active', 'warm', 'cold', 'archived'):
94
+ for state in ('active', 'warm', 'cold', 'archive', 'forgotten'):
78
95
  try:
79
96
  row = conn.execute(
80
- "SELECT AVG(julianday('now') - julianday(created_at)) as avg_age, "
81
- "MIN(julianday('now') - julianday(created_at)) as min_age, "
82
- "MAX(julianday('now') - julianday(created_at)) as max_age "
83
- "FROM atomic_facts WHERE profile_id = ? AND lifecycle = ?",
97
+ "SELECT AVG(julianday('now') - julianday(af.created_at)) as avg_age, "
98
+ "MIN(julianday('now') - julianday(af.created_at)) as min_age, "
99
+ "MAX(julianday('now') - julianday(af.created_at)) as max_age "
100
+ "FROM fact_retention fr "
101
+ "JOIN atomic_facts af ON fr.fact_id = af.fact_id "
102
+ "WHERE fr.profile_id = ? AND fr.lifecycle_zone = ?",
84
103
  (profile, state),
85
104
  ).fetchone()
86
105
  if row and row['avg_age'] is not None:
@@ -112,11 +112,22 @@ async def set_mode(request: Request):
112
112
  new_config.active_profile = old_config.active_profile
113
113
  new_config.save()
114
114
 
115
+ # V3.3: Check if embedding model changed — flag for re-indexing
116
+ needs_reindex = (
117
+ old_config.embedding.provider != new_config.embedding.provider
118
+ or old_config.embedding.model_name != new_config.embedding.model_name
119
+ )
120
+
115
121
  # Reset engine to pick up new config
116
122
  if hasattr(request.app.state, "engine"):
117
123
  request.app.state.engine = None
118
124
 
119
- return {"success": True, "mode": new_mode}
125
+ return {
126
+ "success": True,
127
+ "mode": new_mode,
128
+ "needs_reindex": needs_reindex,
129
+ "message": "Embedding re-indexing will run on next recall." if needs_reindex else "",
130
+ }
120
131
  except Exception as e:
121
132
  return JSONResponse({"error": str(e)}, status_code=500)
122
133
 
@@ -1190,3 +1201,494 @@ async def get_vector_store_status(request: Request, profile: str = ""):
1190
1201
  return result
1191
1202
  except Exception as e:
1192
1203
  return JSONResponse({"error": str(e)}, status_code=500)
1204
+
1205
+
1206
+ # ── Phase 10: V3.3 API Endpoints ────────────────────────────
1207
+ # 7 new endpoints for the V3.3 dashboard:
1208
+ # Forgetting (2), Quantization (1), CCQ (1),
1209
+ # Soft Prompts (1), Process Health (1), V3.3 Overview (1)
1210
+ #
1211
+ # Rules enforced:
1212
+ # 01 - Profile scoping on ALL endpoints
1213
+ # 06 - No engine import from routes (direct sqlite3)
1214
+ # 11 - Parameterized SQL everywhere
1215
+ # 19 - Silent errors with JSONResponse
1216
+ # ──────────────────────────────────────────────────────────────
1217
+
1218
+
1219
+ # ── 1a. GET /api/v3/forgetting/stats ────────────────────────
1220
+
1221
+ @router.get("/forgetting/stats")
1222
+ async def forgetting_stats(request: Request, profile: str = ""):
1223
+ """Get memory retention zone distribution."""
1224
+ try:
1225
+ from superlocalmemory.server.routes.helpers import get_active_profile, DB_PATH
1226
+ import sqlite3 as _sqlite3
1227
+ pid = profile or get_active_profile()
1228
+
1229
+ zones = {"active": 0, "warm": 0, "cold": 0, "archive": 0, "forgotten": 0}
1230
+ total = 0
1231
+
1232
+ if not DB_PATH.exists():
1233
+ return {"total": total, "zones": zones}
1234
+
1235
+ conn = _sqlite3.connect(str(DB_PATH))
1236
+ conn.row_factory = _sqlite3.Row
1237
+
1238
+ try:
1239
+ rows = conn.execute(
1240
+ "SELECT lifecycle_zone, COUNT(*) AS cnt "
1241
+ "FROM fact_retention WHERE profile_id = ? "
1242
+ "GROUP BY lifecycle_zone",
1243
+ (pid,),
1244
+ ).fetchall()
1245
+ for row in rows:
1246
+ zone = dict(row)["lifecycle_zone"]
1247
+ cnt = dict(row)["cnt"]
1248
+ if zone in zones:
1249
+ zones[zone] = cnt
1250
+ total += cnt
1251
+ except Exception:
1252
+ # Table may not exist in older DBs -- graceful fallback
1253
+ pass
1254
+
1255
+ conn.close()
1256
+ return {"total": total, "zones": zones}
1257
+ except Exception as e:
1258
+ return JSONResponse({"error": str(e)}, status_code=500)
1259
+
1260
+
1261
+ # ── 1b. POST /api/v3/forgetting/run ─────────────────────────
1262
+
1263
+ @router.post("/forgetting/run")
1264
+ async def run_forgetting(request: Request):
1265
+ """Trigger a forgetting decay cycle.
1266
+
1267
+ Body: {"profile": ""} (optional profile override).
1268
+ """
1269
+ try:
1270
+ body = await request.json()
1271
+ profile = body.get("profile", "")
1272
+
1273
+ from superlocalmemory.server.routes.helpers import get_active_profile, DB_PATH
1274
+ import sqlite3 as _sqlite3
1275
+ pid = profile or get_active_profile()
1276
+
1277
+ if not DB_PATH.exists():
1278
+ return {"success": False, "error": "Database not found"}
1279
+
1280
+ conn = _sqlite3.connect(str(DB_PATH))
1281
+ conn.row_factory = _sqlite3.Row
1282
+
1283
+ updated = 0
1284
+ try:
1285
+ # Apply Ebbinghaus decay: reduce retention for facts not accessed recently
1286
+ # Formula: retention *= exp(-0.1) for each cycle (simplified batch decay)
1287
+ conn.execute(
1288
+ "UPDATE fact_retention "
1289
+ "SET retention_score = MAX(0.0, retention_score * 0.9), "
1290
+ " last_computed_at = datetime('now') "
1291
+ "WHERE profile_id = ? "
1292
+ "AND lifecycle_zone NOT IN ('archive', 'forgotten')",
1293
+ (pid,),
1294
+ )
1295
+ updated = conn.total_changes
1296
+
1297
+ # Transition zones based on new retention scores
1298
+ zone_thresholds = [
1299
+ ("forgotten", 0.05),
1300
+ ("archive", 0.15),
1301
+ ("cold", 0.35),
1302
+ ("warm", 0.65),
1303
+ ]
1304
+ for zone, threshold in zone_thresholds:
1305
+ conn.execute(
1306
+ "UPDATE fact_retention "
1307
+ "SET lifecycle_zone = ? "
1308
+ "WHERE profile_id = ? "
1309
+ "AND retention_score < ? "
1310
+ "AND lifecycle_zone NOT IN ('archive', 'forgotten')",
1311
+ (zone, pid, threshold),
1312
+ )
1313
+
1314
+ # Ensure high-retention facts are active
1315
+ conn.execute(
1316
+ "UPDATE fact_retention "
1317
+ "SET lifecycle_zone = 'active' "
1318
+ "WHERE profile_id = ? AND retention_score >= 0.65 "
1319
+ "AND lifecycle_zone NOT IN ('archive', 'forgotten')",
1320
+ (pid,),
1321
+ )
1322
+
1323
+ conn.commit()
1324
+ except Exception as exc:
1325
+ conn.close()
1326
+ return {"success": False, "error": str(exc)}
1327
+
1328
+ conn.close()
1329
+ return {"success": True, "facts_decayed": updated, "profile": pid}
1330
+ except Exception as e:
1331
+ return JSONResponse({"error": str(e)}, status_code=500)
1332
+
1333
+
1334
+ # ── 1c. GET /api/v3/quantization/stats ──────────────────────
1335
+
1336
+ @router.get("/quantization/stats")
1337
+ async def quantization_stats(request: Request, profile: str = ""):
1338
+ """Get embedding quantization tier distribution."""
1339
+ try:
1340
+ from superlocalmemory.server.routes.helpers import get_active_profile, DB_PATH
1341
+ import sqlite3 as _sqlite3
1342
+ pid = profile or get_active_profile()
1343
+
1344
+ tiers = {"float32": 0, "int8": 0, "polar4": 0, "polar2": 0}
1345
+ total = 0
1346
+ compression_ratio = 1.0
1347
+
1348
+ if not DB_PATH.exists():
1349
+ return {"total": total, "tiers": tiers, "compression_ratio": compression_ratio}
1350
+
1351
+ conn = _sqlite3.connect(str(DB_PATH))
1352
+ conn.row_factory = _sqlite3.Row
1353
+
1354
+ try:
1355
+ rows = conn.execute(
1356
+ "SELECT quantization_level, COUNT(*) AS cnt "
1357
+ "FROM embedding_quantization_metadata "
1358
+ "WHERE profile_id = ? "
1359
+ "GROUP BY quantization_level",
1360
+ (pid,),
1361
+ ).fetchall()
1362
+ for row in rows:
1363
+ level = dict(row)["quantization_level"]
1364
+ cnt = dict(row)["cnt"]
1365
+ if level in tiers:
1366
+ tiers[level] = cnt
1367
+ total += cnt
1368
+ except Exception:
1369
+ pass
1370
+
1371
+ # Compute compression ratio from actual sizes if available
1372
+ try:
1373
+ size_row = conn.execute(
1374
+ "SELECT "
1375
+ "SUM(CASE WHEN bit_width = 32 THEN 768 * 4 ELSE "
1376
+ " COALESCE(compressed_size_bytes, 768 * bit_width / 8) END) AS actual, "
1377
+ "SUM(768 * 4) AS uncompressed "
1378
+ "FROM embedding_quantization_metadata "
1379
+ "WHERE profile_id = ?",
1380
+ (pid,),
1381
+ ).fetchone()
1382
+ if size_row:
1383
+ d = dict(size_row)
1384
+ uncompressed = d.get("uncompressed") or 0
1385
+ actual = d.get("actual") or 0
1386
+ if actual > 0 and uncompressed > 0:
1387
+ compression_ratio = round(uncompressed / actual, 2)
1388
+ except Exception:
1389
+ pass
1390
+
1391
+ conn.close()
1392
+ return {"total": total, "tiers": tiers, "compression_ratio": compression_ratio}
1393
+ except Exception as e:
1394
+ return JSONResponse({"error": str(e)}, status_code=500)
1395
+
1396
+
1397
+ # ── 1d. GET /api/v3/ccq/blocks ──────────────────────────────
1398
+
1399
+ @router.get("/ccq/blocks")
1400
+ async def ccq_blocks(request: Request, profile: str = "", limit: int = 50):
1401
+ """Get CCQ consolidated blocks."""
1402
+ try:
1403
+ from superlocalmemory.server.routes.helpers import get_active_profile, DB_PATH
1404
+ import sqlite3 as _sqlite3
1405
+ pid = profile or get_active_profile()
1406
+
1407
+ if not DB_PATH.exists():
1408
+ return {"blocks": [], "total": 0}
1409
+
1410
+ conn = _sqlite3.connect(str(DB_PATH))
1411
+ conn.row_factory = _sqlite3.Row
1412
+
1413
+ blocks = []
1414
+ total = 0
1415
+ try:
1416
+ rows = conn.execute(
1417
+ "SELECT block_id, content, source_fact_ids, char_count, "
1418
+ "compiled_by, cluster_id, created_at "
1419
+ "FROM ccq_consolidated_blocks "
1420
+ "WHERE profile_id = ? "
1421
+ "ORDER BY created_at DESC LIMIT ?",
1422
+ (pid, limit),
1423
+ ).fetchall()
1424
+
1425
+ for row in rows:
1426
+ d = dict(row)
1427
+ source_ids = []
1428
+ try:
1429
+ source_ids = json.loads(d.get("source_fact_ids", "[]"))
1430
+ except (json.JSONDecodeError, TypeError):
1431
+ pass
1432
+ blocks.append({
1433
+ "block_id": d["block_id"],
1434
+ "content": d["content"],
1435
+ "source_fact_count": len(source_ids),
1436
+ "char_count": d["char_count"],
1437
+ "compiled_by": d["compiled_by"],
1438
+ "cluster_id": d["cluster_id"],
1439
+ "created_at": d["created_at"],
1440
+ })
1441
+
1442
+ count_row = conn.execute(
1443
+ "SELECT COUNT(*) FROM ccq_consolidated_blocks "
1444
+ "WHERE profile_id = ?",
1445
+ (pid,),
1446
+ ).fetchone()
1447
+ total = count_row[0] if count_row else 0
1448
+ except Exception:
1449
+ pass
1450
+
1451
+ conn.close()
1452
+ return {"blocks": blocks, "total": total}
1453
+ except Exception as e:
1454
+ return JSONResponse({"error": str(e)}, status_code=500)
1455
+
1456
+
1457
+ # ── 1e. GET /api/v3/soft-prompts ─────────────────────────────
1458
+
1459
+ @router.get("/soft-prompts")
1460
+ async def get_soft_prompts(request: Request, profile: str = ""):
1461
+ """Get active soft prompt templates."""
1462
+ try:
1463
+ from superlocalmemory.server.routes.helpers import get_active_profile, DB_PATH
1464
+ import sqlite3 as _sqlite3
1465
+ pid = profile or get_active_profile()
1466
+
1467
+ if not DB_PATH.exists():
1468
+ return {"prompts": [], "total": 0, "total_tokens": 0}
1469
+
1470
+ conn = _sqlite3.connect(str(DB_PATH))
1471
+ conn.row_factory = _sqlite3.Row
1472
+
1473
+ prompts = []
1474
+ total_tokens = 0
1475
+ try:
1476
+ rows = conn.execute(
1477
+ "SELECT prompt_id, category, content, confidence, "
1478
+ "effectiveness, token_count, retention_score, "
1479
+ "active, version, created_at, updated_at "
1480
+ "FROM soft_prompt_templates "
1481
+ "WHERE profile_id = ? AND active = 1 "
1482
+ "ORDER BY confidence DESC",
1483
+ (pid,),
1484
+ ).fetchall()
1485
+
1486
+ for row in rows:
1487
+ d = dict(row)
1488
+ tokens = d.get("token_count", 0)
1489
+ total_tokens += tokens
1490
+ prompts.append({
1491
+ "prompt_id": d["prompt_id"],
1492
+ "category": d["category"],
1493
+ "content": d["content"][:200],
1494
+ "confidence": round(float(d["confidence"]), 3),
1495
+ "effectiveness": round(float(d.get("effectiveness", 0.5)), 3),
1496
+ "token_count": tokens,
1497
+ "retention_score": round(float(d.get("retention_score", 1.0)), 3),
1498
+ "version": d["version"],
1499
+ "created_at": d["created_at"],
1500
+ })
1501
+ except Exception:
1502
+ pass
1503
+
1504
+ conn.close()
1505
+ return {"prompts": prompts, "total": len(prompts), "total_tokens": total_tokens}
1506
+ except Exception as e:
1507
+ return JSONResponse({"error": str(e)}, status_code=500)
1508
+
1509
+
1510
+ # ── 1f. GET /api/v3/health/processes ─────────────────────────
1511
+
1512
+ @router.get("/health/processes")
1513
+ async def process_health(request: Request):
1514
+ """Get SLM process health status."""
1515
+ try:
1516
+ import os as _os
1517
+
1518
+ processes = {
1519
+ "mcp_server": {"pid": _os.getpid(), "status": "running"},
1520
+ "parent": {"pid": _os.getppid(), "status": "unknown"},
1521
+ }
1522
+
1523
+ # Check parent process
1524
+ try:
1525
+ _os.kill(_os.getppid(), 0)
1526
+ processes["parent"]["status"] = "running"
1527
+ except ProcessLookupError:
1528
+ processes["parent"]["status"] = "dead"
1529
+ except PermissionError:
1530
+ processes["parent"]["status"] = "running"
1531
+ except OSError:
1532
+ processes["parent"]["status"] = "unknown"
1533
+
1534
+ # Check worker pool status
1535
+ worker_status = "unavailable"
1536
+ try:
1537
+ from superlocalmemory.core.worker_pool import WorkerPool
1538
+ pool = WorkerPool.shared()
1539
+ worker_status = "running" if pool else "stopped"
1540
+ except Exception:
1541
+ pass
1542
+ processes["worker_pool"] = {"status": worker_status}
1543
+
1544
+ # Memory usage of current process (approximate)
1545
+ memory_mb = 0.0
1546
+ try:
1547
+ import resource
1548
+ usage = resource.getrusage(resource.RUSAGE_SELF)
1549
+ memory_mb = round(usage.ru_maxrss / (1024 * 1024), 1)
1550
+ except Exception:
1551
+ pass
1552
+
1553
+ return {
1554
+ "processes": processes,
1555
+ "memory_mb": memory_mb,
1556
+ "healthy": processes["parent"]["status"] != "dead",
1557
+ }
1558
+ except Exception as e:
1559
+ return JSONResponse({"error": str(e)}, status_code=500)
1560
+
1561
+
1562
+ # ── 1g. GET /api/v3/v33/overview ─────────────────────────────
1563
+
1564
+ @router.get("/v33/overview")
1565
+ async def v33_overview(request: Request, profile: str = ""):
1566
+ """Get SLM 3.3 feature overview -- all new capabilities at a glance."""
1567
+ try:
1568
+ from superlocalmemory.server.routes.helpers import get_active_profile, DB_PATH
1569
+ import sqlite3 as _sqlite3
1570
+ pid = profile or get_active_profile()
1571
+
1572
+ overview: dict = {
1573
+ "version": "3.3",
1574
+ "profile": pid,
1575
+ "forgetting": {"total": 0, "zones": {}},
1576
+ "quantization": {"total": 0, "tiers": {}, "compression_ratio": 1.0},
1577
+ "ccq": {"blocks": 0, "facts_archived": 0},
1578
+ "soft_prompts": {"total": 0, "total_tokens": 0},
1579
+ "hopfield": {
1580
+ "available": False,
1581
+ "description": "Modern Continuous Hopfield Network retrieval channel",
1582
+ },
1583
+ "process_health": {"healthy": True},
1584
+ }
1585
+
1586
+ if not DB_PATH.exists():
1587
+ return overview
1588
+
1589
+ conn = _sqlite3.connect(str(DB_PATH))
1590
+ conn.row_factory = _sqlite3.Row
1591
+
1592
+ # Forgetting stats
1593
+ try:
1594
+ zones = {"active": 0, "warm": 0, "cold": 0, "archive": 0, "forgotten": 0}
1595
+ rows = conn.execute(
1596
+ "SELECT lifecycle_zone, COUNT(*) AS cnt "
1597
+ "FROM fact_retention WHERE profile_id = ? "
1598
+ "GROUP BY lifecycle_zone",
1599
+ (pid,),
1600
+ ).fetchall()
1601
+ total_fg = 0
1602
+ for row in rows:
1603
+ d = dict(row)
1604
+ zone = d["lifecycle_zone"]
1605
+ if zone in zones:
1606
+ zones[zone] = d["cnt"]
1607
+ total_fg += d["cnt"]
1608
+ overview["forgetting"] = {"total": total_fg, "zones": zones}
1609
+ except Exception:
1610
+ pass
1611
+
1612
+ # Quantization stats
1613
+ try:
1614
+ tiers = {"float32": 0, "int8": 0, "polar4": 0, "polar2": 0}
1615
+ rows = conn.execute(
1616
+ "SELECT quantization_level, COUNT(*) AS cnt "
1617
+ "FROM embedding_quantization_metadata "
1618
+ "WHERE profile_id = ? GROUP BY quantization_level",
1619
+ (pid,),
1620
+ ).fetchall()
1621
+ total_q = 0
1622
+ for row in rows:
1623
+ d = dict(row)
1624
+ level = d["quantization_level"]
1625
+ if level in tiers:
1626
+ tiers[level] = d["cnt"]
1627
+ total_q += d["cnt"]
1628
+ overview["quantization"] = {
1629
+ "total": total_q, "tiers": tiers, "compression_ratio": 1.0,
1630
+ }
1631
+ except Exception:
1632
+ pass
1633
+
1634
+ # CCQ stats
1635
+ try:
1636
+ block_count = conn.execute(
1637
+ "SELECT COUNT(*) FROM ccq_consolidated_blocks "
1638
+ "WHERE profile_id = ?", (pid,),
1639
+ ).fetchone()[0]
1640
+ # Count archived facts (lifecycle='archived' from CCQ)
1641
+ archived_count = 0
1642
+ try:
1643
+ archived_count = conn.execute(
1644
+ "SELECT COUNT(*) FROM atomic_facts "
1645
+ "WHERE profile_id = ? AND lifecycle = 'archived'",
1646
+ (pid,),
1647
+ ).fetchone()[0]
1648
+ except Exception:
1649
+ pass
1650
+ overview["ccq"] = {
1651
+ "blocks": block_count,
1652
+ "facts_archived": archived_count,
1653
+ }
1654
+ except Exception:
1655
+ pass
1656
+
1657
+ # Soft prompts stats
1658
+ try:
1659
+ prompt_rows = conn.execute(
1660
+ "SELECT COUNT(*) AS cnt, COALESCE(SUM(token_count), 0) AS tokens "
1661
+ "FROM soft_prompt_templates "
1662
+ "WHERE profile_id = ? AND active = 1",
1663
+ (pid,),
1664
+ ).fetchone()
1665
+ if prompt_rows:
1666
+ d = dict(prompt_rows)
1667
+ overview["soft_prompts"] = {
1668
+ "total": d["cnt"],
1669
+ "total_tokens": d["tokens"],
1670
+ }
1671
+ except Exception:
1672
+ pass
1673
+
1674
+ # Hopfield channel availability
1675
+ try:
1676
+ from superlocalmemory.retrieval.hopfield_channel import HopfieldChannel # noqa: F401
1677
+ overview["hopfield"]["available"] = True
1678
+ except ImportError:
1679
+ pass
1680
+
1681
+ # Process health
1682
+ try:
1683
+ import os as _os
1684
+ _os.kill(_os.getppid(), 0)
1685
+ overview["process_health"] = {"healthy": True}
1686
+ except ProcessLookupError:
1687
+ overview["process_health"] = {"healthy": False}
1688
+ except (PermissionError, OSError):
1689
+ overview["process_health"] = {"healthy": True}
1690
+
1691
+ conn.close()
1692
+ return overview
1693
+ except Exception as e:
1694
+ return JSONResponse({"error": str(e)}, status_code=500)