superlocalmemory 3.4.59 → 3.4.61

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 CHANGED
@@ -5,6 +5,68 @@ All notable changes to SuperLocalMemory V3 will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.4.61] - 2026-05-31 — Dashboard search fix (in-process engine)
9
+
10
+ **Fixes dashboard search always timing out** with "signal is aborted without reason".
11
+
12
+ ### Root Cause
13
+ `POST /api/search` (used by the SLM dashboard memories pane) called
14
+ `WorkerPool.shared()` — the legacy subprocess-based worker pool from v3.4.32
15
+ (pre-unified-daemon). This spawned a fresh Python subprocess and loaded the full
16
+ SLM engine cold on **every single search request**, taking 15–20s. The browser's
17
+ AbortController always fired before the response arrived.
18
+
19
+ The `/recall` HTTP endpoint (used by MCP `session_init`) uses the daemon engine
20
+ directly and is warm at <1s. The dashboard used a completely different code path.
21
+
22
+ ### Fix
23
+ `search_memories` now calls `_get_engine(request).recall()` — the daemon's own
24
+ in-process engine that is already loaded and shares the warm SQLite page cache.
25
+ Falls back to direct LIKE text search if engine is unavailable during startup.
26
+
27
+ ### Result
28
+ Dashboard search: **<1s warm** (was >15s → browser abort).
29
+ GitHub sync failure shown in sidebar is a separate backup connectivity issue,
30
+ unrelated to search.
31
+
32
+ ### Changed
33
+ - `server/routes/memories.py`: `search_memories` uses daemon engine, not WorkerPool
34
+
35
+ ---
36
+
37
+ ## [3.4.60] - 2026-05-31 — Daemon OpenMP Crash Hotfix
38
+
39
+ **Hotfix for v3.4.59.** Forces `OMP_NUM_THREADS=1` and `KMP_DUPLICATE_LIB_OK=TRUE`
40
+ in the daemon subprocess environment BEFORE Python imports any C extensions that
41
+ bundle their own libomp.dylib (torch, scikit-learn, lightgbm).
42
+
43
+ ### Root Cause
44
+ Setting these env vars in `superlocalmemory/__init__.py` (v3.4.58 fix) was too
45
+ late on Apple Silicon (M5 Pro). By the time `import superlocalmemory` runs, the
46
+ parent process has often already loaded one of the OpenMP-using extensions, and
47
+ that libomp's thread pool is initialized. When lightgbm later forks its worker
48
+ pool from `LGBM_DatasetCreateFromMat`, the parent's libomp thread structs are
49
+ incompatible with lightgbm's libomp → SIGSEGV at
50
+ `__kmp_suspend_initialize_thread` reading address `0x580`.
51
+
52
+ ### Fix
53
+ `cli/daemon.py:_start_daemon_subprocess()` now copies `os.environ`, sets
54
+ `OMP_NUM_THREADS=1` + `KMP_DUPLICATE_LIB_OK=TRUE`, and passes it via the
55
+ `env=` kwarg to `subprocess.Popen`. The daemon Python interpreter starts
56
+ with these vars already in its environment, so C extension imports see the
57
+ correct values during their `_init` constructors.
58
+
59
+ ### Why 1 thread and not 2
60
+ v3.4.58 set `OMP_NUM_THREADS=2` as a compromise. On M5 Pro the parallel-fork
61
+ race still triggered at 2. SLM's actual ML workloads (ranker retrain, dedup)
62
+ operate on datasets of 50–5,000 rows where 1-thread serial OpenMP is within
63
+ ~10% of multi-threaded but eliminates the crash class entirely.
64
+
65
+ ### Changed
66
+ - `cli/daemon.py`: `_start_daemon_subprocess()` injects `env={OMP_NUM_THREADS=1, KMP_DUPLICATE_LIB_OK=TRUE}` into Popen
67
+
68
+ ---
69
+
8
70
  ## [3.4.59] - 2026-05-31 — Graph Edge Cap + Recall Reliability
9
71
 
10
72
  **Fixes SLM falling into degraded FTS5 mode on every session start**, and stops
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superlocalmemory",
3
- "version": "3.4.59",
3
+ "version": "3.4.61",
4
4
  "description": "Information-geometric agent memory with mathematical guarantees. 4-channel retrieval, Fisher-Rao similarity, zero-LLM mode, EU AI Act compliant. Works with Claude, Cursor, Windsurf, and 17+ AI tools.",
5
5
  "keywords": [
6
6
  "ai-memory",
@@ -88,4 +88,4 @@
88
88
  "dependencies": {
89
89
  "docx": "^9.5.1"
90
90
  }
91
- }
91
+ }
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "superlocalmemory"
3
- version = "3.4.59"
3
+ version = "3.4.61"
4
4
  description = "Information-geometric agent memory with mathematical guarantees"
5
5
  readme = "README.md"
6
6
  license = {text = "AGPL-3.0-or-later"}
@@ -28,7 +28,7 @@ if "OMP_NUM_THREADS" not in os.environ:
28
28
  os.environ["OMP_NUM_THREADS"] = "2"
29
29
  # ---------------------------------------------------------------------------
30
30
 
31
- __version__ = "3.4.59"
31
+ __version__ = "3.4.61"
32
32
 
33
33
  _REQUIRED_VERSIONS = {
34
34
  "sentence_transformers": "5.3.0",
@@ -171,6 +171,17 @@ def _start_daemon_subprocess() -> bool:
171
171
  else:
172
172
  kwargs["start_new_session"] = True
173
173
 
174
+ # v3.4.60: Force OMP_NUM_THREADS=1 in daemon env BEFORE Python imports
175
+ # numpy/torch/lightgbm. Setting it in __init__.py is too late on M5 Pro —
176
+ # by the time superlocalmemory.__init__ runs, libomp has already been
177
+ # initialized by an earlier import, causing the SIGSEGV at
178
+ # __kmp_suspend_initialize_thread when lightgbm forks its worker pool.
179
+ # Forcing serial OpenMP eliminates the parallel barrier race entirely.
180
+ daemon_env = os.environ.copy()
181
+ daemon_env["OMP_NUM_THREADS"] = "1"
182
+ daemon_env["KMP_DUPLICATE_LIB_OK"] = "TRUE"
183
+ kwargs["env"] = daemon_env
184
+
174
185
  with open(log_file, "a") as lf:
175
186
  proc = subprocess.Popen(cmd, stdout=lf, stderr=lf, **kwargs)
176
187
 
@@ -398,27 +398,47 @@ async def get_graph(
398
398
 
399
399
  @router.post("/api/search")
400
400
  async def search_memories(request: Request, body: SearchRequest):
401
- """Semantic search via subprocess worker pool (memory-isolated).
401
+ """Semantic search using the daemon's in-process engine.
402
402
 
403
- v3.4.32: marks recall in-flight so the pending materializer yields.
403
+ v3.4.61: Replaced WorkerPool.shared() (subprocess-based, cold-starts on
404
+ every request, always >15s) with the daemon's own engine that is already
405
+ loaded and warm. WorkerPool.shared() was legacy from v3.4.32 before the
406
+ unified daemon architecture. Using the daemon engine matches what the /recall
407
+ HTTP endpoint does and shares its warm SQLite page cache, bringing dashboard
408
+ search from >15s timeout to <1s warm.
409
+
410
+ Falls back to direct DB LIKE search if engine is unavailable.
404
411
  """
405
412
  from superlocalmemory.core.recall_gate import begin_recall, end_recall
406
413
  begin_recall()
407
414
  try:
408
- from superlocalmemory.core.worker_pool import WorkerPool
409
- pool = WorkerPool.shared()
410
- result = pool.recall(body.query, limit=body.limit)
411
-
412
- if result.get("ok"):
415
+ # Use the daemon engine directly — already loaded, shares warm cache
416
+ engine = _get_engine(request)
417
+ if engine is not None:
418
+ import time as _time
419
+ t0 = _time.monotonic()
420
+ response = engine.recall(body.query, limit=body.limit)
421
+ elapsed_ms = round((_time.monotonic() - t0) * 1000, 1)
422
+ results = []
423
+ for r in response.results[: body.limit]:
424
+ results.append({
425
+ "fact_id": r.fact.fact_id,
426
+ "memory_id": getattr(r.fact, "memory_id", ""),
427
+ "content": r.fact.content[:300],
428
+ "score": round(r.score, 4),
429
+ "confidence": round(getattr(r, "confidence", 0.0), 4),
430
+ "channel_scores": getattr(r, "channel_scores", {}),
431
+ "created_at": getattr(r.fact, "created_at", ""),
432
+ })
413
433
  return {
414
434
  "query": body.query,
415
- "results": result.get("results", []),
416
- "total": result.get("result_count", 0),
417
- "query_type": result.get("query_type", "unknown"),
418
- "retrieval_time_ms": result.get("retrieval_time_ms", 0),
435
+ "results": results,
436
+ "total": len(results),
437
+ "query_type": getattr(response, "query_type", "semantic"),
438
+ "retrieval_time_ms": elapsed_ms,
419
439
  }
420
440
 
421
- # Fallback: direct DB text search (no engine needed)
441
+ # Fallback: direct DB text search (engine not yet initialised)
422
442
  conn = get_db_connection()
423
443
  conn.row_factory = dict_factory
424
444
  cursor = conn.cursor()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: superlocalmemory
3
- Version: 3.4.59
3
+ Version: 3.4.61
4
4
  Summary: Information-geometric agent memory with mathematical guarantees
5
5
  Author-email: Varun Pratap Bhardwaj <admin@superlocalmemory.com>
6
6
  License: AGPL-3.0-or-later