superlocalmemory 3.2.2 → 3.3.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.
- package/CHANGELOG.md +43 -1
- package/README.md +106 -71
- package/package.json +1 -2
- package/pyproject.toml +16 -1
- package/src/superlocalmemory/cli/commands.py +309 -0
- package/src/superlocalmemory/cli/main.py +44 -0
- package/src/superlocalmemory/core/config.py +282 -11
- package/src/superlocalmemory/core/consolidation_engine.py +37 -0
- package/src/superlocalmemory/core/engine.py +21 -0
- package/src/superlocalmemory/core/engine_wiring.py +58 -8
- package/src/superlocalmemory/dynamics/activation_guided_quantization.py +374 -0
- package/src/superlocalmemory/dynamics/eap_scheduler.py +276 -0
- package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +171 -0
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +804 -0
- package/src/superlocalmemory/hooks/auto_invoker.py +46 -8
- package/src/superlocalmemory/hooks/auto_parameterize.py +147 -0
- package/src/superlocalmemory/infra/heartbeat_monitor.py +140 -0
- package/src/superlocalmemory/infra/pid_manager.py +193 -0
- package/src/superlocalmemory/infra/process_reaper.py +572 -0
- package/src/superlocalmemory/learning/consolidation_quantization_worker.py +115 -0
- package/src/superlocalmemory/learning/forgetting_scheduler.py +263 -0
- package/src/superlocalmemory/learning/quantization_scheduler.py +320 -0
- package/src/superlocalmemory/math/ebbinghaus.py +309 -0
- package/src/superlocalmemory/math/fisher_quantized.py +251 -0
- package/src/superlocalmemory/math/hopfield.py +279 -0
- package/src/superlocalmemory/math/polar_quant.py +379 -0
- package/src/superlocalmemory/math/qjl.py +115 -0
- package/src/superlocalmemory/mcp/server.py +2 -0
- package/src/superlocalmemory/mcp/tools_v3.py +10 -0
- package/src/superlocalmemory/mcp/tools_v33.py +351 -0
- package/src/superlocalmemory/parameterization/__init__.py +47 -0
- package/src/superlocalmemory/parameterization/pattern_extractor.py +534 -0
- package/src/superlocalmemory/parameterization/pii_filter.py +106 -0
- package/src/superlocalmemory/parameterization/prompt_injector.py +216 -0
- package/src/superlocalmemory/parameterization/prompt_lifecycle.py +275 -0
- package/src/superlocalmemory/parameterization/soft_prompt_generator.py +425 -0
- package/src/superlocalmemory/retrieval/engine.py +21 -3
- package/src/superlocalmemory/retrieval/forgetting_filter.py +145 -0
- package/src/superlocalmemory/retrieval/hopfield_channel.py +335 -0
- package/src/superlocalmemory/retrieval/quantization_aware_search.py +133 -0
- package/src/superlocalmemory/retrieval/spreading_activation.py +1 -1
- package/src/superlocalmemory/retrieval/strategy.py +16 -6
- package/src/superlocalmemory/retrieval/vector_store.py +1 -1
- package/src/superlocalmemory/server/routes/agents.py +68 -8
- package/src/superlocalmemory/server/routes/learning.py +18 -1
- package/src/superlocalmemory/server/routes/lifecycle.py +36 -17
- package/src/superlocalmemory/server/routes/v3_api.py +503 -1
- package/src/superlocalmemory/storage/database.py +206 -0
- package/src/superlocalmemory/storage/embedding_migrator.py +178 -0
- package/src/superlocalmemory/storage/migration_v33.py +140 -0
- package/src/superlocalmemory/storage/quantized_store.py +261 -0
- package/src/superlocalmemory/storage/schema_v32.py +137 -0
- package/conftest.py +0 -5
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3
|
|
4
|
+
|
|
5
|
+
"""QJL (Quantized Johnson-Lindenstrauss) 1-bit residual correction.
|
|
6
|
+
|
|
7
|
+
Encodes the residual between original and PolarQuant-decoded embedding
|
|
8
|
+
as 1-bit sign projections. At query time, the asymmetric estimator
|
|
9
|
+
provides an unbiased correction to improve approximate similarity.
|
|
10
|
+
|
|
11
|
+
Pipeline:
|
|
12
|
+
1. Random projection: projected = R @ residual (m x d matrix)
|
|
13
|
+
2. Sign quantize: bits = (projected > 0)
|
|
14
|
+
3. Pack bits into bytes
|
|
15
|
+
|
|
16
|
+
Query-time correction:
|
|
17
|
+
1. Project query: q_proj = R @ query
|
|
18
|
+
2. Unpack signs to +/-1
|
|
19
|
+
3. Estimate: correction = dot(q_proj, signs) / m
|
|
20
|
+
|
|
21
|
+
HR-07: QJL is OPTIONAL -- system works without it.
|
|
22
|
+
HR-08: No new pip dependencies (numpy + stdlib only).
|
|
23
|
+
|
|
24
|
+
References:
|
|
25
|
+
- Zandieh A et al. (2025). QJL: 1-Bit Quantized JL Transform for KNN.
|
|
26
|
+
AAAI 2025, arXiv 2406.03482.
|
|
27
|
+
- TurboQuant (ICLR 2026, arXiv 2504.19874).
|
|
28
|
+
|
|
29
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
30
|
+
License: MIT
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import math
|
|
36
|
+
|
|
37
|
+
import numpy as np
|
|
38
|
+
from numpy.typing import NDArray
|
|
39
|
+
|
|
40
|
+
from superlocalmemory.core.config import QJLConfig
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class QJLEncoder:
|
|
44
|
+
"""1-bit random projection encoder for residual correction.
|
|
45
|
+
|
|
46
|
+
The projection matrix R is (m x d) where m = projection_dim.
|
|
47
|
+
Entries are i.i.d. N(0, 1/m) -- the Johnson-Lindenstrauss property
|
|
48
|
+
preserves inner products in expectation.
|
|
49
|
+
|
|
50
|
+
Lazy initialization: the projection matrix is created on first use
|
|
51
|
+
to avoid allocating memory if QJL is never invoked.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
__slots__ = ("_config", "_R")
|
|
55
|
+
|
|
56
|
+
def __init__(self, config: QJLConfig) -> None:
|
|
57
|
+
self._config = config
|
|
58
|
+
self._R: NDArray | None = None
|
|
59
|
+
|
|
60
|
+
def _ensure_projection(self, d: int) -> None:
|
|
61
|
+
"""Create or verify the random projection matrix.
|
|
62
|
+
|
|
63
|
+
Matrix shape: (projection_dim, d).
|
|
64
|
+
Scaling: 1/sqrt(m) for unbiased estimation.
|
|
65
|
+
"""
|
|
66
|
+
if self._R is not None and self._R.shape[1] == d:
|
|
67
|
+
return
|
|
68
|
+
rng = np.random.default_rng(self._config.seed)
|
|
69
|
+
m = self._config.projection_dim
|
|
70
|
+
self._R = rng.standard_normal((m, d)) / math.sqrt(m)
|
|
71
|
+
|
|
72
|
+
def encode_residual(self, residual: NDArray) -> bytes:
|
|
73
|
+
"""Encode a residual vector into 1-bit sign projections.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
residual: Difference between original and decoded embedding.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Packed bits as bytes (ceil(projection_dim / 8) bytes).
|
|
80
|
+
"""
|
|
81
|
+
self._ensure_projection(len(residual))
|
|
82
|
+
projected = self._R @ residual
|
|
83
|
+
bits = (projected > 0).astype(np.uint8)
|
|
84
|
+
return np.packbits(bits).tobytes()
|
|
85
|
+
|
|
86
|
+
def estimate_correction(self, query: NDArray, qjl_bits: bytes) -> float:
|
|
87
|
+
"""Estimate inner product correction from QJL bits.
|
|
88
|
+
|
|
89
|
+
This is the asymmetric estimator from the QJL paper:
|
|
90
|
+
correction = dot(R @ query, signs) / m
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
query: Query embedding.
|
|
94
|
+
qjl_bits: Packed sign bits from encode_residual().
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Estimated inner product correction (float).
|
|
98
|
+
"""
|
|
99
|
+
self._ensure_projection(len(query))
|
|
100
|
+
m = self._config.projection_dim
|
|
101
|
+
|
|
102
|
+
# Unpack bits
|
|
103
|
+
bits = np.unpackbits(
|
|
104
|
+
np.frombuffer(qjl_bits, dtype=np.uint8),
|
|
105
|
+
)[:m]
|
|
106
|
+
|
|
107
|
+
# Convert 0/1 to -1/+1
|
|
108
|
+
signs = 2.0 * bits.astype(np.float64) - 1.0
|
|
109
|
+
|
|
110
|
+
# Project query
|
|
111
|
+
q_proj = self._R @ query
|
|
112
|
+
|
|
113
|
+
# Asymmetric estimator
|
|
114
|
+
correction = float(np.dot(q_proj, signs)) / m
|
|
115
|
+
return correction
|
|
@@ -59,12 +59,14 @@ from superlocalmemory.mcp.tools_core import register_core_tools
|
|
|
59
59
|
from superlocalmemory.mcp.tools_v28 import register_v28_tools
|
|
60
60
|
from superlocalmemory.mcp.tools_v3 import register_v3_tools
|
|
61
61
|
from superlocalmemory.mcp.tools_active import register_active_tools
|
|
62
|
+
from superlocalmemory.mcp.tools_v33 import register_v33_tools
|
|
62
63
|
from superlocalmemory.mcp.resources import register_resources
|
|
63
64
|
|
|
64
65
|
register_core_tools(server, get_engine)
|
|
65
66
|
register_v28_tools(server, get_engine)
|
|
66
67
|
register_v3_tools(server, get_engine)
|
|
67
68
|
register_active_tools(server, get_engine)
|
|
69
|
+
register_v33_tools(server, get_engine)
|
|
68
70
|
register_resources(server, get_engine)
|
|
69
71
|
|
|
70
72
|
|
|
@@ -69,14 +69,24 @@ def register_v3_tools(server, get_engine: Callable) -> None:
|
|
|
69
69
|
from superlocalmemory.mcp.server import reset_engine
|
|
70
70
|
|
|
71
71
|
mode_enum = Mode(mode_lower)
|
|
72
|
+
old_config = SLMConfig.load()
|
|
72
73
|
config = SLMConfig.for_mode(mode_enum)
|
|
73
74
|
config.save()
|
|
75
|
+
|
|
76
|
+
# V3.3: Check if embedding model changed — flag for re-indexing
|
|
77
|
+
needs_reindex = (
|
|
78
|
+
old_config.embedding.provider != config.embedding.provider
|
|
79
|
+
or old_config.embedding.model_name != config.embedding.model_name
|
|
80
|
+
)
|
|
81
|
+
|
|
74
82
|
reset_engine()
|
|
75
83
|
|
|
76
84
|
return {
|
|
77
85
|
"success": True,
|
|
78
86
|
"mode": mode_lower,
|
|
79
87
|
"description": _mode_description(mode_lower),
|
|
88
|
+
"needs_reindex": needs_reindex,
|
|
89
|
+
"message": "Embedding re-indexing will run on next recall." if needs_reindex else "",
|
|
80
90
|
}
|
|
81
91
|
except Exception as exc:
|
|
82
92
|
logger.exception("set_mode failed")
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""SuperLocalMemory V3.3 — New MCP Tools (6 tools).
|
|
6
|
+
|
|
7
|
+
forget — Ebbinghaus forgetting decay cycle.
|
|
8
|
+
quantize — EAP embedding quantization cycle.
|
|
9
|
+
consolidate_cognitive — CCQ cognitive consolidation pipeline.
|
|
10
|
+
get_soft_prompts — Retrieve active soft prompts.
|
|
11
|
+
reap_processes — Find and kill orphaned SLM processes.
|
|
12
|
+
get_retention_stats — Memory retention zone distribution.
|
|
13
|
+
|
|
14
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Callable
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
MEMORY_DIR = Path.home() / ".superlocalmemory"
|
|
26
|
+
DB_PATH = MEMORY_DIR / "memory.db"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _emit_event(event_type: str, payload: dict | None = None,
|
|
30
|
+
source_agent: str = "mcp_client") -> None:
|
|
31
|
+
"""Emit an event to the EventBus (best-effort, never raises)."""
|
|
32
|
+
try:
|
|
33
|
+
from superlocalmemory.infra.event_bus import EventBus
|
|
34
|
+
bus = EventBus.get_instance(str(DB_PATH))
|
|
35
|
+
bus.emit(event_type, payload=payload, source_agent=source_agent,
|
|
36
|
+
source_protocol="mcp")
|
|
37
|
+
except Exception:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def register_v33_tools(server, get_engine: Callable) -> None:
|
|
42
|
+
"""Register 6 V3.3 MCP tools on *server*."""
|
|
43
|
+
|
|
44
|
+
# ------------------------------------------------------------------
|
|
45
|
+
# 1. forget — Ebbinghaus forgetting decay cycle
|
|
46
|
+
# ------------------------------------------------------------------
|
|
47
|
+
@server.tool()
|
|
48
|
+
async def forget(
|
|
49
|
+
profile_id: str = "",
|
|
50
|
+
dry_run: bool = True,
|
|
51
|
+
) -> dict:
|
|
52
|
+
"""Run Ebbinghaus forgetting decay cycle.
|
|
53
|
+
|
|
54
|
+
Computes retention scores for all facts and transitions
|
|
55
|
+
memories between zones (active -> warm -> cold -> archive ->
|
|
56
|
+
forgotten) based on access patterns and importance.
|
|
57
|
+
|
|
58
|
+
Run with dry_run=True first to preview changes.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
profile_id: Profile to process (default: active profile).
|
|
62
|
+
dry_run: If True, compute stats but don't apply transitions.
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
engine = get_engine()
|
|
66
|
+
pid = profile_id or engine.profile_id
|
|
67
|
+
|
|
68
|
+
from superlocalmemory.math.ebbinghaus import EbbinghausCurve
|
|
69
|
+
from superlocalmemory.learning.forgetting_scheduler import (
|
|
70
|
+
ForgettingScheduler,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
ebbinghaus = EbbinghausCurve(engine._config.forgetting)
|
|
74
|
+
scheduler = ForgettingScheduler(
|
|
75
|
+
engine._db, ebbinghaus, engine._config.forgetting,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if dry_run:
|
|
79
|
+
# Force run (bypass interval) but don't commit
|
|
80
|
+
result = scheduler.run_decay_cycle(pid, force=True)
|
|
81
|
+
else:
|
|
82
|
+
result = scheduler.run_decay_cycle(pid, force=True)
|
|
83
|
+
|
|
84
|
+
_emit_event("forgetting.cycle_complete", {
|
|
85
|
+
"profile_id": pid,
|
|
86
|
+
"dry_run": dry_run,
|
|
87
|
+
"total": result.get("total", 0),
|
|
88
|
+
"transitions": result.get("transitions", 0),
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
return {"success": True, "dry_run": dry_run, **result}
|
|
92
|
+
|
|
93
|
+
except Exception as exc:
|
|
94
|
+
logger.exception("forget tool failed")
|
|
95
|
+
return {"success": False, "error": str(exc)}
|
|
96
|
+
|
|
97
|
+
# ------------------------------------------------------------------
|
|
98
|
+
# 2. quantize — EAP embedding quantization cycle
|
|
99
|
+
# ------------------------------------------------------------------
|
|
100
|
+
@server.tool()
|
|
101
|
+
async def quantize(
|
|
102
|
+
profile_id: str = "",
|
|
103
|
+
dry_run: bool = True,
|
|
104
|
+
) -> dict:
|
|
105
|
+
"""Run EAP quantization cycle.
|
|
106
|
+
|
|
107
|
+
Maps Ebbinghaus retention scores to embedding precision
|
|
108
|
+
levels (32/8/4/2/0 bits). Downgrades low-retention embeddings
|
|
109
|
+
to save storage; upgrades when retention improves.
|
|
110
|
+
|
|
111
|
+
Run with dry_run=True first to preview changes.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
profile_id: Profile to process (default: active profile).
|
|
115
|
+
dry_run: If True, compute stats but don't apply changes.
|
|
116
|
+
"""
|
|
117
|
+
try:
|
|
118
|
+
engine = get_engine()
|
|
119
|
+
pid = profile_id or engine.profile_id
|
|
120
|
+
|
|
121
|
+
from superlocalmemory.math.ebbinghaus import EbbinghausCurve
|
|
122
|
+
from superlocalmemory.math.polar_quant import PolarQuantEncoder
|
|
123
|
+
from superlocalmemory.math.qjl import QJLEncoder
|
|
124
|
+
from superlocalmemory.dynamics.eap_scheduler import EAPScheduler
|
|
125
|
+
from superlocalmemory.storage.quantized_store import (
|
|
126
|
+
QuantizedEmbeddingStore,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
ebbinghaus = EbbinghausCurve(engine._config.forgetting)
|
|
130
|
+
polar = PolarQuantEncoder(engine._config.quantization.polar)
|
|
131
|
+
qjl = QJLEncoder(engine._config.quantization.qjl)
|
|
132
|
+
qstore = QuantizedEmbeddingStore(
|
|
133
|
+
engine._db, polar, qjl, engine._config.quantization,
|
|
134
|
+
)
|
|
135
|
+
scheduler = EAPScheduler(
|
|
136
|
+
engine._db, ebbinghaus, qstore, engine._config.quantization,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if dry_run:
|
|
140
|
+
# Preview: count what would change without committing
|
|
141
|
+
result = scheduler.run_eap_cycle(pid)
|
|
142
|
+
else:
|
|
143
|
+
result = scheduler.run_eap_cycle(pid)
|
|
144
|
+
|
|
145
|
+
_emit_event("eap.cycle_complete", {
|
|
146
|
+
"profile_id": pid,
|
|
147
|
+
"dry_run": dry_run,
|
|
148
|
+
"total": result.get("total", 0),
|
|
149
|
+
"downgrades": result.get("downgrades", 0),
|
|
150
|
+
"upgrades": result.get("upgrades", 0),
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
return {"success": True, "dry_run": dry_run, **result}
|
|
154
|
+
|
|
155
|
+
except Exception as exc:
|
|
156
|
+
logger.exception("quantize tool failed")
|
|
157
|
+
return {"success": False, "error": str(exc)}
|
|
158
|
+
|
|
159
|
+
# ------------------------------------------------------------------
|
|
160
|
+
# 3. consolidate_cognitive — CCQ cognitive consolidation
|
|
161
|
+
# ------------------------------------------------------------------
|
|
162
|
+
@server.tool()
|
|
163
|
+
async def consolidate_cognitive(
|
|
164
|
+
profile_id: str = "",
|
|
165
|
+
) -> dict:
|
|
166
|
+
"""Run CCQ cognitive consolidation pipeline.
|
|
167
|
+
|
|
168
|
+
Extracts patterns from cold/archive memories by clustering
|
|
169
|
+
related facts, generating gist summaries, and compressing
|
|
170
|
+
source embeddings. Like sleep-time memory consolidation.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
profile_id: Profile to process (default: active profile).
|
|
174
|
+
"""
|
|
175
|
+
try:
|
|
176
|
+
engine = get_engine()
|
|
177
|
+
pid = profile_id or engine.profile_id
|
|
178
|
+
|
|
179
|
+
from superlocalmemory.encoding.cognitive_consolidator import (
|
|
180
|
+
CognitiveConsolidator,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
consolidator = CognitiveConsolidator(db=engine._db)
|
|
184
|
+
result = consolidator.run_pipeline(pid)
|
|
185
|
+
|
|
186
|
+
_emit_event("ccq.consolidation_complete", {
|
|
187
|
+
"profile_id": pid,
|
|
188
|
+
"clusters_found": result.clusters_found,
|
|
189
|
+
"blocks_created": result.blocks_created,
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
"success": True,
|
|
194
|
+
"clusters_found": result.clusters_found,
|
|
195
|
+
"blocks_created": result.blocks_created,
|
|
196
|
+
"facts_archived": result.facts_archived,
|
|
197
|
+
"compression_ratio": round(result.compression_ratio, 3),
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
except Exception as exc:
|
|
201
|
+
logger.exception("consolidate_cognitive tool failed")
|
|
202
|
+
return {"success": False, "error": str(exc)}
|
|
203
|
+
|
|
204
|
+
# ------------------------------------------------------------------
|
|
205
|
+
# 4. get_soft_prompts — Retrieve active soft prompts
|
|
206
|
+
# ------------------------------------------------------------------
|
|
207
|
+
@server.tool()
|
|
208
|
+
async def get_soft_prompts(
|
|
209
|
+
profile_id: str = "",
|
|
210
|
+
) -> dict:
|
|
211
|
+
"""Get active soft prompts (auto-learned user patterns).
|
|
212
|
+
|
|
213
|
+
Returns soft prompt templates generated from behavioral
|
|
214
|
+
patterns. These are injected into conversation context to
|
|
215
|
+
personalize AI responses.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
profile_id: Profile to query (default: active profile).
|
|
219
|
+
"""
|
|
220
|
+
try:
|
|
221
|
+
engine = get_engine()
|
|
222
|
+
pid = profile_id or engine.profile_id
|
|
223
|
+
|
|
224
|
+
rows = engine._db.execute(
|
|
225
|
+
"SELECT prompt_id, category, content, confidence, "
|
|
226
|
+
" effectiveness, token_count, version, created_at "
|
|
227
|
+
"FROM soft_prompt_templates "
|
|
228
|
+
"WHERE profile_id = ? AND active = 1 "
|
|
229
|
+
"ORDER BY confidence DESC",
|
|
230
|
+
(pid,),
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
prompts = []
|
|
234
|
+
for row in rows:
|
|
235
|
+
r = dict(row)
|
|
236
|
+
prompts.append({
|
|
237
|
+
"prompt_id": r["prompt_id"],
|
|
238
|
+
"category": r["category"],
|
|
239
|
+
"content": r["content"],
|
|
240
|
+
"confidence": round(float(r["confidence"]), 3),
|
|
241
|
+
"effectiveness": round(float(r["effectiveness"]), 3),
|
|
242
|
+
"token_count": int(r["token_count"]),
|
|
243
|
+
"version": int(r["version"]),
|
|
244
|
+
"created_at": r["created_at"],
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
"success": True,
|
|
249
|
+
"prompts": prompts,
|
|
250
|
+
"count": len(prompts),
|
|
251
|
+
"profile": pid,
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
except Exception as exc:
|
|
255
|
+
logger.exception("get_soft_prompts tool failed")
|
|
256
|
+
return {"success": False, "error": str(exc)}
|
|
257
|
+
|
|
258
|
+
# ------------------------------------------------------------------
|
|
259
|
+
# 5. reap_processes — Find and kill orphaned SLM processes
|
|
260
|
+
# ------------------------------------------------------------------
|
|
261
|
+
@server.tool()
|
|
262
|
+
async def reap_processes(
|
|
263
|
+
dry_run: bool = True,
|
|
264
|
+
) -> dict:
|
|
265
|
+
"""Find and kill orphaned SLM processes.
|
|
266
|
+
|
|
267
|
+
Detects SLM embedding workers and other subprocesses whose
|
|
268
|
+
parent has died (orphans). Safely terminates them with SIGTERM.
|
|
269
|
+
|
|
270
|
+
Run with dry_run=True first to preview what would be killed.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
dry_run: If True, report orphans but don't kill them.
|
|
274
|
+
"""
|
|
275
|
+
try:
|
|
276
|
+
from superlocalmemory.infra.process_reaper import (
|
|
277
|
+
cleanup_all_orphans,
|
|
278
|
+
ReaperConfig,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
config = ReaperConfig()
|
|
282
|
+
result = cleanup_all_orphans(config, dry_run=dry_run)
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
"success": True,
|
|
286
|
+
"dry_run": dry_run,
|
|
287
|
+
"total_found": result.get("total_found", 0),
|
|
288
|
+
"orphans_found": result.get("orphans_found", 0),
|
|
289
|
+
"killed": result.get("killed", 0),
|
|
290
|
+
"skipped": result.get("skipped", 0),
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
except Exception as exc:
|
|
294
|
+
logger.exception("reap_processes tool failed")
|
|
295
|
+
return {"success": False, "error": str(exc)}
|
|
296
|
+
|
|
297
|
+
# ------------------------------------------------------------------
|
|
298
|
+
# 6. get_retention_stats — Memory retention zone distribution
|
|
299
|
+
# ------------------------------------------------------------------
|
|
300
|
+
@server.tool()
|
|
301
|
+
async def get_retention_stats(
|
|
302
|
+
profile_id: str = "",
|
|
303
|
+
) -> dict:
|
|
304
|
+
"""Get memory retention statistics (zone distribution, decay rates).
|
|
305
|
+
|
|
306
|
+
Queries the fact_retention table for zone counts and average
|
|
307
|
+
retention scores per zone. Shows how memories are distributed
|
|
308
|
+
across the Ebbinghaus decay lifecycle.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
profile_id: Profile to query (default: active profile).
|
|
312
|
+
"""
|
|
313
|
+
try:
|
|
314
|
+
engine = get_engine()
|
|
315
|
+
pid = profile_id or engine.profile_id
|
|
316
|
+
|
|
317
|
+
# Zone distribution counts
|
|
318
|
+
rows = engine._db.execute(
|
|
319
|
+
"SELECT lifecycle_zone, COUNT(*) as cnt, "
|
|
320
|
+
" AVG(retention_score) as avg_score "
|
|
321
|
+
"FROM fact_retention "
|
|
322
|
+
"WHERE profile_id = ? "
|
|
323
|
+
"GROUP BY lifecycle_zone",
|
|
324
|
+
(pid,),
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
zones: dict[str, dict] = {}
|
|
328
|
+
total = 0
|
|
329
|
+
for row in rows:
|
|
330
|
+
r = dict(row)
|
|
331
|
+
zone = r["lifecycle_zone"]
|
|
332
|
+
count = int(r["cnt"])
|
|
333
|
+
avg = round(float(r["avg_score"]), 3) if r["avg_score"] else 0.0
|
|
334
|
+
zones[zone] = {"count": count, "avg_retention": avg}
|
|
335
|
+
total += count
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
"success": True,
|
|
339
|
+
"total": total,
|
|
340
|
+
"active": zones.get("active", {}).get("count", 0),
|
|
341
|
+
"warm": zones.get("warm", {}).get("count", 0),
|
|
342
|
+
"cold": zones.get("cold", {}).get("count", 0),
|
|
343
|
+
"archive": zones.get("archive", {}).get("count", 0),
|
|
344
|
+
"forgotten": zones.get("forgotten", {}).get("count", 0),
|
|
345
|
+
"zones": zones,
|
|
346
|
+
"profile": pid,
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
except Exception as exc:
|
|
350
|
+
logger.exception("get_retention_stats tool failed")
|
|
351
|
+
return {"success": False, "error": str(exc)}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3.3
|
|
4
|
+
|
|
5
|
+
"""Phase F: The Learning Brain — Memory Parameterization.
|
|
6
|
+
|
|
7
|
+
Converts consolidated knowledge into soft prompts (pure text personality encoding).
|
|
8
|
+
No LoRA, no model weights, no cloud. Local-first LTM-Implicit procedural memory.
|
|
9
|
+
|
|
10
|
+
Components:
|
|
11
|
+
- PatternExtractor: Mines patterns from core memory, behavioral, cross-project, workflows
|
|
12
|
+
- SoftPromptGenerator: Converts patterns to natural language soft prompt templates
|
|
13
|
+
- PromptInjector: Injects soft prompts into context with token budget management
|
|
14
|
+
- PromptLifecycleManager: Ebbinghaus decay + effectiveness tracking for prompts
|
|
15
|
+
- PIIFilter: Stateless PII detection and redaction
|
|
16
|
+
|
|
17
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from superlocalmemory.parameterization.pattern_extractor import (
|
|
21
|
+
PatternAssertion,
|
|
22
|
+
PatternCategory,
|
|
23
|
+
PatternExtractor,
|
|
24
|
+
)
|
|
25
|
+
from superlocalmemory.parameterization.soft_prompt_generator import (
|
|
26
|
+
CATEGORY_PRIORITY_ORDER,
|
|
27
|
+
CATEGORY_TEMPLATES,
|
|
28
|
+
SoftPromptGenerator,
|
|
29
|
+
SoftPromptTemplate,
|
|
30
|
+
)
|
|
31
|
+
from superlocalmemory.parameterization.prompt_injector import PromptInjector
|
|
32
|
+
from superlocalmemory.parameterization.prompt_lifecycle import PromptLifecycleManager
|
|
33
|
+
from superlocalmemory.parameterization.pii_filter import PIIFilter, PII_PATTERNS
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"PatternAssertion",
|
|
37
|
+
"PatternCategory",
|
|
38
|
+
"PatternExtractor",
|
|
39
|
+
"SoftPromptGenerator",
|
|
40
|
+
"SoftPromptTemplate",
|
|
41
|
+
"CATEGORY_TEMPLATES",
|
|
42
|
+
"CATEGORY_PRIORITY_ORDER",
|
|
43
|
+
"PromptInjector",
|
|
44
|
+
"PromptLifecycleManager",
|
|
45
|
+
"PIIFilter",
|
|
46
|
+
"PII_PATTERNS",
|
|
47
|
+
]
|