superlocalmemory 3.4.16 → 3.4.18

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 (83) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/package.json +1 -3
  3. package/pyproject.toml +10 -1
  4. package/src/superlocalmemory/cli/setup_wizard.py +30 -0
  5. package/src/superlocalmemory/server/routes/entity.py +5 -9
  6. package/src/superlocalmemory/server/routes/helpers.py +120 -15
  7. package/src/superlocalmemory/server/routes/ingest.py +2 -3
  8. package/src/superlocalmemory/server/routes/v3_api.py +42 -2
  9. package/src/superlocalmemory/server/unified_daemon.py +21 -11
  10. package/src/superlocalmemory.egg-info/PKG-INFO +5 -2
  11. package/src/superlocalmemory.egg-info/requires.txt +3 -0
  12. package/docs/ARCHITECTURE.md +0 -149
  13. package/docs/api-reference.md +0 -284
  14. package/docs/auto-memory.md +0 -150
  15. package/docs/cli-reference.md +0 -327
  16. package/docs/cloud-backup.md +0 -174
  17. package/docs/compliance.md +0 -191
  18. package/docs/configuration.md +0 -182
  19. package/docs/getting-started.md +0 -102
  20. package/docs/ide-setup.md +0 -261
  21. package/docs/mcp-tools.md +0 -220
  22. package/docs/migration-from-v2.md +0 -170
  23. package/docs/profiles.md +0 -173
  24. package/docs/screenshots/01-dashboard-main.png +0 -0
  25. package/docs/screenshots/02-knowledge-graph.png +0 -0
  26. package/docs/screenshots/03-math-health.png +0 -0
  27. package/docs/screenshots/03-patterns-learning.png +0 -0
  28. package/docs/screenshots/04-learning-dashboard.png +0 -0
  29. package/docs/screenshots/04-recall-lab.png +0 -0
  30. package/docs/screenshots/05-behavioral-analysis.png +0 -0
  31. package/docs/screenshots/05-trust-dashboard.png +0 -0
  32. package/docs/screenshots/06-graph-communities.png +0 -0
  33. package/docs/screenshots/06-settings.png +0 -0
  34. package/docs/screenshots/07-memories-blurred.png +0 -0
  35. package/docs/skill-evolution.md +0 -256
  36. package/docs/troubleshooting.md +0 -310
  37. package/docs/v2-archive/ACCESSIBILITY.md +0 -291
  38. package/docs/v2-archive/ARCHITECTURE.md +0 -886
  39. package/docs/v2-archive/CLI-COMMANDS-REFERENCE.md +0 -425
  40. package/docs/v2-archive/COMPRESSION-README.md +0 -390
  41. package/docs/v2-archive/FRAMEWORK-INTEGRATIONS.md +0 -300
  42. package/docs/v2-archive/MCP-MANUAL-SETUP.md +0 -775
  43. package/docs/v2-archive/MCP-TROUBLESHOOTING.md +0 -787
  44. package/docs/v2-archive/PATTERN-LEARNING.md +0 -228
  45. package/docs/v2-archive/PROFILES-GUIDE.md +0 -453
  46. package/docs/v2-archive/RESET-GUIDE.md +0 -353
  47. package/docs/v2-archive/SEARCH-ENGINE-V2.2.0.md +0 -749
  48. package/docs/v2-archive/SEARCH-INTEGRATION-GUIDE.md +0 -502
  49. package/docs/v2-archive/UI-SERVER.md +0 -262
  50. package/docs/v2-archive/UNIVERSAL-INTEGRATION.md +0 -488
  51. package/docs/v2-archive/V2.2.0-OPTIONAL-SEARCH.md +0 -666
  52. package/docs/v2-archive/WINDOWS-INSTALL-README.txt +0 -34
  53. package/docs/v2-archive/WINDOWS-POST-INSTALL.txt +0 -45
  54. package/docs/v2-archive/example_graph_usage.py +0 -146
  55. package/ui/index.html +0 -1879
  56. package/ui/js/agents.js +0 -192
  57. package/ui/js/auto-settings.js +0 -399
  58. package/ui/js/behavioral.js +0 -276
  59. package/ui/js/clusters.js +0 -206
  60. package/ui/js/compliance.js +0 -252
  61. package/ui/js/core.js +0 -246
  62. package/ui/js/dashboard.js +0 -110
  63. package/ui/js/events.js +0 -178
  64. package/ui/js/fact-detail.js +0 -92
  65. package/ui/js/feedback.js +0 -333
  66. package/ui/js/graph-core.js +0 -447
  67. package/ui/js/graph-filters.js +0 -220
  68. package/ui/js/graph-interactions.js +0 -351
  69. package/ui/js/graph-ui.js +0 -214
  70. package/ui/js/ide-status.js +0 -102
  71. package/ui/js/init.js +0 -45
  72. package/ui/js/learning.js +0 -435
  73. package/ui/js/lifecycle.js +0 -298
  74. package/ui/js/math-health.js +0 -98
  75. package/ui/js/memories.js +0 -264
  76. package/ui/js/modal.js +0 -357
  77. package/ui/js/patterns.js +0 -93
  78. package/ui/js/profiles.js +0 -236
  79. package/ui/js/recall-lab.js +0 -292
  80. package/ui/js/search.js +0 -59
  81. package/ui/js/settings.js +0 -224
  82. package/ui/js/timeline.js +0 -32
  83. package/ui/js/trust-dashboard.js +0 -73
package/CHANGELOG.md CHANGED
@@ -10,6 +10,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10
10
 
11
11
  ---
12
12
 
13
+ ## [3.4.18] - 2026-04-17
14
+
15
+ ### Fixed
16
+ - pip and npm installs now ship identical functionality. Semantic search and cross-encoder reranking work out of the box on pip (previously required `pip install superlocalmemory[search]`).
17
+ - First pip run auto-installs Claude Code hooks when Claude Code is detected, matching the npm postinstall experience.
18
+
19
+ ---
20
+
21
+ ## [3.4.17] - 2026-04-17
22
+
23
+ ### Fixed
24
+ - Entity Explorer no longer stuck on "No entities found" after switching operating modes.
25
+ - Engine-backed routes (entity, ingest, recall, remember, list) auto-recover after mode changes — no daemon restart required.
26
+
27
+ ### Added
28
+ - Mode change audit log at `~/.superlocalmemory/logs/mode-audit.log`.
29
+ - Mode C now requires an explicit API key via Settings to prevent accidental cloud-mode writes.
30
+
31
+ ---
32
+
13
33
  ## Author
14
34
 
15
35
  **Varun Pratap Bhardwaj**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superlocalmemory",
3
- "version": "3.4.16",
3
+ "version": "3.4.18",
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",
@@ -65,8 +65,6 @@
65
65
  "src/",
66
66
  "ide/",
67
67
  "scripts/",
68
- "ui/",
69
- "docs/",
70
68
  "pyproject.toml",
71
69
  "README.md",
72
70
  "LICENSE",
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "superlocalmemory"
3
- version = "3.4.16"
3
+ version = "3.4.18"
4
4
  description = "Information-geometric agent memory with mathematical guarantees"
5
5
  readme = "README.md"
6
6
  license = {text = "AGPL-3.0-or-later"}
@@ -52,9 +52,18 @@ dependencies = [
52
52
  # V3.4.3: Unified Brain
53
53
  "psutil>=5.9.0",
54
54
  "structlog>=24.0.0,<27.0.0",
55
+ # V3.4.18: Semantic search + cross-encoder reranker (npm install parity).
56
+ # Previously under [search] extra — pip users silently lost 30pp of recall
57
+ # quality vs. npm users. Now ships by default for both install paths.
58
+ "sentence-transformers[onnx]>=5.0.0",
59
+ "torch>=2.2.0",
60
+ "scikit-learn>=1.3.0,<2.0.0",
55
61
  ]
56
62
 
57
63
  [project.optional-dependencies]
64
+ # `search` is now a no-op alias kept for backwards compatibility; the deps
65
+ # moved into core in v3.4.18. ``pip install superlocalmemory[search]`` still
66
+ # works but installs nothing extra.
58
67
  search = [
59
68
  "sentence-transformers[onnx]>=5.0.0",
60
69
  "einops>=0.8.2",
@@ -621,6 +621,10 @@ def check_first_use(command: str) -> None:
621
621
 
622
622
  Called from main.py before dispatching any command.
623
623
  Skips for commands that don't need setup (setup, hook, --version, --help).
624
+
625
+ On first use, also auto-installs Claude Code hooks so pip installs have
626
+ the same "it just works" experience as npm installs (npm does this via
627
+ postinstall; pip has no postinstall, so we do it here).
624
628
  """
625
629
  # Commands that work without setup
626
630
  _SKIP_COMMANDS = {"setup", "init", "hook", "hooks", "reap", "mcp"}
@@ -641,6 +645,7 @@ def check_first_use(command: str) -> None:
641
645
  _mark_complete()
642
646
  except Exception:
643
647
  pass
648
+ _maybe_install_hooks_on_first_use()
644
649
  return
645
650
 
646
651
  # Interactive: run the full wizard
@@ -648,6 +653,31 @@ def check_first_use(command: str) -> None:
648
653
  print(" First time using SuperLocalMemory!")
649
654
  print(" Running setup wizard...\n")
650
655
  run_wizard()
656
+ _maybe_install_hooks_on_first_use()
657
+
658
+
659
+ def _maybe_install_hooks_on_first_use() -> None:
660
+ """Install Claude Code hooks on first SLM run — matches npm postinstall.
661
+
662
+ Install/uninstall parity rules:
663
+ * Skip if the user explicitly opted out via ``slm hooks remove``
664
+ (creates ``~/.superlocalmemory/hooks/.hooks-disabled``).
665
+ * Skip if Claude Code isn't installed (no ``~/.claude/settings.json``
666
+ to merge into).
667
+ * Silent + best-effort: never fail a CLI command because of this.
668
+ """
669
+ try:
670
+ opt_out = _SLM_HOME / "hooks" / ".hooks-disabled"
671
+ if opt_out.exists():
672
+ return
673
+ claude_settings = Path.home() / ".claude" / "settings.json"
674
+ if not claude_settings.exists():
675
+ return # Claude Code not installed — nothing to hook into.
676
+ from superlocalmemory.hooks.claude_code_hooks import install_hooks
677
+ install_hooks()
678
+ except Exception:
679
+ # Best-effort: parity-fallback, never block CLI.
680
+ pass
651
681
 
652
682
 
653
683
  # ---------------------------------------------------------------------------
@@ -8,6 +8,8 @@ from __future__ import annotations
8
8
 
9
9
  from fastapi import APIRouter, HTTPException, Request, Query
10
10
 
11
+ from .helpers import require_engine
12
+
11
13
  router = APIRouter(prefix="/api/entity", tags=["entity"])
12
14
 
13
15
 
@@ -19,9 +21,7 @@ async def list_entities(
19
21
  offset: int = Query(default=0, ge=0),
20
22
  ):
21
23
  """List all entities with basic info (canonical name, type, fact count)."""
22
- engine = request.app.state.engine
23
- if engine is None:
24
- raise HTTPException(503, detail="Engine not initialized")
24
+ engine = require_engine(request)
25
25
 
26
26
  import sqlite3
27
27
  import json
@@ -75,9 +75,7 @@ async def get_entity(
75
75
  project: str = Query(default=""),
76
76
  ):
77
77
  """Get compiled truth + timeline for an entity."""
78
- engine = request.app.state.engine
79
- if engine is None:
80
- raise HTTPException(503, detail="Engine not initialized")
78
+ engine = require_engine(request)
81
79
 
82
80
  import sqlite3
83
81
  import json
@@ -121,9 +119,7 @@ async def recompile_entity(
121
119
  project: str = Query(default=""),
122
120
  ):
123
121
  """Force immediate recompilation of an entity."""
124
- engine = request.app.state.engine
125
- if engine is None:
126
- raise HTTPException(503, detail="Engine not initialized")
122
+ engine = require_engine(request)
127
123
 
128
124
  import sqlite3
129
125
  conn = sqlite3.connect(str(engine._config.db_path))
@@ -5,18 +5,26 @@
5
5
  - AGPL-3.0-or-later
6
6
 
7
7
  Shared utilities for all route modules: DB connection, dict factory,
8
- profile helper, validation, Pydantic models, config paths.
8
+ profile helper, validation, Pydantic models, config paths, and the
9
+ shared lazy engine accessor used by every engine-dependent route.
9
10
  """
11
+ import logging
10
12
  import re
11
13
  import json
12
14
  import sqlite3
15
+ import threading
16
+ import time
17
+ from datetime import datetime, timezone
13
18
  from pathlib import Path
14
19
  from typing import Optional
15
20
 
16
- from fastapi import HTTPException
21
+ from fastapi import HTTPException, Request
17
22
  from pydantic import BaseModel, Field
18
23
 
19
24
 
25
+ _engine_logger = logging.getLogger("superlocalmemory.engine")
26
+
27
+
20
28
  # ---------------------------------------------------------------------------
21
29
  # Version detection (shared — avoids circular import between ui.py ↔ v3_api.py)
22
30
  # ---------------------------------------------------------------------------
@@ -65,25 +73,122 @@ UI_DIR = Path(__file__).parent.parent / "ui"
65
73
  PROFILES_DIR = MEMORY_DIR / "profiles"
66
74
 
67
75
 
76
+ # ---------------------------------------------------------------------------
77
+ # Engine lifecycle — lazy, thread-safe, recoverable after mode changes.
78
+ # ---------------------------------------------------------------------------
79
+
80
+ _engine_init_lock = threading.Lock()
81
+ _last_engine_failure: float = 0.0
82
+ _ENGINE_FAILURE_COOLDOWN_S: float = 5.0
83
+
84
+
68
85
  def get_engine_lazy(app_state):
69
- """Get or lazily initialize the V3 engine. Returns engine or None."""
86
+ """Return ``app_state.engine``, initialising it if None.
87
+
88
+ Why this exists
89
+ ---------------
90
+ Mode-change endpoints (``PUT`` / ``POST /api/v3/mode[/set]``) set
91
+ ``app.state.engine = None`` so the next request picks up the new config.
92
+ Before this helper, no code path re-created the engine until the daemon
93
+ restarted, breaking every engine-backed route (entity, ingest, tiers,
94
+ recall, remember, list, status).
95
+
96
+ Contract
97
+ --------
98
+ * Returns the cached engine if already initialised.
99
+ * Otherwise acquires a process-wide lock and builds a fresh
100
+ ``MemoryEngine`` from the latest ``SLMConfig.load()``.
101
+ * Returns ``None`` if init fails. A brief cooldown prevents hammering
102
+ init when the underlying cause (e.g., corrupt DB) is persistent.
103
+ * Never uses a sticky "already attempted" flag — recovery must be
104
+ automatic after a transient failure.
105
+ """
106
+ global _last_engine_failure
107
+
70
108
  engine = getattr(app_state, "engine", None)
71
109
  if engine is not None:
72
110
  return engine
73
- if getattr(app_state, "_engine_init_attempted", False):
111
+
112
+ now = time.monotonic()
113
+ if _last_engine_failure and (now - _last_engine_failure) < _ENGINE_FAILURE_COOLDOWN_S:
74
114
  return None
115
+
116
+ with _engine_init_lock:
117
+ # Double-checked: another thread may have initialised while we waited.
118
+ engine = getattr(app_state, "engine", None)
119
+ if engine is not None:
120
+ return engine
121
+ try:
122
+ from superlocalmemory.core.config import SLMConfig
123
+ from superlocalmemory.core.engine import MemoryEngine
124
+ config = SLMConfig.load()
125
+ new_engine = MemoryEngine(config)
126
+ new_engine.initialize()
127
+ app_state.engine = new_engine
128
+ app_state.config = config
129
+ _engine_logger.info(
130
+ "Engine lazy-initialised (mode=%s, profile=%s)",
131
+ getattr(getattr(config, "mode", None), "value", "?"),
132
+ getattr(config, "active_profile", "?"),
133
+ )
134
+ _last_engine_failure = 0.0
135
+ return new_engine
136
+ except Exception as exc:
137
+ _engine_logger.warning("Engine lazy init failed: %s", exc)
138
+ _last_engine_failure = time.monotonic()
139
+ return None
140
+
141
+
142
+ def require_engine(request: Request):
143
+ """Return the engine or raise ``HTTPException(503)``.
144
+
145
+ Use this in every route that touches ``app.state.engine``. Replaces the
146
+ old ``engine = request.app.state.engine; if engine is None: raise ...``
147
+ pattern with a single call that also lazily re-initialises after a mode
148
+ change.
149
+ """
150
+ engine = get_engine_lazy(request.app.state)
151
+ if engine is None:
152
+ raise HTTPException(
153
+ status_code=503,
154
+ detail="Engine not initialized. Retry in a few seconds — it's warming up.",
155
+ )
156
+ return engine
157
+
158
+
159
+ def log_mode_change(
160
+ old_mode: str,
161
+ new_mode: str,
162
+ *,
163
+ provider: str = "",
164
+ model: str = "",
165
+ source: str = "api",
166
+ ) -> None:
167
+ """Append an audit entry for every mode change.
168
+
169
+ Lets us trace phantom writes (e.g., a stray dashboard-card button click
170
+ silently flipping the system to Mode C with ``anthropic/claude-sonnet-4``
171
+ as the auto-defaulted model).
172
+
173
+ Audit file: ``<base_dir>/logs/mode-audit.log`` (tab-separated).
174
+ """
175
+ audit_path = MEMORY_DIR / "logs" / "mode-audit.log"
75
176
  try:
76
- from superlocalmemory.core.config import SLMConfig
77
- from superlocalmemory.core.engine import MemoryEngine
78
- config = SLMConfig.load()
79
- engine = MemoryEngine(config)
80
- engine.initialize()
81
- app_state.engine = engine
82
- app_state._engine_init_attempted = True
83
- return engine
84
- except Exception:
85
- app_state._engine_init_attempted = True
86
- return None
177
+ audit_path.parent.mkdir(parents=True, exist_ok=True)
178
+ ts = datetime.now(timezone.utc).isoformat(timespec="seconds")
179
+ line = (
180
+ f"{ts}\told={old_mode}\tnew={new_mode}"
181
+ f"\tprovider={provider}\tmodel={model}\tsource={source}\n"
182
+ )
183
+ with open(audit_path, "a", encoding="utf-8") as handle:
184
+ handle.write(line)
185
+ except Exception as exc: # Logging must never break the mode-change path.
186
+ _engine_logger.warning("mode audit write failed: %s", exc)
187
+
188
+ _engine_logger.info(
189
+ "Mode change: %s→%s provider=%s model=%s (%s)",
190
+ old_mode, new_mode, provider, model, source,
191
+ )
87
192
 
88
193
 
89
194
  def get_db_connection() -> sqlite3.Connection:
@@ -44,9 +44,8 @@ async def ingest(req: IngestRequest, request: Request):
44
44
  """
45
45
  global _active_count
46
46
 
47
- engine = request.app.state.engine
48
- if engine is None:
49
- raise HTTPException(503, detail="Engine not initialized")
47
+ from .helpers import require_engine
48
+ engine = require_engine(request)
50
49
 
51
50
  if not req.content:
52
51
  raise HTTPException(400, detail="content required")
@@ -101,7 +101,28 @@ async def set_mode(request: Request):
101
101
 
102
102
  from superlocalmemory.core.config import SLMConfig
103
103
  from superlocalmemory.storage.models import Mode
104
+ from superlocalmemory.server.routes.helpers import log_mode_change
104
105
  old_config = SLMConfig.load()
106
+ old_mode = old_config.mode.value
107
+
108
+ # Safety: a bare ``{mode:"c"}`` body (e.g., a stray dashboard button
109
+ # click) used to silently auto-default the model to
110
+ # ``anthropic/claude-sonnet-4`` with no API key, writing phantom state
111
+ # into config.json. Refuse that path — Mode C requires explicit
112
+ # provider+key via POST /api/v3/mode/set.
113
+ if new_mode == "c" and not old_config.llm.api_key:
114
+ return JSONResponse(
115
+ {
116
+ "error": (
117
+ "Mode C requires a cloud API key. "
118
+ "Configure provider + key in Settings → Step 2 "
119
+ "(uses POST /api/v3/mode/set)."
120
+ ),
121
+ "code": "mode_c_requires_api_key",
122
+ },
123
+ status_code=400,
124
+ )
125
+
105
126
  new_config = SLMConfig.for_mode(
106
127
  Mode(new_mode),
107
128
  llm_provider=old_config.llm.provider,
@@ -112,13 +133,23 @@ async def set_mode(request: Request):
112
133
  new_config.active_profile = old_config.active_profile
113
134
  new_config.save()
114
135
 
136
+ # Audit the change before we lose context — proves who/when/what.
137
+ # Captures the phantom-write case where `for_mode(C)` auto-defaults
138
+ # the model to "anthropic/claude-sonnet-4" (see core/config.py).
139
+ log_mode_change(
140
+ old_mode, new_mode,
141
+ provider=new_config.llm.provider,
142
+ model=new_config.llm.model,
143
+ source="PUT /api/v3/mode",
144
+ )
145
+
115
146
  # V3.3: Check if embedding model changed — flag for re-indexing
116
147
  needs_reindex = (
117
148
  old_config.embedding.provider != new_config.embedding.provider
118
149
  or old_config.embedding.model_name != new_config.embedding.model_name
119
150
  )
120
151
 
121
- # Reset engine to pick up new config
152
+ # Invalidate engine; next engine-backed request lazy-inits with new config.
122
153
  if hasattr(request.app.state, "engine"):
123
154
  request.app.state.engine = None
124
155
 
@@ -147,6 +178,9 @@ async def set_full_config(request: Request):
147
178
 
148
179
  from superlocalmemory.core.config import SLMConfig
149
180
  from superlocalmemory.storage.models import Mode
181
+ from superlocalmemory.server.routes.helpers import log_mode_change
182
+ old = SLMConfig.load()
183
+ old_mode = old.mode.value
150
184
  config = SLMConfig.for_mode(
151
185
  Mode(new_mode),
152
186
  llm_provider=provider if provider != "none" else "",
@@ -154,10 +188,16 @@ async def set_full_config(request: Request):
154
188
  llm_api_key=api_key,
155
189
  llm_api_base="http://localhost:11434" if provider == "ollama" else "",
156
190
  )
157
- old = SLMConfig.load()
158
191
  config.active_profile = old.active_profile
159
192
  config.save()
160
193
 
194
+ log_mode_change(
195
+ old_mode, new_mode,
196
+ provider=config.llm.provider,
197
+ model=config.llm.model,
198
+ source="POST /api/v3/mode/set",
199
+ )
200
+
161
201
  # Kill existing worker so next request uses new config
162
202
  try:
163
203
  from superlocalmemory.core.worker_pool import WorkerPool
@@ -559,10 +559,25 @@ def _register_daemon_routes(application: FastAPI) -> None:
559
559
  """Add daemon-specific routes for CLI integration."""
560
560
  global _last_activity
561
561
 
562
+ from superlocalmemory.server.routes.helpers import get_engine_lazy
563
+
564
+ def _get_engine_or_503():
565
+ """Lazy-init engine; raise 503 if init fails.
566
+
567
+ Shared by every daemon route so a mode switch that nulled
568
+ ``application.state.engine`` never leaves the daemon stuck in
569
+ 503 until restart.
570
+ """
571
+ engine = get_engine_lazy(application.state)
572
+ if engine is None:
573
+ raise HTTPException(503, detail="Engine not initialized")
574
+ return engine
575
+
562
576
  @application.get("/health")
563
577
  async def health():
564
578
  _update_activity()
565
- engine = application.state.engine
579
+ # Non-blocking peek: report status without forcing a re-init.
580
+ engine = getattr(application.state, "engine", None)
566
581
  return {
567
582
  "status": "ok",
568
583
  "pid": os.getpid(),
@@ -574,9 +589,7 @@ def _register_daemon_routes(application: FastAPI) -> None:
574
589
  async def recall(q: str = "", query: str = "", limit: int = 20):
575
590
  _update_activity()
576
591
  search_query = q or query # Accept both ?q= and ?query= for compatibility
577
- engine = application.state.engine
578
- if engine is None:
579
- raise HTTPException(503, detail="Engine not initialized")
592
+ engine = _get_engine_or_503()
580
593
  if not search_query:
581
594
  return {"results": [], "count": 0, "query_type": "none", "retrieval_time_ms": 0}
582
595
  try:
@@ -605,9 +618,7 @@ def _register_daemon_routes(application: FastAPI) -> None:
605
618
  @application.post("/remember")
606
619
  async def remember(req: RememberRequest):
607
620
  _update_activity()
608
- engine = application.state.engine
609
- if engine is None:
610
- raise HTTPException(503, detail="Engine not initialized")
621
+ engine = _get_engine_or_503()
611
622
  try:
612
623
  metadata = {"tags": req.tags} if req.tags else {}
613
624
  fact_ids = engine.store(req.content, metadata=metadata)
@@ -624,7 +635,8 @@ def _register_daemon_routes(application: FastAPI) -> None:
624
635
  @application.get("/status")
625
636
  async def status():
626
637
  _update_activity()
627
- engine = application.state.engine
638
+ # Non-blocking peek — status must never force a re-init.
639
+ engine = getattr(application.state, "engine", None)
628
640
  fact_count = engine.fact_count if engine else 0
629
641
  mode = engine._config.mode.value if engine and hasattr(engine, '_config') else "unknown"
630
642
  return {
@@ -641,9 +653,7 @@ def _register_daemon_routes(application: FastAPI) -> None:
641
653
  @application.get("/list")
642
654
  async def list_facts(limit: int = 50):
643
655
  _update_activity()
644
- engine = application.state.engine
645
- if engine is None:
646
- raise HTTPException(503, detail="Engine not initialized")
656
+ engine = _get_engine_or_503()
647
657
  try:
648
658
  facts = engine.list_facts(limit=limit)
649
659
  items = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: superlocalmemory
3
- Version: 3.4.14
3
+ Version: 3.4.18
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
@@ -48,6 +48,9 @@ Requires-Dist: rustworkx<1,>=0.15
48
48
  Requires-Dist: watchdog<6,>=4.0
49
49
  Requires-Dist: psutil>=5.9.0
50
50
  Requires-Dist: structlog<27.0.0,>=24.0.0
51
+ Requires-Dist: sentence-transformers[onnx]>=5.0.0
52
+ Requires-Dist: torch>=2.2.0
53
+ Requires-Dist: scikit-learn<2.0.0,>=1.3.0
51
54
  Provides-Extra: search
52
55
  Requires-Dist: sentence-transformers[onnx]>=5.0.0; extra == "search"
53
56
  Requires-Dist: einops>=0.8.2; extra == "search"
@@ -634,7 +637,7 @@ Qualixar is building the open-source infrastructure for AI agent reliability eng
634
637
  | **[SLM Mesh](https://github.com/qualixar/slm-mesh)** | P2P coordination across AI agent sessions | `npm i slm-mesh` | — |
635
638
  | **[SLM MCP Hub](https://github.com/qualixar/slm-mcp-hub)** | Federate 430+ MCP tools through one gateway | `pip install slm-mcp-hub` | — |
636
639
  | **[AgentAssay](https://github.com/qualixar/agentassay)** | Token-efficient AI agent testing | `pip install agentassay` | [arXiv:2603.02601](https://arxiv.org/abs/2603.02601) |
637
- | **[AgentAssert](https://github.com/qualixar/agentassert-abc)** | Behavioral contracts + drift detection | `pip install agentassert` | [arXiv:2602.22302](https://arxiv.org/abs/2602.22302) |
640
+ | **[AgentAssert](https://github.com/qualixar/agentassert-abc)** | Behavioral contracts + drift detection | `pip install agentassert-abc` | [arXiv:2602.22302](https://arxiv.org/abs/2602.22302) |
638
641
  | **[SkillFortify](https://github.com/qualixar/skillfortify)** | Formal verification for AI agent skills | `pip install skillfortify` | [arXiv:2603.00195](https://arxiv.org/abs/2603.00195) |
639
642
 
640
643
  **Zero cloud dependency. Local-first. EU AI Act compliant.**
@@ -19,6 +19,9 @@ rustworkx<1,>=0.15
19
19
  watchdog<6,>=4.0
20
20
  psutil>=5.9.0
21
21
  structlog<27.0.0,>=24.0.0
22
+ sentence-transformers[onnx]>=5.0.0
23
+ torch>=2.2.0
24
+ scikit-learn<2.0.0,>=1.3.0
22
25
 
23
26
  [dev]
24
27
  pytest>=8.0