superlocalmemory 3.0.36 → 3.1.0

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.
@@ -339,6 +339,13 @@ async def recall_trace(request: Request):
339
339
  except Exception:
340
340
  pass
341
341
 
342
+ # Record learning signals (non-blocking, non-critical)
343
+ try:
344
+ _record_learning_signals(query, result.get("results", []))
345
+ except Exception as _sig_exc:
346
+ import logging as _log
347
+ _log.getLogger(__name__).warning("Learning signal error: %s", _sig_exc)
348
+
342
349
  return {
343
350
  "query": query,
344
351
  "query_type": result.get("query_type", "unknown"),
@@ -351,6 +358,44 @@ async def recall_trace(request: Request):
351
358
  return JSONResponse({"error": str(e)}, status_code=500)
352
359
 
353
360
 
361
+ def _record_learning_signals(query: str, results: list) -> None:
362
+ """Record feedback + co-retrieval + confidence boost for any recall."""
363
+ from pathlib import Path
364
+ from superlocalmemory.core.config import SLMConfig
365
+
366
+ slm_dir = Path.home() / ".superlocalmemory"
367
+ config = SLMConfig.load()
368
+ pid = config.active_profile
369
+ fact_ids = [r.get("fact_id", "") for r in results[:10] if r.get("fact_id")]
370
+ if not fact_ids:
371
+ return
372
+
373
+ try:
374
+ from superlocalmemory.learning.feedback import FeedbackCollector
375
+ collector = FeedbackCollector(slm_dir / "learning.db")
376
+ collector.record_implicit(
377
+ profile_id=pid, query=query,
378
+ fact_ids_returned=fact_ids, fact_ids_available=fact_ids,
379
+ )
380
+ except Exception:
381
+ pass
382
+
383
+ try:
384
+ from superlocalmemory.learning.signals import LearningSignals
385
+ signals = LearningSignals(slm_dir / "learning.db")
386
+ signals.record_co_retrieval(pid, fact_ids)
387
+ except Exception:
388
+ pass
389
+
390
+ try:
391
+ from superlocalmemory.learning.signals import LearningSignals
392
+ mem_db = str(slm_dir / "memory.db")
393
+ for fid in fact_ids[:5]:
394
+ LearningSignals.boost_confidence(mem_db, fid)
395
+ except Exception:
396
+ pass
397
+
398
+
354
399
  # ── Trust Dashboard ──────────────────────────────────────────
355
400
 
356
401
  @router.get("/trust/dashboard")
@@ -521,3 +566,51 @@ async def ide_connect(request: Request):
521
566
  return {"results": results}
522
567
  except Exception as e:
523
568
  return JSONResponse({"error": str(e)}, status_code=500)
569
+
570
+
571
+ # ── Active Memory (V3.1) ────────────────────────────────────
572
+
573
+ @router.get("/learning/signals")
574
+ async def learning_signals():
575
+ """Get zero-cost learning signal statistics."""
576
+ try:
577
+ from superlocalmemory.learning.signals import LearningSignals
578
+ from superlocalmemory.core.config import SLMConfig
579
+ from superlocalmemory.server.routes.helpers import DB_PATH
580
+ learning_db = DB_PATH.parent / "learning.db"
581
+ signals = LearningSignals(learning_db)
582
+ config = SLMConfig.load()
583
+ pid = config.active_profile
584
+ return {"success": True, **signals.get_signal_stats(pid)}
585
+ except Exception as exc:
586
+ return {"success": False, "error": str(exc)}
587
+
588
+
589
+ @router.post("/learning/consolidate")
590
+ async def run_consolidation(request: Request):
591
+ """Run sleep-time consolidation. Body: {dry_run: true/false}."""
592
+ try:
593
+ body = await request.json()
594
+ dry_run = body.get("dry_run", False)
595
+ from superlocalmemory.learning.consolidation_worker import ConsolidationWorker
596
+ from superlocalmemory.core.config import SLMConfig
597
+ from superlocalmemory.server.routes.helpers import DB_PATH
598
+ worker = ConsolidationWorker(
599
+ memory_db=str(DB_PATH),
600
+ learning_db=str(DB_PATH.parent / "learning.db"),
601
+ )
602
+ config = SLMConfig.load()
603
+ stats = worker.run(config.active_profile, dry_run=dry_run)
604
+ return {"success": True, **stats}
605
+ except Exception as exc:
606
+ return {"success": False, "error": str(exc)}
607
+
608
+
609
+ @router.get("/hooks/status")
610
+ async def hooks_status():
611
+ """Check if Claude Code hooks are installed."""
612
+ try:
613
+ from superlocalmemory.hooks.claude_code_hooks import check_status
614
+ return {"success": True, **check_status()}
615
+ except Exception as exc:
616
+ return {"success": False, "error": str(exc)}