superlocalmemory 3.4.24 → 3.4.30

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 (47) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +8 -1
  3. package/package.json +1 -1
  4. package/pyproject.toml +3 -1
  5. package/src/superlocalmemory/__init__.py +1 -1
  6. package/src/superlocalmemory/cli/commands.py +3 -0
  7. package/src/superlocalmemory/cli/daemon.py +90 -16
  8. package/src/superlocalmemory/cli/doctor_cmd.py +152 -0
  9. package/src/superlocalmemory/cli/main.py +28 -0
  10. package/src/superlocalmemory/cli/post_install.py +15 -0
  11. package/src/superlocalmemory/cli/setup_wizard.py +20 -0
  12. package/src/superlocalmemory/cli/version_banner.py +183 -0
  13. package/src/superlocalmemory/cli/wizard_v3426_options.py +129 -0
  14. package/src/superlocalmemory/core/clock_monitor.py +45 -0
  15. package/src/superlocalmemory/core/db_pool.py +80 -0
  16. package/src/superlocalmemory/core/engine.py +75 -30
  17. package/src/superlocalmemory/core/engine_capabilities.py +24 -0
  18. package/src/superlocalmemory/core/engine_lock.py +75 -0
  19. package/src/superlocalmemory/core/error_catalog.py +113 -0
  20. package/src/superlocalmemory/core/error_envelope.py +60 -0
  21. package/src/superlocalmemory/core/file_lock.py +92 -0
  22. package/src/superlocalmemory/core/loop_watchdog.py +56 -0
  23. package/src/superlocalmemory/core/priority_queue.py +61 -0
  24. package/src/superlocalmemory/core/queue_dispatcher.py +73 -0
  25. package/src/superlocalmemory/core/rate_limit.py +151 -0
  26. package/src/superlocalmemory/core/recall_pipeline.py +2 -0
  27. package/src/superlocalmemory/core/recall_queue.py +370 -0
  28. package/src/superlocalmemory/core/recall_worker.py +10 -0
  29. package/src/superlocalmemory/core/safe_fs.py +108 -0
  30. package/src/superlocalmemory/hooks/auto_capture.py +34 -12
  31. package/src/superlocalmemory/hooks/auto_recall.py +36 -9
  32. package/src/superlocalmemory/mcp/_daemon_proxy.py +107 -0
  33. package/src/superlocalmemory/mcp/_pool_adapter.py +121 -0
  34. package/src/superlocalmemory/mcp/resources.py +8 -5
  35. package/src/superlocalmemory/mcp/server.py +38 -9
  36. package/src/superlocalmemory/mcp/tools_active.py +21 -9
  37. package/src/superlocalmemory/mcp/tools_core.py +13 -9
  38. package/src/superlocalmemory/mcp/tools_evolution.py +4 -2
  39. package/src/superlocalmemory/mcp/tools_learning.py +5 -3
  40. package/src/superlocalmemory/mcp/tools_mesh.py +5 -3
  41. package/src/superlocalmemory/mcp/tools_v3.py +18 -22
  42. package/src/superlocalmemory/mcp/tools_v33.py +65 -2
  43. package/src/superlocalmemory/migrations/__init__.py +5 -0
  44. package/src/superlocalmemory/migrations/v3_4_25_to_v3_4_26.py +144 -0
  45. package/src/superlocalmemory/server/routes/events.py +1 -1
  46. package/src/superlocalmemory/server/routes/v3_api.py +0 -1
  47. package/src/superlocalmemory/server/unified_daemon.py +128 -12
@@ -59,6 +59,7 @@ _PORT_FILE = Path.home() / ".superlocalmemory" / "daemon.port"
59
59
  class RememberRequest(BaseModel):
60
60
  content: str
61
61
  tags: str = ""
62
+ metadata: dict | None = None # v3.4.26: pass-through from MCP pool_store
62
63
 
63
64
 
64
65
  class ObserveRequest(BaseModel):
@@ -952,23 +953,56 @@ def _register_daemon_routes(application: FastAPI) -> None:
952
953
  response = engine.recall(
953
954
  search_query, limit=limit, session_id=effective_sid,
954
955
  )
955
- results = [
956
- {
956
+ # v3.4.26: return the same field shape as recall_worker so
957
+ # MCP processes proxying through the daemon get recall_trace-
958
+ # compatible data without a second round trip.
959
+ memory_ids = list({
960
+ r.fact.memory_id for r in response.results[:limit]
961
+ if r.fact.memory_id
962
+ })
963
+ memory_map = (
964
+ engine._db.get_memory_content_batch(memory_ids)
965
+ if memory_ids else {}
966
+ )
967
+ results = []
968
+ for r in response.results[:limit]:
969
+ fact_type = getattr(r.fact, "fact_type", None)
970
+ lifecycle = getattr(r.fact, "lifecycle", None)
971
+ results.append({
972
+ "fact_id": r.fact.fact_id,
973
+ "memory_id": r.fact.memory_id,
957
974
  "content": r.fact.content,
975
+ "source_content": memory_map.get(r.fact.memory_id, ""),
958
976
  "score": round(r.score, 4),
959
- "fact_type": getattr(r.fact.fact_type, 'value', str(r.fact.fact_type)),
960
- "fact_id": r.fact.fact_id,
977
+ "confidence": round(r.confidence, 4),
978
+ "trust_score": round(r.trust_score, 4),
961
979
  "channel_scores": {
962
- k: round(v, 4) for k, v in r.channel_scores.items()
963
- } if r.channel_scores else {},
964
- }
965
- for r in response.results
966
- ]
980
+ k: round(v, 4)
981
+ for k, v in (r.channel_scores or {}).items()
982
+ },
983
+ "fact_type": fact_type.value
984
+ if fact_type and hasattr(fact_type, "value")
985
+ else getattr(r.fact, "fact_type", ""),
986
+ "lifecycle": lifecycle.value
987
+ if lifecycle and hasattr(lifecycle, "value") else "",
988
+ "access_count": getattr(r.fact, "access_count", 0),
989
+ "evidence_chain": list(
990
+ getattr(r, "evidence_chain", []) or []
991
+ ),
992
+ })
967
993
  return {
968
- "results": results,
969
- "count": len(results),
994
+ "ok": True,
995
+ "query": search_query,
970
996
  "query_type": response.query_type,
997
+ "result_count": len(results),
971
998
  "retrieval_time_ms": round(response.retrieval_time_ms, 1),
999
+ "channel_weights": {
1000
+ k: round(v, 3)
1001
+ for k, v in (response.channel_weights or {}).items()
1002
+ },
1003
+ "total_candidates": getattr(response, "total_candidates", 0),
1004
+ "results": results,
1005
+ "count": len(results),
972
1006
  }
973
1007
  except Exception as exc:
974
1008
  raise HTTPException(500, detail=str(exc))
@@ -979,8 +1013,11 @@ def _register_daemon_routes(application: FastAPI) -> None:
979
1013
  engine = _get_engine_or_503()
980
1014
  try:
981
1015
  metadata = {"tags": req.tags} if req.tags else {}
1016
+ extra = getattr(req, "metadata", None)
1017
+ if isinstance(extra, dict):
1018
+ metadata.update(extra)
982
1019
  fact_ids = engine.store(req.content, metadata=metadata)
983
- return {"fact_ids": fact_ids, "count": len(fact_ids)}
1020
+ return {"ok": True, "fact_ids": fact_ids, "count": len(fact_ids)}
984
1021
  except Exception as exc:
985
1022
  raise HTTPException(500, detail=str(exc))
986
1023
 
@@ -990,6 +1027,71 @@ def _register_daemon_routes(application: FastAPI) -> None:
990
1027
  result = _observe_buffer.enqueue(req.content)
991
1028
  return result
992
1029
 
1030
+ # v3.4.26: CCQ consolidation via daemon so MCP clients don't need to
1031
+ # import CognitiveConsolidator (which pulls sentence-transformers).
1032
+ @application.post("/consolidate/cognitive")
1033
+ async def consolidate_cognitive_endpoint(body: dict):
1034
+ _update_activity()
1035
+ engine = _get_engine_or_503()
1036
+ try:
1037
+ pid = body.get("profile_id") or engine.profile_id
1038
+ from superlocalmemory.encoding.cognitive_consolidator import (
1039
+ CognitiveConsolidator,
1040
+ )
1041
+ consolidator = CognitiveConsolidator(db=engine._db)
1042
+ result = consolidator.run_pipeline(pid)
1043
+ return {
1044
+ "ok": True,
1045
+ "profile_id": pid,
1046
+ "clusters_processed": result.clusters_processed,
1047
+ "blocks_created": result.blocks_created,
1048
+ }
1049
+ except Exception as exc:
1050
+ raise HTTPException(500, detail=str(exc))
1051
+
1052
+ # v3.4.26: run_maintenance via daemon so MCP doesn't import
1053
+ # EbbinghausCurve, ForgettingScheduler, or ConsolidationWorker.
1054
+ @application.post("/maintenance/run")
1055
+ async def run_maintenance_endpoint(body: dict):
1056
+ _update_activity()
1057
+ engine = _get_engine_or_503()
1058
+ try:
1059
+ pid = body.get("profile_id") or engine.profile_id
1060
+ results: dict = {}
1061
+ try:
1062
+ from superlocalmemory.core.maintenance import run_maintenance as _run_maint
1063
+ maint_result = _run_maint(engine._db, engine._config, pid)
1064
+ results["langevin"] = {"updated": maint_result.get("updated", 0)}
1065
+ except Exception as exc:
1066
+ results["langevin"] = {"error": str(exc)}
1067
+ try:
1068
+ from superlocalmemory.math.ebbinghaus import EbbinghausCurve
1069
+ from superlocalmemory.learning.forgetting_scheduler import (
1070
+ ForgettingScheduler,
1071
+ )
1072
+ ebb = EbbinghausCurve(engine._config.forgetting)
1073
+ sched = ForgettingScheduler(
1074
+ engine._db, ebb, engine._config.forgetting,
1075
+ )
1076
+ results["forgetting"] = sched.run_decay_cycle(pid, force=False)
1077
+ except Exception as exc:
1078
+ results["forgetting"] = {"error": str(exc)}
1079
+ try:
1080
+ from superlocalmemory.learning.consolidation_worker import (
1081
+ ConsolidationWorker,
1082
+ )
1083
+ cw = ConsolidationWorker(
1084
+ engine._db.db_path,
1085
+ engine._db.db_path.parent / "learning.db",
1086
+ )
1087
+ count = cw._generate_patterns(pid, False)
1088
+ results["behavioral"] = {"patterns_mined": count}
1089
+ except Exception as exc:
1090
+ results["behavioral"] = {"error": str(exc)}
1091
+ return {"ok": True, "profile": pid, **results}
1092
+ except Exception as exc:
1093
+ raise HTTPException(500, detail=str(exc))
1094
+
993
1095
  @application.get("/status")
994
1096
  async def status():
995
1097
  _update_activity()
@@ -1104,6 +1206,20 @@ def start_server(port: int = _DEFAULT_PORT) -> None:
1104
1206
  _PORT_FILE.write_text(str(port))
1105
1207
  _start_time = time.monotonic()
1106
1208
 
1209
+ try:
1210
+ from superlocalmemory.migrations.v3_4_25_to_v3_4_26 import (
1211
+ is_ready as _is_ready, migrate as _migrate,
1212
+ )
1213
+ _data = Path(os.environ.get("SLM_DATA_DIR")
1214
+ or Path.home() / ".superlocalmemory")
1215
+ if not _is_ready(_data):
1216
+ _migrate(_data)
1217
+ except Exception as exc:
1218
+ import logging as _logging
1219
+ _logging.getLogger(__name__).warning(
1220
+ "v3.4.26 migration on daemon start failed: %s", exc,
1221
+ )
1222
+
1107
1223
  # v3.4.7: Start memory watchdog to prevent runaway workers
1108
1224
  _start_memory_watchdog()
1109
1225