superlocalmemory 3.8.10 → 3.8.12
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 +91 -0
- package/README.md +7 -3
- 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/cli/commands.py +28 -6
- package/src/superlocalmemory/cli/daemon.py +219 -10
- package/src/superlocalmemory/cli/setup_wizard.py +45 -1
- package/src/superlocalmemory/core/component_registry.py +25 -0
- package/src/superlocalmemory/core/config.py +35 -1
- package/src/superlocalmemory/core/engine_wiring.py +81 -5
- package/src/superlocalmemory/core/recall_pipeline.py +25 -4
- package/src/superlocalmemory/core/reranker_worker.py +78 -17
- package/src/superlocalmemory/infra/daemon_identity.py +16 -0
- package/src/superlocalmemory/infra/process_identity.py +180 -0
- package/src/superlocalmemory/learning/feedback.py +328 -27
- package/src/superlocalmemory/learning/legacy_migration.py +45 -4
- package/src/superlocalmemory/learning/pattern_miner.py +31 -11
- package/src/superlocalmemory/mcp/_daemon_proxy.py +23 -1
- package/src/superlocalmemory/mcp/tools_active.py +179 -17
- package/src/superlocalmemory/mcp/tools_core.py +6 -5
- package/src/superlocalmemory/retrieval/remote_reranker.py +636 -0
- package/src/superlocalmemory/retrieval/reranker.py +52 -5
- package/src/superlocalmemory/server/unified_daemon.py +4 -0
- package/src/superlocalmemory/storage/migration_runner.py +9 -0
- package/src/superlocalmemory/storage/migrations/M033_learning_feedback_channel.py +77 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +2 -0
|
@@ -84,6 +84,21 @@ _WARMUP_MAX_ATTEMPTS = int(os.environ.get("SLM_RERANKER_WARMUP_ATTEMPTS", "5"))
|
|
|
84
84
|
_WARMUP_RETRY_BACKOFF_S = float(os.environ.get("SLM_RERANKER_WARMUP_BACKOFF", "3"))
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
# Substrings that mark a load failure as a configuration problem rather than a
|
|
88
|
+
# transient one. Retrying these can never succeed, so the warmup aborts on the
|
|
89
|
+
# first occurrence instead of spending _WARMUP_MAX_ATTEMPTS × backoff on them.
|
|
90
|
+
_PERMANENT_LOAD_ERROR_MARKERS = (
|
|
91
|
+
"unknown backend",
|
|
92
|
+
"sentence-transformers is not installed",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _is_permanent_load_error(error: str) -> bool:
|
|
97
|
+
"""True when a worker load error cannot be fixed by retrying."""
|
|
98
|
+
lowered = (error or "").lower()
|
|
99
|
+
return any(m in lowered for m in _PERMANENT_LOAD_ERROR_MARKERS)
|
|
100
|
+
|
|
101
|
+
|
|
87
102
|
class CrossEncoderReranker:
|
|
88
103
|
"""Rerank candidate facts using a local cross-encoder model.
|
|
89
104
|
|
|
@@ -201,11 +216,43 @@ class CrossEncoderReranker:
|
|
|
201
216
|
resp.get("warmup_inference", False),
|
|
202
217
|
)
|
|
203
218
|
return
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
219
|
+
# v3.8.11 (issue #103): this used to report
|
|
220
|
+
# "(timeout=90s)" for EVERY failure, including loads
|
|
221
|
+
# that failed instantly. A user whose retries were 3s
|
|
222
|
+
# apart was told each one timed out after 90s, and the
|
|
223
|
+
# worker's actual error was never printed at all.
|
|
224
|
+
# Distinguish the two cases and surface the real cause.
|
|
225
|
+
if resp is None:
|
|
226
|
+
logger.warning(
|
|
227
|
+
"Reranker warmup attempt %d/%d: no response "
|
|
228
|
+
"from worker within %ds; retrying",
|
|
229
|
+
attempt, _WARMUP_MAX_ATTEMPTS,
|
|
230
|
+
_WARMUP_LOAD_TIMEOUT,
|
|
231
|
+
)
|
|
232
|
+
else:
|
|
233
|
+
load_error = (
|
|
234
|
+
resp.get("error")
|
|
235
|
+
or "worker reported not-ready without an error"
|
|
236
|
+
)
|
|
237
|
+
# A misconfiguration cannot fix itself. Retrying a
|
|
238
|
+
# bad backend name or a missing dependency four
|
|
239
|
+
# more times burns ~7.5 minutes of daemon startup
|
|
240
|
+
# to reach the same answer (issue #103). Fail fast
|
|
241
|
+
# and say exactly what to change.
|
|
242
|
+
if _is_permanent_load_error(load_error):
|
|
243
|
+
logger.error(
|
|
244
|
+
"Reranker disabled — configuration error: "
|
|
245
|
+
"%s. Not retrying. Fix the config or set "
|
|
246
|
+
"retrieval.use_cross_encoder=false; recall "
|
|
247
|
+
"continues with fusion scores.",
|
|
248
|
+
load_error,
|
|
249
|
+
)
|
|
250
|
+
return
|
|
251
|
+
logger.warning(
|
|
252
|
+
"Reranker warmup attempt %d/%d failed: %s; "
|
|
253
|
+
"retrying",
|
|
254
|
+
attempt, _WARMUP_MAX_ATTEMPTS, load_error,
|
|
255
|
+
)
|
|
209
256
|
|
|
210
257
|
if attempt < _WARMUP_MAX_ATTEMPTS and not self._model_loaded:
|
|
211
258
|
if self._shutdown_event.wait(
|
|
@@ -534,6 +534,10 @@ def _recall_keyword_fallback(engine, query: str, limit: int) -> dict:
|
|
|
534
534
|
"results": results,
|
|
535
535
|
"count": len(results),
|
|
536
536
|
"no_confident_match": True,
|
|
537
|
+
# PR #101: every other recall path returns this key, so clients format
|
|
538
|
+
# it unconditionally. Omitting it here made the degraded path — the one
|
|
539
|
+
# that fires when recall is ALREADY struggling — crash the CLI.
|
|
540
|
+
"retrieval_time_ms": 0,
|
|
537
541
|
}
|
|
538
542
|
|
|
539
543
|
# v3.4.52: Embedding model warm state. Set to True by the async pre-warm
|
|
@@ -128,6 +128,9 @@ from superlocalmemory.storage.migrations import (
|
|
|
128
128
|
from superlocalmemory.storage.migrations import (
|
|
129
129
|
M032_write_coordinator_admission as _M032,
|
|
130
130
|
)
|
|
131
|
+
from superlocalmemory.storage.migrations import (
|
|
132
|
+
M033_learning_feedback_channel as _M033,
|
|
133
|
+
)
|
|
131
134
|
|
|
132
135
|
# Map migration name → module (used for the optional ``verify(conn)`` hook
|
|
133
136
|
# that lets the runner detect "already applied" state when an idempotent
|
|
@@ -164,6 +167,7 @@ _MODULES = {
|
|
|
164
167
|
_M030.NAME: _M030,
|
|
165
168
|
_M031.NAME: _M031,
|
|
166
169
|
_M032.NAME: _M032,
|
|
170
|
+
_M033.NAME: _M033,
|
|
167
171
|
}
|
|
168
172
|
|
|
169
173
|
logger = logging.getLogger(__name__)
|
|
@@ -220,6 +224,11 @@ MIGRATIONS: list[Migration] = [
|
|
|
220
224
|
# observations for ShadowTest persistence across daemon restart.
|
|
221
225
|
Migration(name=_M012.NAME, db_target="learning", ddl=_M012.DDL,
|
|
222
226
|
dependencies=(_M003.NAME,)),
|
|
227
|
+
# M033 adds learning_feedback.channel, which pattern_miner has always
|
|
228
|
+
# queried but which no schema ever defined. Its DDL creates the table
|
|
229
|
+
# when absent, so it needs no dependency beyond the migration log.
|
|
230
|
+
Migration(name=_M033.NAME, db_target="learning", ddl=_M033.DDL,
|
|
231
|
+
dependencies=(_M003.NAME,)),
|
|
223
232
|
Migration(name=_M004.NAME, db_target="memory", ddl=_M004.DDL),
|
|
224
233
|
# M007 creates pending_outcomes (memory.db, LLD-00 §1.2).
|
|
225
234
|
Migration(name=_M007.NAME, db_target="memory", ddl=_M007.DDL),
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory v3.8.11
|
|
4
|
+
|
|
5
|
+
"""M033 — add the ``channel`` column to ``learning_feedback``.
|
|
6
|
+
|
|
7
|
+
``pattern_miner._mine_channel_and_coretrieval`` has always executed::
|
|
8
|
+
|
|
9
|
+
SELECT channel, COUNT(*) AS cnt, AVG(signal_value) AS avg_signal
|
|
10
|
+
FROM learning_feedback GROUP BY channel
|
|
11
|
+
|
|
12
|
+
but ``channel`` was never defined on the table. Every database therefore
|
|
13
|
+
raised ``sqlite3.OperationalError: no such column: channel``. That error was
|
|
14
|
+
caught by the miner's outer ``except Exception`` and logged at DEBUG, so the
|
|
15
|
+
failure was invisible — and because the channel query runs FIRST inside that
|
|
16
|
+
try block, it also aborted the co-retrieval mining below it. One missing
|
|
17
|
+
column silently disabled two pattern types (issue #102, "Patterns learned
|
|
18
|
+
remains 0" despite a restored backup: restoring rows cannot fix a schema gap).
|
|
19
|
+
|
|
20
|
+
Additive only — ``ALTER TABLE ADD COLUMN`` with a default. No data loss and no
|
|
21
|
+
type changes; existing rows get ``'unknown'``, which groups cleanly rather than
|
|
22
|
+
being dropped by the miner's ``GROUP BY``.
|
|
23
|
+
|
|
24
|
+
``learning_feedback`` is bootstrapped at runtime by
|
|
25
|
+
``learning.feedback.FeedbackCollector._ensure_schema`` rather than by a
|
|
26
|
+
migration, so the DDL below CREATEs it (without ``channel``) when absent
|
|
27
|
+
before the ALTER. That keeps all three states correct:
|
|
28
|
+
|
|
29
|
+
- table missing -> CREATE, then ALTER adds ``channel``
|
|
30
|
+
- table present, no column -> CREATE is a no-op, ALTER adds ``channel``
|
|
31
|
+
- table already migrated -> ``verify()`` returns True, runner skips entirely
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import sqlite3
|
|
37
|
+
|
|
38
|
+
NAME = "M033_learning_feedback_channel"
|
|
39
|
+
DB_TARGET = "learning"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def verify(conn: sqlite3.Connection) -> bool:
|
|
43
|
+
"""Return True if ``learning_feedback.channel`` already exists."""
|
|
44
|
+
try:
|
|
45
|
+
cols = {
|
|
46
|
+
row[1] for row in
|
|
47
|
+
conn.execute("PRAGMA table_info(learning_feedback)").fetchall()
|
|
48
|
+
}
|
|
49
|
+
except sqlite3.Error:
|
|
50
|
+
return False
|
|
51
|
+
# An empty set means the table does not exist yet — not migrated.
|
|
52
|
+
return bool(cols) and "channel" in cols
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
DDL = """
|
|
56
|
+
BEGIN IMMEDIATE;
|
|
57
|
+
|
|
58
|
+
CREATE TABLE IF NOT EXISTS learning_feedback (
|
|
59
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
60
|
+
profile_id TEXT NOT NULL,
|
|
61
|
+
fact_id TEXT NOT NULL,
|
|
62
|
+
signal_type TEXT NOT NULL,
|
|
63
|
+
signal_value REAL NOT NULL,
|
|
64
|
+
query_hash TEXT,
|
|
65
|
+
created_at TEXT NOT NULL,
|
|
66
|
+
metadata TEXT
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
ALTER TABLE learning_feedback ADD COLUMN channel TEXT DEFAULT 'unknown';
|
|
70
|
+
|
|
71
|
+
CREATE INDEX IF NOT EXISTS idx_feedback_profile
|
|
72
|
+
ON learning_feedback (profile_id, created_at DESC);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_feedback_channel
|
|
74
|
+
ON learning_feedback (profile_id, channel);
|
|
75
|
+
|
|
76
|
+
COMMIT;
|
|
77
|
+
"""
|
|
@@ -28,6 +28,7 @@ from . import (
|
|
|
28
28
|
M020_model_state_integrity,
|
|
29
29
|
M029_behavioral_history_indexes,
|
|
30
30
|
M030_entity_explorer_indexes,
|
|
31
|
+
M033_learning_feedback_channel,
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
# ---------------------------------------------------------------------------
|
|
@@ -81,6 +82,7 @@ __all__ = (
|
|
|
81
82
|
"M020_model_state_integrity",
|
|
82
83
|
"M029_behavioral_history_indexes",
|
|
83
84
|
"M030_entity_explorer_indexes",
|
|
85
|
+
"M033_learning_feedback_channel",
|
|
84
86
|
# Legacy re-exports (backward compat):
|
|
85
87
|
"CURRENT_SCHEMA_VERSION",
|
|
86
88
|
"get_schema_version",
|