superlocalmemory 3.8.0 → 3.8.2

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 (134) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/README.md +32 -120
  3. package/package.json +9 -2
  4. package/plugin/.claude-plugin/plugin.json +1 -2
  5. package/plugin/CLAUDE.md +3 -3
  6. package/plugin/agents/slm-governance-advisor.md +1 -1
  7. package/plugin/agents/slm-loop-runner.md +1 -1
  8. package/plugin/agents/slm-memory-advisor.md +2 -2
  9. package/plugin/agents/slm-optimize-advisor.md +1 -1
  10. package/plugin/requirements.txt +1 -1
  11. package/plugin/skills/slm-cache/SKILL.md +1 -1
  12. package/plugin/skills/slm-compress/SKILL.md +1 -1
  13. package/plugin/skills/slm-governance/SKILL.md +1 -1
  14. package/plugin/skills/slm-graph/SKILL.md +1 -1
  15. package/plugin/skills/slm-loop/SKILL.md +1 -1
  16. package/plugin/skills/slm-mesh/SKILL.md +1 -1
  17. package/plugin/skills/slm-profile/SKILL.md +1 -1
  18. package/plugin/skills/slm-recall/SKILL.md +3 -5
  19. package/plugin/skills/slm-remember/SKILL.md +1 -1
  20. package/plugin/skills/slm-scope/SKILL.md +1 -1
  21. package/plugin/skills/slm-session/SKILL.md +1 -1
  22. package/plugin/skills/slm-status/SKILL.md +1 -1
  23. package/plugin-src/rules/AGENTS.md +2 -1
  24. package/plugin-src/skills/slm-cache/SKILL.md +1 -1
  25. package/plugin-src/skills/slm-compress/SKILL.md +1 -1
  26. package/plugin-src/skills/slm-graph/SKILL.md +1 -1
  27. package/plugin-src/skills/slm-recall/SKILL.md +3 -5
  28. package/plugin-src/skills/slm-remember/SKILL.md +1 -1
  29. package/plugin-src/skills/slm-session/SKILL.md +1 -1
  30. package/plugin-src/skills/slm-status/SKILL.md +1 -1
  31. package/pyproject.toml +2 -1
  32. package/scripts/postinstall.js +7 -1
  33. package/src/superlocalmemory/__init__.py +1 -1
  34. package/src/superlocalmemory/cli/commands.py +494 -9
  35. package/src/superlocalmemory/cli/daemon.py +7 -0
  36. package/src/superlocalmemory/cli/loop_cmd.py +2 -7
  37. package/src/superlocalmemory/cli/main.py +72 -7
  38. package/src/superlocalmemory/cli/setup_wizard.py +142 -16
  39. package/src/superlocalmemory/cli/version_banner.py +17 -3
  40. package/src/superlocalmemory/core/backend_orchestrator.py +18 -16
  41. package/src/superlocalmemory/core/component_healer.py +144 -0
  42. package/src/superlocalmemory/core/component_registry.py +487 -0
  43. package/src/superlocalmemory/core/config.py +21 -0
  44. package/src/superlocalmemory/core/embedding_worker.py +4 -5
  45. package/src/superlocalmemory/core/embeddings.py +132 -45
  46. package/src/superlocalmemory/core/engine.py +29 -22
  47. package/src/superlocalmemory/core/engine_ingestion.py +332 -45
  48. package/src/superlocalmemory/core/ingestion_command.py +154 -25
  49. package/src/superlocalmemory/core/injection.py +12 -7
  50. package/src/superlocalmemory/core/maintenance.py +43 -0
  51. package/src/superlocalmemory/core/maintenance_scheduler.py +44 -6
  52. package/src/superlocalmemory/core/recall_pipeline.py +42 -4
  53. package/src/superlocalmemory/core/store_pipeline.py +195 -20
  54. package/src/superlocalmemory/hooks/hook_handlers.py +6 -1
  55. package/src/superlocalmemory/hooks/portable_kit.py +34 -2
  56. package/src/superlocalmemory/learning/model_rollback.py +3 -0
  57. package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
  58. package/src/superlocalmemory/learning/reward.py +50 -0
  59. package/src/superlocalmemory/learning/source_quality.py +523 -1
  60. package/src/superlocalmemory/loops/ledger.py +25 -5
  61. package/src/superlocalmemory/mcp/_daemon_proxy.py +6 -2
  62. package/src/superlocalmemory/mcp/_pool_adapter.py +4 -1
  63. package/src/superlocalmemory/mcp/server.py +11 -30
  64. package/src/superlocalmemory/mcp/tools_active.py +1 -1
  65. package/src/superlocalmemory/mcp/tools_core.py +21 -5
  66. package/src/superlocalmemory/mcp/tools_learning.py +2 -2
  67. package/src/superlocalmemory/retrieval/bridge_discovery.py +14 -0
  68. package/src/superlocalmemory/retrieval/engine.py +53 -21
  69. package/src/superlocalmemory/retrieval/reranker.py +3 -4
  70. package/src/superlocalmemory/retrieval/spreading_activation.py +68 -38
  71. package/src/superlocalmemory/server/config_file.py +90 -0
  72. package/src/superlocalmemory/server/origin.py +50 -0
  73. package/src/superlocalmemory/server/routes/backup.py +293 -70
  74. package/src/superlocalmemory/server/routes/behavioral.py +342 -61
  75. package/src/superlocalmemory/server/routes/brain.py +57 -16
  76. package/src/superlocalmemory/server/routes/config_api.py +84 -82
  77. package/src/superlocalmemory/server/routes/entity.py +100 -23
  78. package/src/superlocalmemory/server/routes/evolution.py +103 -100
  79. package/src/superlocalmemory/server/routes/learning.py +286 -105
  80. package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
  81. package/src/superlocalmemory/server/routes/memories.py +8 -3
  82. package/src/superlocalmemory/server/routes/mesh.py +121 -32
  83. package/src/superlocalmemory/server/routes/ratelimit.py +33 -25
  84. package/src/superlocalmemory/server/routes/stats.py +93 -155
  85. package/src/superlocalmemory/server/routes/token.py +3 -13
  86. package/src/superlocalmemory/server/routes/v3_api.py +184 -20
  87. package/src/superlocalmemory/server/unified_daemon.py +732 -41
  88. package/src/superlocalmemory/storage/embedding_migrator.py +235 -0
  89. package/src/superlocalmemory/storage/migration_runner.py +79 -1
  90. package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -0
  91. package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +270 -0
  92. package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
  93. package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
  94. package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
  95. package/src/superlocalmemory/storage/schema.py +49 -1
  96. package/src/superlocalmemory/storage/schema_v32.py +2 -0
  97. package/src/superlocalmemory/storage/schema_v347.py +4 -0
  98. package/src/superlocalmemory/ui/index.html +6 -8
  99. package/src/superlocalmemory/ui/js/core.js +52 -9
  100. package/src/superlocalmemory/ui/js/dashboard.js +169 -82
  101. package/src/superlocalmemory/ui/js/od-backup.js +156 -65
  102. package/src/superlocalmemory/ui/js/od-brain.js +88 -51
  103. package/src/superlocalmemory/ui/js/od-components.js +147 -0
  104. package/src/superlocalmemory/ui/js/od-entities.js +65 -22
  105. package/src/superlocalmemory/ui/js/od-graph.js +46 -4
  106. package/src/superlocalmemory/ui/js/od-health.js +18 -0
  107. package/src/superlocalmemory/ui/js/od-memories.js +84 -5
  108. package/src/superlocalmemory/ui/js/od-mesh.js +23 -9
  109. package/src/superlocalmemory/ui/js/od-operations.js +36 -0
  110. package/src/superlocalmemory/ui/js/od-settings.js +186 -63
  111. package/src/superlocalmemory/ui/js/od-shell.js +249 -33
  112. package/src/superlocalmemory/ui/js/od-skills.js +44 -17
  113. package/src/superlocalmemory/ui/js/settings.js +15 -1
  114. package/plugin-src/.mcp.json +0 -12
  115. package/plugin-src/agents/slm-governance-advisor.md +0 -80
  116. package/plugin-src/agents/slm-loop-runner.md +0 -71
  117. package/plugin-src/agents/slm-memory-advisor.md +0 -49
  118. package/plugin-src/agents/slm-optimize-advisor.md +0 -44
  119. package/plugin-src/commands/slm-loop.md +0 -31
  120. package/plugin-src/hooks/.gitkeep +0 -0
  121. package/plugin-src/hooks/hooks.json +0 -102
  122. package/plugin-src/manifest.json +0 -30
  123. package/plugin-src/requirements.txt +0 -1
  124. package/plugin-src/rules/CLAUDE.md.fragment +0 -44
  125. package/plugin-src/scripts/ensure-venv.bat +0 -122
  126. package/plugin-src/scripts/ensure-venv.sh +0 -105
  127. package/plugin-src/scripts/slm-launch +0 -62
  128. package/plugin-src/scripts/slm-launch.bat +0 -23
  129. package/plugin-src/settings.json +0 -25
  130. package/plugin-src/skills/slm-governance/SKILL.md +0 -248
  131. package/plugin-src/skills/slm-loop/SKILL.md +0 -99
  132. package/plugin-src/skills/slm-mesh/SKILL.md +0 -282
  133. package/plugin-src/skills/slm-profile/SKILL.md +0 -148
  134. package/plugin-src/skills/slm-scope/SKILL.md +0 -176
@@ -52,6 +52,42 @@ async def _apply_runtime_config(request: Request, config, *, mode_change: bool)
52
52
  authorization.complete()
53
53
 
54
54
 
55
+ def _require_manage(request: Request) -> None:
56
+ """Use one explicit authorization boundary for dashboard configuration."""
57
+ from superlocalmemory.server.rbac_enforce import require_manage
58
+
59
+ require_manage(request)
60
+
61
+
62
+ def _resolve_profile(request: Request, requested: str = "") -> str:
63
+ """Resolve a profile override and enforce READ on its actual target."""
64
+ from superlocalmemory.access.rbac import Permission
65
+ from superlocalmemory.server.rbac_enforce import require_permission
66
+ from superlocalmemory.server.routes.helpers import get_active_profile
67
+
68
+ if requested and not isinstance(requested, str):
69
+ raise HTTPException(status_code=422, detail="profile must be a string")
70
+ profile = requested.strip() if requested else get_active_profile()
71
+ require_permission(request, Permission.READ, profile=profile)
72
+ return profile
73
+
74
+
75
+ def _resolve_mutation_profile(requested: object) -> str:
76
+ """Resolve a body-supplied profile without silently coercing bad input."""
77
+ from superlocalmemory.server.routes.helpers import get_active_profile
78
+
79
+ if requested is not None and not isinstance(requested, str):
80
+ raise HTTPException(status_code=422, detail="profile must be a string")
81
+ return requested.strip() if requested and requested.strip() else get_active_profile()
82
+
83
+
84
+ def _require_manage_for_profile(request: Request, profile: str) -> None:
85
+ """Enforce MANAGE on the resolved mutation target before any side effect."""
86
+ from superlocalmemory.server.rbac_enforce import require_manage
87
+
88
+ require_manage(request, profile=profile)
89
+
90
+
55
91
  # ── Dashboard ────────────────────────────────────────────────
56
92
 
57
93
  @router.get("/dashboard")
@@ -448,9 +484,9 @@ async def set_scope_config(request: Request):
448
484
  @router.post("/embedding/test")
449
485
  async def test_embedding_endpoint(request: Request):
450
486
  """Test connectivity to a custom embedding endpoint."""
487
+ _require_manage(request)
451
488
  try:
452
489
  import httpx
453
- from urllib.parse import urlparse
454
490
  body = await request.json()
455
491
  endpoint = body.get("api_endpoint", "").rstrip("/")
456
492
  model = body.get("model_name", "test")
@@ -459,12 +495,10 @@ async def test_embedding_endpoint(request: Request):
459
495
  if not endpoint:
460
496
  return JSONResponse({"error": "No endpoint provided"}, status_code=400)
461
497
 
462
- parsed = urlparse(endpoint)
463
- if parsed.scheme not in ("http", "https"):
464
- return JSONResponse({"error": "Only http/https endpoints supported"}, status_code=400)
465
- host = parsed.hostname or ""
466
- if host in ("169.254.169.254", "metadata.google.internal"):
467
- return JSONResponse({"error": "Cloud metadata endpoints not allowed"}, status_code=400)
498
+ client_host = request.client.host if request.client else ""
499
+ error = _validate_provider_url(endpoint, client_host)
500
+ if error:
501
+ return JSONResponse({"error": error}, status_code=400)
468
502
 
469
503
  if not endpoint.endswith("/embeddings"):
470
504
  endpoint = f"{endpoint}/embeddings"
@@ -980,6 +1014,7 @@ async def get_auto_capture_config():
980
1014
  @router.put("/auto-capture/config")
981
1015
  async def set_auto_capture_config(request: Request):
982
1016
  """Update auto-capture config. Body: {"enabled": true, "capture_decisions": true, ...}"""
1017
+ _require_manage(request)
983
1018
  try:
984
1019
  body = await request.json()
985
1020
  from superlocalmemory.hooks.rules_engine import RulesEngine
@@ -1010,6 +1045,7 @@ async def get_auto_recall_config():
1010
1045
  @router.put("/auto-recall/config")
1011
1046
  async def set_auto_recall_config(request: Request):
1012
1047
  """Update auto-recall config."""
1048
+ _require_manage(request)
1013
1049
  try:
1014
1050
  body = await request.json()
1015
1051
  from superlocalmemory.hooks.rules_engine import RulesEngine
@@ -1024,6 +1060,126 @@ async def set_auto_recall_config(request: Request):
1024
1060
  return _internal_error()
1025
1061
 
1026
1062
 
1063
+ # ── Runtime behaviour config (v3.8.2 UX-1) ──────────────────
1064
+ # User-facing settings that take effect LIVE (no restart) via the
1065
+ # reconfigure_daemon_engine hot-swap path AND persist across restarts via
1066
+ # SLMConfig.save(). Scoped deliberately to fields that save() round-trips:
1067
+ # retrieval (asdict) + injection (explicit). Excludes setup-time/internal
1068
+ # knobs and any field save() doesn't persist (which would silently revert).
1069
+ _RUNTIME_CONFIG_FIELDS = (
1070
+ # (section, field, kind, min, max)
1071
+ ("retrieval", "top_k", "int", 1, 200),
1072
+ ("retrieval", "use_cross_encoder", "bool", None, None),
1073
+ ("injection", "enabled", "bool", None, None),
1074
+ ("injection", "core_block_enabled", "bool", None, None),
1075
+ ("injection", "core_block_max_facts", "int", 0, 50),
1076
+ )
1077
+
1078
+
1079
+ def _runtime_config_snapshot(config) -> dict:
1080
+ """Current values of the exposed runtime fields, grouped by section."""
1081
+ out: dict = {}
1082
+ for section, field, *_ in _RUNTIME_CONFIG_FIELDS:
1083
+ sec = getattr(config, section, None)
1084
+ out.setdefault(section, {})[field] = (
1085
+ getattr(sec, field, None) if sec is not None else None
1086
+ )
1087
+ return out
1088
+
1089
+
1090
+ @router.get("/runtime/config")
1091
+ async def get_runtime_config(request: Request):
1092
+ """User-facing runtime behaviour settings.
1093
+
1094
+ Recall depth (top_k), reranker on/off (use_cross_encoder), and memory
1095
+ injection (master + core-block). All apply live via the daemon hot-swap —
1096
+ no restart — and persist across restarts.
1097
+ """
1098
+ try:
1099
+ from superlocalmemory.core.config import SLMConfig
1100
+ config = getattr(request.app.state, "config", None) or SLMConfig.load()
1101
+ return {"success": True, "config": _runtime_config_snapshot(config)}
1102
+ except Exception:
1103
+ return _internal_error()
1104
+
1105
+
1106
+ @router.put("/runtime/config")
1107
+ async def set_runtime_config(request: Request):
1108
+ """Validate, persist, and hot-apply runtime behaviour settings.
1109
+
1110
+ Body: ``{"retrieval": {"top_k": 30}, "injection": {"enabled": false}}``.
1111
+ Only known fields are accepted; a bad type/range is rejected 400 and
1112
+ nothing is applied (fail-fast — never let a bad value wedge the engine).
1113
+ """
1114
+ _require_manage(request)
1115
+ try:
1116
+ import dataclasses as _dc
1117
+ from superlocalmemory.core.config import SLMConfig
1118
+
1119
+ body = await request.json()
1120
+ if not isinstance(body, dict):
1121
+ return JSONResponse({"error": "body must be an object"}, status_code=400)
1122
+
1123
+ # Validate everything BEFORE mutating anything.
1124
+ updates: dict[str, dict] = {}
1125
+ for section, field, kind, lo, hi in _RUNTIME_CONFIG_FIELDS:
1126
+ sec_in = body.get(section)
1127
+ if not isinstance(sec_in, dict) or field not in sec_in:
1128
+ continue
1129
+ val = sec_in[field]
1130
+ if kind == "bool":
1131
+ if not isinstance(val, bool):
1132
+ return JSONResponse(
1133
+ {"error": f"{section}.{field} must be true or false"},
1134
+ status_code=400,
1135
+ )
1136
+ elif kind == "int":
1137
+ # bool is an int subclass — reject it explicitly.
1138
+ if isinstance(val, bool) or not isinstance(val, int):
1139
+ return JSONResponse(
1140
+ {"error": f"{section}.{field} must be an integer"},
1141
+ status_code=400,
1142
+ )
1143
+ if (lo is not None and val < lo) or (hi is not None and val > hi):
1144
+ return JSONResponse(
1145
+ {"error": f"{section}.{field} must be between {lo} and {hi}"},
1146
+ status_code=400,
1147
+ )
1148
+ updates.setdefault(section, {})[field] = val
1149
+
1150
+ if not updates:
1151
+ return JSONResponse(
1152
+ {"error": "no known runtime settings in request body"},
1153
+ status_code=400,
1154
+ )
1155
+
1156
+ # These are plain behaviour flags read per-recall — NOT model/mode
1157
+ # swaps. So we apply them the light way: swap the sub-config objects on
1158
+ # the LIVE config the daemon+engine already hold, then persist to disk.
1159
+ # This avoids the heavyweight engine drain+rebuild (which reloads
1160
+ # models and can time out draining in-flight recalls) — the running
1161
+ # engine simply reads the new values on its next recall/injection.
1162
+ live = getattr(request.app.state, "config", None) or SLMConfig.load()
1163
+ targets = [live]
1164
+ engine = getattr(request.app.state, "engine", None)
1165
+ eng_cfg = getattr(engine, "_config", None) if engine is not None else None
1166
+ if eng_cfg is not None and eng_cfg is not live:
1167
+ targets.append(eng_cfg)
1168
+ for cfg in targets:
1169
+ for section, changes in updates.items():
1170
+ sec = getattr(cfg, section, None)
1171
+ if sec is None:
1172
+ continue
1173
+ # replace() yields a NEW sub-config (works frozen or mutable);
1174
+ # assigning the attribute is an atomic reference swap.
1175
+ setattr(cfg, section, _dc.replace(sec, **changes))
1176
+ request.app.state.config = live
1177
+ live.save(mode_change=False) # durable across restart
1178
+ return {"success": True, "config": _runtime_config_snapshot(live)}
1179
+ except Exception:
1180
+ return _internal_error()
1181
+
1182
+
1027
1183
  # ── IDE Status ───────────────────────────────────────────────
1028
1184
 
1029
1185
  @router.get("/ide/status")
@@ -1096,6 +1252,7 @@ async def run_consolidation(request: Request):
1096
1252
 
1097
1253
  config = SLMConfig.load()
1098
1254
  learning_db = DB_PATH.parent / "learning.db"
1255
+ _require_manage_for_profile(request, config.active_profile)
1099
1256
  authorization = authorize_route_mutation(
1100
1257
  request,
1101
1258
  operation="update",
@@ -1213,6 +1370,7 @@ async def set_auto_invoke_config(request: Request):
1213
1370
 
1214
1371
  Body: {"enabled": true, "min_score": 0.15, "weights": {...}}
1215
1372
  """
1373
+ _require_manage(request)
1216
1374
  try:
1217
1375
  body = await request.json()
1218
1376
 
@@ -1254,10 +1412,10 @@ async def get_associations(
1254
1412
  profile: str = "",
1255
1413
  ):
1256
1414
  """Get association edges for a profile with content previews."""
1415
+ pid = _resolve_profile(request, profile)
1257
1416
  try:
1258
1417
  from superlocalmemory.server.routes.helpers import get_active_profile, DB_PATH
1259
1418
  import sqlite3
1260
- pid = profile or get_active_profile()
1261
1419
 
1262
1420
  if not DB_PATH.exists():
1263
1421
  return {"edges": [], "total": 0}
@@ -1321,10 +1479,10 @@ async def get_associations(
1321
1479
  @router.get("/associations/stats")
1322
1480
  async def get_association_stats(request: Request, profile: str = ""):
1323
1481
  """Get association graph statistics."""
1482
+ pid = _resolve_profile(request, profile)
1324
1483
  try:
1325
1484
  from superlocalmemory.server.routes.helpers import get_active_profile, DB_PATH
1326
1485
  import sqlite3
1327
- pid = profile or get_active_profile()
1328
1486
 
1329
1487
  if not DB_PATH.exists():
1330
1488
  return {
@@ -1422,12 +1580,12 @@ async def get_association_stats(request: Request, profile: str = ""):
1422
1580
  @router.get("/consolidation/status")
1423
1581
  async def get_consolidation_status(request: Request, profile: str = ""):
1424
1582
  """Get consolidation status and last run results."""
1583
+ pid = _resolve_profile(request, profile)
1425
1584
  try:
1426
1585
  from superlocalmemory.server.routes.helpers import get_active_profile, DB_PATH
1427
1586
  from superlocalmemory.core.config import SLMConfig
1428
1587
  import sqlite3
1429
1588
 
1430
- pid = profile or get_active_profile()
1431
1589
  config = SLMConfig.load()
1432
1590
  cons_cfg = config.consolidation
1433
1591
 
@@ -1520,7 +1678,8 @@ async def trigger_consolidation(request: Request):
1520
1678
  profile = body.get("profile", "")
1521
1679
 
1522
1680
  from superlocalmemory.server.routes.helpers import get_active_profile
1523
- pid = profile or get_active_profile()
1681
+ pid = _resolve_mutation_profile(profile)
1682
+ _require_manage_for_profile(request, pid)
1524
1683
  authorization = authorize_route_mutation(
1525
1684
  request,
1526
1685
  operation="update",
@@ -1590,10 +1749,10 @@ async def trigger_consolidation(request: Request):
1590
1749
  @router.get("/core-memory")
1591
1750
  async def get_core_memory(request: Request, profile: str = ""):
1592
1751
  """Get all Core Memory blocks for a profile."""
1752
+ pid = _resolve_profile(request, profile)
1593
1753
  try:
1594
1754
  from superlocalmemory.server.routes.helpers import get_active_profile, DB_PATH
1595
1755
  import sqlite3
1596
- pid = profile or get_active_profile()
1597
1756
 
1598
1757
  if not DB_PATH.exists():
1599
1758
  return {"blocks": [], "total_chars": 0, "char_limit": 2000}
@@ -1719,6 +1878,7 @@ async def update_core_memory_block(block_id: str, request: Request):
1719
1878
  @router.get("/vector-store/status")
1720
1879
  async def get_vector_store_status(request: Request, profile: str = ""):
1721
1880
  """Get VectorStore health and statistics."""
1881
+ pid = _resolve_profile(request, profile)
1722
1882
  try:
1723
1883
  from superlocalmemory.core.config import SLMConfig
1724
1884
  from superlocalmemory.server.routes.helpers import DB_PATH
@@ -1749,7 +1909,8 @@ async def get_vector_store_status(request: Request, profile: str = ""):
1749
1909
  try:
1750
1910
  conn = sqlite3.connect(str(DB_PATH))
1751
1911
  count = conn.execute(
1752
- "SELECT COUNT(*) FROM embedding_metadata"
1912
+ "SELECT COUNT(*) FROM embedding_metadata WHERE profile_id = ?",
1913
+ (pid,),
1753
1914
  ).fetchone()[0]
1754
1915
  result["total_vectors"] = count
1755
1916
  conn.close()
@@ -1779,10 +1940,10 @@ async def get_vector_store_status(request: Request, profile: str = ""):
1779
1940
  @router.get("/forgetting/stats")
1780
1941
  async def forgetting_stats(request: Request, profile: str = ""):
1781
1942
  """Get memory retention zone distribution."""
1943
+ pid = _resolve_profile(request, profile)
1782
1944
  try:
1783
1945
  from superlocalmemory.server.routes.helpers import get_active_profile, DB_PATH
1784
1946
  import sqlite3 as _sqlite3
1785
- pid = profile or get_active_profile()
1786
1947
 
1787
1948
  zones = {"active": 0, "warm": 0, "cold": 0, "archive": 0, "forgotten": 0}
1788
1949
  total = 0
@@ -1830,7 +1991,8 @@ async def run_forgetting(request: Request):
1830
1991
 
1831
1992
  from superlocalmemory.server.routes.helpers import get_active_profile, DB_PATH
1832
1993
  import sqlite3 as _sqlite3
1833
- pid = profile or get_active_profile()
1994
+ pid = _resolve_mutation_profile(profile)
1995
+ _require_manage_for_profile(request, pid)
1834
1996
 
1835
1997
  if not DB_PATH.exists():
1836
1998
  return {"success": False, "error": "Database not found"}
@@ -1907,10 +2069,10 @@ async def run_forgetting(request: Request):
1907
2069
  @router.get("/quantization/stats")
1908
2070
  async def quantization_stats(request: Request, profile: str = ""):
1909
2071
  """Get embedding quantization tier distribution."""
2072
+ pid = _resolve_profile(request, profile)
1910
2073
  try:
1911
2074
  from superlocalmemory.server.routes.helpers import get_active_profile, DB_PATH
1912
2075
  import sqlite3 as _sqlite3
1913
- pid = profile or get_active_profile()
1914
2076
 
1915
2077
  tiers = {"float32": 0, "int8": 0, "polar4": 0, "polar2": 0}
1916
2078
  total = 0
@@ -1970,10 +2132,10 @@ async def quantization_stats(request: Request, profile: str = ""):
1970
2132
  @router.get("/ccq/blocks")
1971
2133
  async def ccq_blocks(request: Request, profile: str = "", limit: int = 50):
1972
2134
  """Get CCQ consolidated blocks."""
2135
+ pid = _resolve_profile(request, profile)
1973
2136
  try:
1974
2137
  from superlocalmemory.server.routes.helpers import get_active_profile, DB_PATH
1975
2138
  import sqlite3 as _sqlite3
1976
- pid = profile or get_active_profile()
1977
2139
 
1978
2140
  if not DB_PATH.exists():
1979
2141
  return {"blocks": [], "total": 0}
@@ -2030,10 +2192,10 @@ async def ccq_blocks(request: Request, profile: str = "", limit: int = 50):
2030
2192
  @router.get("/soft-prompts")
2031
2193
  async def get_soft_prompts(request: Request, profile: str = ""):
2032
2194
  """Get active soft prompt templates."""
2195
+ pid = _resolve_profile(request, profile)
2033
2196
  try:
2034
2197
  from superlocalmemory.server.routes.helpers import get_active_profile, DB_PATH
2035
2198
  import sqlite3 as _sqlite3
2036
- pid = profile or get_active_profile()
2037
2199
 
2038
2200
  if not DB_PATH.exists():
2039
2201
  return {"prompts": [], "total": 0, "total_tokens": 0}
@@ -2083,6 +2245,7 @@ async def get_soft_prompts(request: Request, profile: str = ""):
2083
2245
  @router.get("/health/processes")
2084
2246
  async def process_health(request: Request):
2085
2247
  """Get SLM process health status."""
2248
+ _resolve_profile(request)
2086
2249
  try:
2087
2250
  import os as _os
2088
2251
 
@@ -2137,10 +2300,10 @@ async def get_graph_communities(request: Request, profile: str = ""):
2137
2300
  at consolidation time). Falls back to inline word frequency if labels
2138
2301
  not yet computed.
2139
2302
  """
2303
+ pid = _resolve_profile(request, profile)
2140
2304
  try:
2141
2305
  from superlocalmemory.server.routes.helpers import get_active_profile, DB_PATH
2142
2306
  import sqlite3
2143
- pid = profile or get_active_profile()
2144
2307
 
2145
2308
  if not DB_PATH.exists():
2146
2309
  return {"communities": [], "total": 0}
@@ -2267,6 +2430,7 @@ async def run_community_detection(request: Request):
2267
2430
  from superlocalmemory.core.graph_analyzer import GraphAnalyzer
2268
2431
 
2269
2432
  pid = get_active_profile()
2433
+ _require_manage_for_profile(request, pid)
2270
2434
  authorization = authorize_route_mutation(
2271
2435
  request,
2272
2436
  operation="update",
@@ -2289,10 +2453,10 @@ async def run_community_detection(request: Request):
2289
2453
  @router.get("/v33/overview")
2290
2454
  async def v33_overview(request: Request, profile: str = ""):
2291
2455
  """Get SLM 3.3 feature overview -- all new capabilities at a glance."""
2456
+ pid = _resolve_profile(request, profile)
2292
2457
  try:
2293
2458
  from superlocalmemory.server.routes.helpers import get_active_profile, DB_PATH
2294
2459
  import sqlite3 as _sqlite3
2295
- pid = profile or get_active_profile()
2296
2460
 
2297
2461
  overview: dict = {
2298
2462
  "version": "3.3",