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.
- package/CHANGELOG.md +43 -0
- package/README.md +8 -1
- package/package.json +1 -1
- package/pyproject.toml +3 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +3 -0
- package/src/superlocalmemory/cli/daemon.py +90 -16
- package/src/superlocalmemory/cli/doctor_cmd.py +152 -0
- package/src/superlocalmemory/cli/main.py +28 -0
- package/src/superlocalmemory/cli/post_install.py +15 -0
- package/src/superlocalmemory/cli/setup_wizard.py +20 -0
- package/src/superlocalmemory/cli/version_banner.py +183 -0
- package/src/superlocalmemory/cli/wizard_v3426_options.py +129 -0
- package/src/superlocalmemory/core/clock_monitor.py +45 -0
- package/src/superlocalmemory/core/db_pool.py +80 -0
- package/src/superlocalmemory/core/engine.py +75 -30
- package/src/superlocalmemory/core/engine_capabilities.py +24 -0
- package/src/superlocalmemory/core/engine_lock.py +75 -0
- package/src/superlocalmemory/core/error_catalog.py +113 -0
- package/src/superlocalmemory/core/error_envelope.py +60 -0
- package/src/superlocalmemory/core/file_lock.py +92 -0
- package/src/superlocalmemory/core/loop_watchdog.py +56 -0
- package/src/superlocalmemory/core/priority_queue.py +61 -0
- package/src/superlocalmemory/core/queue_dispatcher.py +73 -0
- package/src/superlocalmemory/core/rate_limit.py +151 -0
- package/src/superlocalmemory/core/recall_pipeline.py +2 -0
- package/src/superlocalmemory/core/recall_queue.py +370 -0
- package/src/superlocalmemory/core/recall_worker.py +10 -0
- package/src/superlocalmemory/core/safe_fs.py +108 -0
- package/src/superlocalmemory/hooks/auto_capture.py +34 -12
- package/src/superlocalmemory/hooks/auto_recall.py +36 -9
- package/src/superlocalmemory/mcp/_daemon_proxy.py +107 -0
- package/src/superlocalmemory/mcp/_pool_adapter.py +121 -0
- package/src/superlocalmemory/mcp/resources.py +8 -5
- package/src/superlocalmemory/mcp/server.py +38 -9
- package/src/superlocalmemory/mcp/tools_active.py +21 -9
- package/src/superlocalmemory/mcp/tools_core.py +13 -9
- package/src/superlocalmemory/mcp/tools_evolution.py +4 -2
- package/src/superlocalmemory/mcp/tools_learning.py +5 -3
- package/src/superlocalmemory/mcp/tools_mesh.py +5 -3
- package/src/superlocalmemory/mcp/tools_v3.py +18 -22
- package/src/superlocalmemory/mcp/tools_v33.py +65 -2
- package/src/superlocalmemory/migrations/__init__.py +5 -0
- package/src/superlocalmemory/migrations/v3_4_25_to_v3_4_26.py +144 -0
- package/src/superlocalmemory/server/routes/events.py +1 -1
- package/src/superlocalmemory/server/routes/v3_api.py +0 -1
- 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
|
-
|
|
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
|
-
"
|
|
960
|
-
"
|
|
977
|
+
"confidence": round(r.confidence, 4),
|
|
978
|
+
"trust_score": round(r.trust_score, 4),
|
|
961
979
|
"channel_scores": {
|
|
962
|
-
k: round(v, 4)
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
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
|
-
"
|
|
969
|
-
"
|
|
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
|
|