superlocalmemory 3.8.2 → 3.8.3
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 +15 -0
- package/README.md +2 -2
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +1 -1
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/plugin-src/skills/slm-cache/SKILL.md +1 -1
- package/plugin-src/skills/slm-compress/SKILL.md +1 -1
- package/plugin-src/skills/slm-graph/SKILL.md +1 -1
- package/plugin-src/skills/slm-recall/SKILL.md +1 -1
- package/plugin-src/skills/slm-remember/SKILL.md +1 -1
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/pyproject.toml +1 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/server/routes/memories.py +72 -23
- package/src/superlocalmemory/server/unified_daemon.py +83 -8
- package/src/superlocalmemory/ui/js/core.js +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ 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.8.3] - 2026-07-24 — Recall stays responsive under heavy load
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Recall no longer hangs when the system is busy.** Search and recall now
|
|
13
|
+
complete within a generous time budget even during background maintenance or
|
|
14
|
+
when many agents are querying at once. If a query can't finish in that window,
|
|
15
|
+
SLM returns keyword-matched results and marks them clearly instead of leaving
|
|
16
|
+
the request to time out. This applies everywhere recall runs — the dashboard
|
|
17
|
+
search, the CLI, and connected assistants — so results are consistent across
|
|
18
|
+
every surface.
|
|
19
|
+
- **Dashboard search waits long enough for a real answer.** The dashboard now
|
|
20
|
+
allows recall the full server-side budget before giving up, so heavy-load
|
|
21
|
+
queries return results rather than an aborted-request error.
|
|
22
|
+
|
|
8
23
|
## [3.8.2] - 2026-07-24 — Self-healing upgrades & faster, consistent recall
|
|
9
24
|
|
|
10
25
|
### Added
|
package/README.md
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
</picture>
|
|
6
6
|
</p>
|
|
7
7
|
|
|
8
|
-
<h1 align="center">SuperLocalMemory V3.8.
|
|
8
|
+
<h1 align="center">SuperLocalMemory V3.8.3</h1>
|
|
9
9
|
<p align="center"><strong>Enterprise-grade, local-first memory for AI agents and teams.</strong><br/>
|
|
10
10
|
<em>A persistent, auditable long-term brain for your agents that runs on your own infrastructure — with multi-workspace isolation, role-based access, and GDPR + EU AI Act governance controls built in.</em></p>
|
|
11
|
-
<p align="center"><code>v3.8.
|
|
11
|
+
<p align="center"><code>v3.8.3</code> — one control plane: auditable retrieval · multi-scope memory (personal / shared / global) · Cache · Compress · trusted-peer Mesh · bounded loops — across CLI, MCP, dashboard, the <strong>Claude plugin</strong>, the <strong>Codex add-on</strong>, and documented IDE integrations.<br/>
|
|
12
12
|
Proxy: <code>slm wrap claude</code> · MCP: add <code>slm_compress</code> to your config · Skill: zero-config</p>
|
|
13
13
|
<p align="center"><strong>3 public research preprints</strong> (arXiv + Zenodo archives) · <a href="https://arxiv.org/abs/2603.02240">arXiv:2603.02240</a> · <a href="https://arxiv.org/abs/2603.14588">arXiv:2603.14588</a> · <a href="https://arxiv.org/abs/2604.04514">arXiv:2604.04514</a></p>
|
|
14
14
|
|
package/package.json
CHANGED
package/plugin/CLAUDE.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- BEGIN SuperLocalMemory v3.8.
|
|
1
|
+
<!-- BEGIN SuperLocalMemory v3.8.3 -->
|
|
2
2
|
|
|
3
3
|
## SuperLocalMemory (SLM) — Agent Rules
|
|
4
4
|
|
|
@@ -39,6 +39,6 @@ slm-recall · slm-remember · slm-session · slm-status · slm-cache · slm-comp
|
|
|
39
39
|
### Subagents
|
|
40
40
|
slm-memory-advisor (memory decisions, session hygiene, scope/profile guidance) · slm-optimize-advisor (context compression + KV cache) · slm-governance-advisor (scope/roles/compliance/GDPR)
|
|
41
41
|
|
|
42
|
-
<!-- END SuperLocalMemory v3.8.
|
|
42
|
+
<!-- END SuperLocalMemory v3.8.3 -->
|
|
43
43
|
|
|
44
|
-
SuperLocalMemory v3.8.
|
|
44
|
+
SuperLocalMemory v3.8.3 · Qualixar · AGPL-3.0-or-later
|
|
@@ -77,4 +77,4 @@ slm-scope · slm-governance · slm-profile · slm-remember · slm-recall
|
|
|
77
77
|
# What NOT to do
|
|
78
78
|
Never session_init twice; never forget without dry-run preview; never store secrets; never bypass role checks; never claim an erasure succeeded without verifying via recall.
|
|
79
79
|
|
|
80
|
-
SuperLocalMemory v3.8.
|
|
80
|
+
SuperLocalMemory v3.8.3 · Qualixar · AGPL-3.0-or-later
|
|
@@ -46,4 +46,4 @@ slm-recall · slm-remember · slm-session · slm-scope · slm-profile · slm-gov
|
|
|
46
46
|
# What NOT to do
|
|
47
47
|
Never session_init twice; never forget dry_run=False without reporting preview; never dump a whole file into remember; never invent a memory; never claim "saved" without success:true / clean CLI exit; never bypass scope or governance restrictions.
|
|
48
48
|
|
|
49
|
-
SuperLocalMemory v3.8.
|
|
49
|
+
SuperLocalMemory v3.8.3 · Qualixar · AGPL-3.0-or-later
|
|
@@ -41,4 +41,4 @@ slm-compress · slm-cache · slm-status · slm-profile
|
|
|
41
41
|
# What NOT to do
|
|
42
42
|
Never compress code-for-edit/JSON-to-parse/<500 chars; never store secrets/ccr_ids; never let optimize failure block/alter the task; never claim a specific savings %; never carry ccr_ids across profile switches.
|
|
43
43
|
|
|
44
|
-
SuperLocalMemory v3.8.
|
|
44
|
+
SuperLocalMemory v3.8.3 · Qualixar · AGPL-3.0-or-later
|
package/plugin/requirements.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
superlocalmemory==3.8.
|
|
1
|
+
superlocalmemory==3.8.3
|
|
@@ -128,4 +128,4 @@ When the SLM MCP server is unavailable, use these CLI equivalents:
|
|
|
128
128
|
- **slm-optimize-advisor** — context compression and KV cache
|
|
129
129
|
- **slm-governance-advisor** — scope/role compliance, retention policies, GDPR
|
|
130
130
|
|
|
131
|
-
SuperLocalMemory v3.8.
|
|
131
|
+
SuperLocalMemory v3.8.3 · Qualixar · AGPL-3.0-or-later
|
package/pyproject.toml
CHANGED
|
@@ -32,7 +32,7 @@ if "OMP_NUM_THREADS" not in os.environ:
|
|
|
32
32
|
os.environ["OMP_NUM_THREADS"] = "2"
|
|
33
33
|
# ---------------------------------------------------------------------------
|
|
34
34
|
|
|
35
|
-
__version__ = "3.8.
|
|
35
|
+
__version__ = "3.8.3"
|
|
36
36
|
|
|
37
37
|
_REQUIRED_VERSIONS = {
|
|
38
38
|
"sentence_transformers": "5.3.0",
|
|
@@ -19,6 +19,25 @@ from .helpers import (
|
|
|
19
19
|
logger = logging.getLogger("superlocalmemory.routes.memories")
|
|
20
20
|
router = APIRouter()
|
|
21
21
|
|
|
22
|
+
# v3.8.3: GENEROUS latency budget for recall. SLM's value is quality recall
|
|
23
|
+
# under heavy multi-agent load, so semantic recall is given ample time to
|
|
24
|
+
# finish — the keyword fallback is a LAST-RESORT safety net for a genuine hang
|
|
25
|
+
# (e.g. a wedged embedder), NOT an aggressive speed cutoff. Only if recall
|
|
26
|
+
# exceeds this budget do we serve the fast keyword search so the caller ALWAYS
|
|
27
|
+
# gets a result instead of hanging forever. Tune with SLM_SEARCH_RECALL_TIMEOUT_S.
|
|
28
|
+
# The dashboard's fetch timeout is set ABOVE this so the browser waits for the
|
|
29
|
+
# quality result rather than aborting early.
|
|
30
|
+
_DEFAULT_RECALL_BUDGET_S = 25.0
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _search_recall_timeout_s() -> float:
|
|
34
|
+
import os
|
|
35
|
+
try:
|
|
36
|
+
v = float(os.environ.get("SLM_SEARCH_RECALL_TIMEOUT_S", ""))
|
|
37
|
+
return v if v > 0 else _DEFAULT_RECALL_BUDGET_S
|
|
38
|
+
except (TypeError, ValueError):
|
|
39
|
+
return _DEFAULT_RECALL_BUDGET_S
|
|
40
|
+
|
|
22
41
|
|
|
23
42
|
def _internal_error(detail: str = "Internal server error") -> HTTPException:
|
|
24
43
|
"""SEC-H-02: log the full traceback server-side; return a generic message.
|
|
@@ -526,35 +545,65 @@ async def search_memories(request: Request, body: SearchRequest):
|
|
|
526
545
|
_window = getattr(body, "window", None) or ""
|
|
527
546
|
if not _window and getattr(body, "date_from", None) and getattr(body, "date_to", None):
|
|
528
547
|
_window = f"{body.date_from}..{body.date_to}"
|
|
529
|
-
|
|
548
|
+
# v3.8.3: bound the synchronous recall. Under a concurrent
|
|
549
|
+
# maintenance pass or a busy embedder it can run tens of seconds
|
|
550
|
+
# and the browser aborts the fetch. If it exceeds the budget we
|
|
551
|
+
# fall through to the fast keyword search below, so the dashboard
|
|
552
|
+
# ALWAYS returns instead of failing with an abort.
|
|
553
|
+
_recall_future = loop.run_in_executor(
|
|
530
554
|
None,
|
|
531
555
|
lambda: engine.recall(
|
|
532
556
|
body.query, limit=body.limit, fast=True,
|
|
533
557
|
window=_window or None,
|
|
534
558
|
),
|
|
535
559
|
)
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
"
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
560
|
+
# A run_in_executor thread cannot be cancelled, and wait_for() on it
|
|
561
|
+
# blocks until the thread finishes (defeating the timeout). So poll
|
|
562
|
+
# the future without blocking the event loop and give up at the
|
|
563
|
+
# deadline — the orphaned recall completes in the background and its
|
|
564
|
+
# result is discarded. This is what bounds dashboard-search latency.
|
|
565
|
+
_budget = _search_recall_timeout_s()
|
|
566
|
+
_deadline = loop.time() + _budget
|
|
567
|
+
while not _recall_future.done() and loop.time() < _deadline:
|
|
568
|
+
await asyncio.sleep(0.05)
|
|
569
|
+
if _recall_future.done():
|
|
570
|
+
response = _recall_future.result()
|
|
571
|
+
else:
|
|
572
|
+
# Ensure the orphaned future's eventual result/exception is
|
|
573
|
+
# retrieved so asyncio doesn't log "never retrieved".
|
|
574
|
+
_recall_future.add_done_callback(
|
|
575
|
+
lambda f: (f.cancelled() or f.exception())
|
|
576
|
+
)
|
|
577
|
+
logger.warning(
|
|
578
|
+
"search_memories: semantic recall exceeded %.0fs budget for "
|
|
579
|
+
"%r — serving keyword fallback",
|
|
580
|
+
_budget, (body.query or "")[:80],
|
|
581
|
+
)
|
|
582
|
+
response = None
|
|
583
|
+
if response is not None:
|
|
584
|
+
elapsed_ms = round((_time.monotonic() - t0) * 1000, 1)
|
|
585
|
+
from superlocalmemory.server.recall_serializer import (
|
|
586
|
+
recall_response_metadata,
|
|
587
|
+
serialize_recall_response,
|
|
588
|
+
)
|
|
589
|
+
results, no_confident_match = serialize_recall_response(
|
|
590
|
+
response,
|
|
591
|
+
limit=body.limit,
|
|
592
|
+
per_fact_max=300,
|
|
593
|
+
total_max=max(300, body.limit * 300),
|
|
594
|
+
)
|
|
595
|
+
return {
|
|
596
|
+
"query": body.query,
|
|
597
|
+
"results": results,
|
|
598
|
+
"total": len(results),
|
|
599
|
+
"query_type": getattr(response, "query_type", "semantic"),
|
|
600
|
+
"retrieval_time_ms": elapsed_ms,
|
|
601
|
+
"no_confident_match": no_confident_match,
|
|
602
|
+
**recall_response_metadata(response),
|
|
603
|
+
}
|
|
604
|
+
# recall timed out — fall through to the fast keyword search.
|
|
605
|
+
|
|
606
|
+
# Fallback: direct DB text search (engine not ready OR recall over budget)
|
|
558
607
|
conn = get_db_connection()
|
|
559
608
|
conn.row_factory = dict_factory
|
|
560
609
|
cursor = conn.cursor()
|
|
@@ -453,6 +453,61 @@ def _emit_event(
|
|
|
453
453
|
import asyncio as _asyncio
|
|
454
454
|
_recall_semaphore = _asyncio.Semaphore(3)
|
|
455
455
|
|
|
456
|
+
|
|
457
|
+
def _recall_budget_s() -> float:
|
|
458
|
+
"""Generous latency budget for a recall before the keyword fallback (v3.8.3).
|
|
459
|
+
|
|
460
|
+
SLM's value is quality recall under heavy multi-agent load, so semantic
|
|
461
|
+
recall is given ample time; the keyword fallback is a LAST-RESORT safety
|
|
462
|
+
net for a genuine hang (e.g. a wedged embedder), not a speed cutoff. Tune
|
|
463
|
+
with SLM_SEARCH_RECALL_TIMEOUT_S (shared with the dashboard search route).
|
|
464
|
+
"""
|
|
465
|
+
import os
|
|
466
|
+
try:
|
|
467
|
+
v = float(os.environ.get("SLM_SEARCH_RECALL_TIMEOUT_S", ""))
|
|
468
|
+
return v if v > 0 else 25.0
|
|
469
|
+
except (TypeError, ValueError):
|
|
470
|
+
return 25.0
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def _recall_keyword_fallback(engine, query: str, limit: int) -> dict:
|
|
474
|
+
"""Fast profile-scoped keyword (LIKE) fallback for /recall.
|
|
475
|
+
|
|
476
|
+
Used only when semantic recall exceeds its budget, so CLI/MCP callers get
|
|
477
|
+
a bounded response instead of hanging. Mirrors the dashboard /api/search
|
|
478
|
+
fallback shape (retrieval_mode=degraded_lexical).
|
|
479
|
+
"""
|
|
480
|
+
results = []
|
|
481
|
+
try:
|
|
482
|
+
rows = engine._db.execute(
|
|
483
|
+
"SELECT fact_id, content, confidence FROM atomic_facts "
|
|
484
|
+
"WHERE profile_id = ? AND content LIKE ? "
|
|
485
|
+
"ORDER BY confidence DESC LIMIT ?",
|
|
486
|
+
(engine.profile_id, f"%{query}%", limit),
|
|
487
|
+
)
|
|
488
|
+
for pos, r in enumerate(rows, start=1):
|
|
489
|
+
d = dict(r)
|
|
490
|
+
results.append({
|
|
491
|
+
"fact_id": d.get("fact_id"),
|
|
492
|
+
"content": (d.get("content") or "")[:2400],
|
|
493
|
+
"score": None, "relevance_score": None, "ranking_score": None,
|
|
494
|
+
"confidence": d.get("confidence"),
|
|
495
|
+
"rank_position": pos,
|
|
496
|
+
})
|
|
497
|
+
except Exception as exc:
|
|
498
|
+
logger.warning("recall keyword fallback failed (non-fatal): %s", exc)
|
|
499
|
+
return {
|
|
500
|
+
"ok": True,
|
|
501
|
+
"query": query,
|
|
502
|
+
"query_type": "text_search",
|
|
503
|
+
"retrieval_mode": "degraded_lexical",
|
|
504
|
+
"degraded_reason": "recall_budget_exceeded",
|
|
505
|
+
"result_count": len(results),
|
|
506
|
+
"results": results,
|
|
507
|
+
"count": len(results),
|
|
508
|
+
"no_confident_match": True,
|
|
509
|
+
}
|
|
510
|
+
|
|
456
511
|
# v3.4.52: Embedding model warm state. Set to True by the async pre-warm
|
|
457
512
|
# thread once Ollama has loaded the embedding model. /health reports this
|
|
458
513
|
# so MCP clients can wait for warm state before issuing recall calls.
|
|
@@ -3013,15 +3068,35 @@ def _register_daemon_routes(application: FastAPI) -> None:
|
|
|
3013
3068
|
if not fast:
|
|
3014
3069
|
await _recall_semaphore.acquire()
|
|
3015
3070
|
try:
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3071
|
+
# v3.8.3: bound the recall so CLI/MCP callers never hang on a
|
|
3072
|
+
# wedged embedder. Poll the executor future (which cannot be
|
|
3073
|
+
# cancelled) without blocking the loop, and give quality recall a
|
|
3074
|
+
# GENEROUS budget; only if it is exceeded do we serve the fast
|
|
3075
|
+
# keyword fallback. The orphaned recall finishes in the background.
|
|
3076
|
+
loop = asyncio.get_running_loop()
|
|
3077
|
+
_rf = loop.run_in_executor(
|
|
3078
|
+
None,
|
|
3079
|
+
lambda: engine.recall(
|
|
3080
|
+
search_query, limit=limit, session_id=effective_sid,
|
|
3081
|
+
agent_id=recall_actor,
|
|
3082
|
+
fast=fast,
|
|
3083
|
+
include_global=include_global,
|
|
3084
|
+
include_shared=include_shared,
|
|
3085
|
+
window=window or None,
|
|
3086
|
+
),
|
|
3024
3087
|
)
|
|
3088
|
+
_budget = _recall_budget_s()
|
|
3089
|
+
_deadline = loop.time() + _budget
|
|
3090
|
+
while not _rf.done() and loop.time() < _deadline:
|
|
3091
|
+
await asyncio.sleep(0.05)
|
|
3092
|
+
if not _rf.done():
|
|
3093
|
+
_rf.add_done_callback(lambda f: (f.cancelled() or f.exception()))
|
|
3094
|
+
logger.warning(
|
|
3095
|
+
"recall: semantic recall exceeded %.0fs budget for %r — "
|
|
3096
|
+
"serving keyword fallback", _budget, (search_query or "")[:80],
|
|
3097
|
+
)
|
|
3098
|
+
return _recall_keyword_fallback(engine, search_query, limit)
|
|
3099
|
+
response = _rf.result()
|
|
3025
3100
|
# v3.4.26: return the same field shape as recall_worker so
|
|
3026
3101
|
# MCP processes proxying through the daemon get recall_trace-
|
|
3027
3102
|
# compatible data without a second round trip.
|
|
@@ -13,7 +13,12 @@
|
|
|
13
13
|
// surfaces as a normal rejection and the UI can show a clear error.
|
|
14
14
|
// ============================================================================
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
// v3.8.3: 30s (> the server's 25s recall budget) so the browser waits for a
|
|
17
|
+
// quality recall under heavy multi-agent load instead of aborting it early
|
|
18
|
+
// ("signal is aborted without reason"). The server self-bounds recall and
|
|
19
|
+
// falls back to keyword within its budget, so this only ever waits longer for
|
|
20
|
+
// a genuinely stuck request. Per-call init.timeoutMs still overrides.
|
|
21
|
+
window.SLM_FETCH_TIMEOUT_MS = 30000;
|
|
17
22
|
window.SLM_INSTALL_TOKEN_KEY = 'slm_install_token';
|
|
18
23
|
|
|
19
24
|
// B2 (3.7.9): the install token is kept in a private closure, never in
|