superlocalmemory 3.4.43 → 3.4.45
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/package.json +1 -1
- package/pyproject.toml +46 -48
- package/scripts/install.ps1 +36 -10
- package/scripts/install.sh +33 -22
- package/scripts/postinstall.js +31 -77
- package/src/superlocalmemory/__init__.py +27 -1
- package/src/superlocalmemory/core/embedding_worker.py +9 -8
- package/src/superlocalmemory/core/engine_wiring.py +10 -29
- package/src/superlocalmemory/server/unified_daemon.py +36 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "superlocalmemory",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.45",
|
|
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",
|
package/pyproject.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "superlocalmemory"
|
|
3
|
-
version = "3.4.
|
|
3
|
+
version = "3.4.45"
|
|
4
4
|
description = "Information-geometric agent memory with mathematical guarantees"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = {text = "AGPL-3.0-or-later"}
|
|
@@ -29,48 +29,44 @@ classifiers = [
|
|
|
29
29
|
]
|
|
30
30
|
|
|
31
31
|
dependencies = [
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
#
|
|
69
|
-
|
|
70
|
-
# tighten this pin in a future release once optimum-onnx ships a
|
|
71
|
-
# transformers-5.x-compatible build.
|
|
72
|
-
"torch>=2.2.0",
|
|
73
|
-
"scikit-learn>=1.3.0,<2.0.0",
|
|
32
|
+
# All versions hard-pinned to the verified-good combination.
|
|
33
|
+
# Mixing versions outside these pins triggers per-batch memory
|
|
34
|
+
# blow-up in the embedding worker on Apple Silicon and breaks
|
|
35
|
+
# recall/remember latency targets. Update only after benchmarking.
|
|
36
|
+
"httpx==0.28.1",
|
|
37
|
+
"numpy==2.4.4",
|
|
38
|
+
"scipy==1.17.1",
|
|
39
|
+
"networkx==3.6.1",
|
|
40
|
+
"mcp==1.27.1",
|
|
41
|
+
"python-dateutil==2.9.0.post0",
|
|
42
|
+
"rank-bm25==0.2.2",
|
|
43
|
+
"vadersentiment==3.3.2",
|
|
44
|
+
"einops==0.8.2",
|
|
45
|
+
"fastapi[all]==0.136.1",
|
|
46
|
+
"uvicorn==0.46.0",
|
|
47
|
+
"websockets==16.0",
|
|
48
|
+
"lightgbm==4.6.0",
|
|
49
|
+
"orjson==3.11.9",
|
|
50
|
+
"tree-sitter==0.25.2",
|
|
51
|
+
"tree-sitter-language-pack==0.13.0",
|
|
52
|
+
"rustworkx==0.17.1",
|
|
53
|
+
"watchdog==5.0.3",
|
|
54
|
+
"psutil==7.2.2",
|
|
55
|
+
"structlog==25.5.0",
|
|
56
|
+
"portalocker==3.2.0",
|
|
57
|
+
# Semantic search + cross-encoder reranker. Do NOT use
|
|
58
|
+
# sentence-transformers[onnx] — its extras pull optimum which
|
|
59
|
+
# overrides the sentence-transformers pin via transitive deps.
|
|
60
|
+
# Pin all three explicitly instead.
|
|
61
|
+
"sentence-transformers==5.3.0",
|
|
62
|
+
"optimum==2.1.0",
|
|
63
|
+
"onnxruntime==1.24.4",
|
|
64
|
+
"transformers==4.57.6",
|
|
65
|
+
"huggingface_hub==0.36.2",
|
|
66
|
+
"torch==2.11.0",
|
|
67
|
+
"scikit-learn==1.8.0",
|
|
68
|
+
# Vector KNN extension for the semantic channel.
|
|
69
|
+
"sqlite-vec==0.1.9",
|
|
74
70
|
]
|
|
75
71
|
|
|
76
72
|
[project.optional-dependencies]
|
|
@@ -78,12 +74,14 @@ dependencies = [
|
|
|
78
74
|
# moved into core in v3.4.18. ``pip install superlocalmemory[search]`` still
|
|
79
75
|
# works but installs nothing extra.
|
|
80
76
|
search = [
|
|
81
|
-
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
77
|
+
# Same hard pin as core deps — no [onnx] extra.
|
|
78
|
+
"sentence-transformers==5.3.0",
|
|
79
|
+
"optimum==2.1.0",
|
|
80
|
+
"einops==0.8.2",
|
|
81
|
+
"torch==2.11.0",
|
|
82
|
+
"scikit-learn==1.8.0",
|
|
85
83
|
"geoopt>=0.5.0",
|
|
86
|
-
"onnxruntime
|
|
84
|
+
"onnxruntime==1.24.4",
|
|
87
85
|
]
|
|
88
86
|
ui = [
|
|
89
87
|
"fastapi[all]>=0.135.1",
|
package/scripts/install.ps1
CHANGED
|
@@ -233,22 +233,48 @@ print('Database ready')
|
|
|
233
233
|
Write-Host "WARNING: setup_validator.py not found, skipping database init" -ForegroundColor Yellow
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
# Install
|
|
236
|
+
# Install SuperLocalMemory and all dependencies via pyproject.toml (single source of truth)
|
|
237
237
|
Write-Host ""
|
|
238
|
-
Write-Host "Installing
|
|
239
|
-
Write-Host "INFO:
|
|
238
|
+
Write-Host "Installing SuperLocalMemory and all dependencies..."
|
|
239
|
+
Write-Host "INFO: Versions are pinned in pyproject.toml -- same versions for every install path" -ForegroundColor Yellow
|
|
240
|
+
|
|
241
|
+
# Find pyproject.toml (parent of scripts/ or scripts/ itself)
|
|
242
|
+
$ParentDir = Split-Path -Parent $REPO_DIR
|
|
243
|
+
if (Test-Path (Join-Path $ParentDir "pyproject.toml")) {
|
|
244
|
+
$ProjRoot = $ParentDir
|
|
245
|
+
} elseif (Test-Path (Join-Path $REPO_DIR "pyproject.toml")) {
|
|
246
|
+
$ProjRoot = $REPO_DIR
|
|
247
|
+
} else {
|
|
248
|
+
$ProjRoot = $null
|
|
249
|
+
}
|
|
240
250
|
|
|
241
|
-
|
|
242
|
-
if (Test-Path $coreRequirements) {
|
|
251
|
+
if ($ProjRoot) {
|
|
243
252
|
try {
|
|
244
|
-
& python -m pip install -q -
|
|
245
|
-
Write-Host "OK
|
|
253
|
+
& python -m pip install -q -e $ProjRoot 2>$null
|
|
254
|
+
Write-Host "OK SuperLocalMemory and all dependencies installed (pinned versions)" -ForegroundColor Green
|
|
246
255
|
} catch {
|
|
247
|
-
Write-Host "WARNING:
|
|
248
|
-
Write-Host " Install manually: python -m pip install -
|
|
256
|
+
Write-Host "WARNING: Dependency installation failed." -ForegroundColor Yellow
|
|
257
|
+
Write-Host " Install manually: python -m pip install -e $ProjRoot" -ForegroundColor Yellow
|
|
249
258
|
}
|
|
250
259
|
} else {
|
|
251
|
-
Write-Host "WARNING:
|
|
260
|
+
Write-Host "WARNING: pyproject.toml not found, cannot install dependencies" -ForegroundColor Yellow
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
# Verify critical dependency versions (pip resolver can override pins)
|
|
264
|
+
Write-Host ""
|
|
265
|
+
Write-Host "Verifying critical dependency versions..."
|
|
266
|
+
$criticalDeps = @{ "sentence_transformers" = "5.3.0"; "onnxruntime" = "1.24.4" }
|
|
267
|
+
foreach ($mod in $criticalDeps.Keys) {
|
|
268
|
+
$expected = $criticalDeps[$mod]
|
|
269
|
+
$pipName = $mod.Replace("_", "-")
|
|
270
|
+
$actual = & python -c "import $mod; print(getattr($mod,'__version__',''))" 2>$null
|
|
271
|
+
if ($actual -and $actual -ne $expected) {
|
|
272
|
+
Write-Host "WARNING: $pipName is $actual, expected $expected. Fixing..." -ForegroundColor Yellow
|
|
273
|
+
& python -m pip install -q "$pipName==$expected" 2>$null
|
|
274
|
+
Write-Host "OK $pipName==$expected installed" -ForegroundColor Green
|
|
275
|
+
} elseif ($actual -eq $expected) {
|
|
276
|
+
Write-Host "OK $mod==$expected" -ForegroundColor Green
|
|
277
|
+
}
|
|
252
278
|
}
|
|
253
279
|
|
|
254
280
|
# Initialize knowledge graph and pattern learning
|
package/scripts/install.sh
CHANGED
|
@@ -358,8 +358,8 @@ except Exception as e:
|
|
|
358
358
|
|
|
359
359
|
# Install core dependencies (required for graph & dashboard)
|
|
360
360
|
echo ""
|
|
361
|
-
echo "Installing
|
|
362
|
-
echo "⏳
|
|
361
|
+
echo "Installing SuperLocalMemory and all dependencies..."
|
|
362
|
+
echo "⏳ Versions are pinned in pyproject.toml — same versions for every install path"
|
|
363
363
|
|
|
364
364
|
# Detect pip installation method
|
|
365
365
|
if pip3 install --help | grep -q "break-system-packages"; then
|
|
@@ -368,32 +368,43 @@ else
|
|
|
368
368
|
PIP_FLAGS=""
|
|
369
369
|
fi
|
|
370
370
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
fi
|
|
371
|
+
# Find the repo root (parent of scripts/)
|
|
372
|
+
PKG_ROOT="$(cd "${REPO_DIR}/.." && pwd)"
|
|
373
|
+
if [ -f "${PKG_ROOT}/pyproject.toml" ]; then
|
|
374
|
+
PROJ_ROOT="${PKG_ROOT}"
|
|
375
|
+
elif [ -f "${REPO_DIR}/pyproject.toml" ]; then
|
|
376
|
+
PROJ_ROOT="${REPO_DIR}"
|
|
378
377
|
else
|
|
379
|
-
|
|
378
|
+
PROJ_ROOT=""
|
|
380
379
|
fi
|
|
381
380
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
echo "
|
|
385
|
-
echo " Enables intelligent pattern learning and personalized recall"
|
|
386
|
-
|
|
387
|
-
if [ -f "${REPO_DIR}/requirements-learning.txt" ]; then
|
|
388
|
-
if pip3 install $PIP_FLAGS -q -r "${REPO_DIR}/requirements-learning.txt" 2>/dev/null; then
|
|
389
|
-
echo "✓ Learning dependencies installed (personalized ranking enabled)"
|
|
381
|
+
if [ -n "${PROJ_ROOT}" ]; then
|
|
382
|
+
if pip3 install $PIP_FLAGS -q -e "${PROJ_ROOT}"; then
|
|
383
|
+
echo "✓ SuperLocalMemory and all dependencies installed (pinned versions)"
|
|
390
384
|
else
|
|
391
|
-
echo "
|
|
392
|
-
echo "
|
|
385
|
+
echo "⚠️ Dependency installation failed."
|
|
386
|
+
echo " Install manually: pip3 install -e ${PROJ_ROOT}"
|
|
393
387
|
fi
|
|
394
388
|
else
|
|
395
|
-
echo "
|
|
396
|
-
fi
|
|
389
|
+
echo "⚠️ pyproject.toml not found, cannot install dependencies"
|
|
390
|
+
fi
|
|
391
|
+
|
|
392
|
+
# Verify critical dependency versions (pip resolver can override pins)
|
|
393
|
+
echo ""
|
|
394
|
+
echo "Verifying critical dependency versions..."
|
|
395
|
+
for pair in "sentence_transformers:5.3.0:sentence-transformers" "onnxruntime:1.24.4:onnxruntime"; do
|
|
396
|
+
mod=$(echo "$pair" | cut -d: -f1)
|
|
397
|
+
expected=$(echo "$pair" | cut -d: -f2)
|
|
398
|
+
pipname=$(echo "$pair" | cut -d: -f3)
|
|
399
|
+
actual=$(python3 -c "import $mod; print(getattr($mod,'__version__',''))" 2>/dev/null)
|
|
400
|
+
if [ -n "$actual" ] && [ "$actual" != "$expected" ]; then
|
|
401
|
+
echo "⚠️ $pipname is $actual, expected $expected. Fixing..."
|
|
402
|
+
pip3 install $PIP_FLAGS -q "$pipname==$expected" 2>/dev/null
|
|
403
|
+
echo "✓ $pipname==$expected installed"
|
|
404
|
+
elif [ "$actual" = "$expected" ]; then
|
|
405
|
+
echo "✓ $mod==$expected"
|
|
406
|
+
fi
|
|
407
|
+
done
|
|
397
408
|
|
|
398
409
|
# Initialize knowledge graph and pattern learning
|
|
399
410
|
echo ""
|
package/scripts/postinstall.js
CHANGED
|
@@ -97,83 +97,15 @@ function pipInstall(packages, label) {
|
|
|
97
97
|
return false;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
console.log('✓ Core dependencies installed (math, search, NLP)');
|
|
110
|
-
} else {
|
|
111
|
-
console.log('⚠ Core dependency installation failed.');
|
|
112
|
-
console.log(' Run manually: pip install ' + coreDeps.join(' '));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Search + ONNX reranking (V3.3.2 — enables 6-channel retrieval + cross-encoder)
|
|
116
|
-
const searchDeps = [
|
|
117
|
-
'sentence-transformers[onnx]>=4.0.0',
|
|
118
|
-
'einops>=0.7.0', 'geoopt>=0.5.0',
|
|
119
|
-
'onnxruntime>=1.17.0',
|
|
120
|
-
];
|
|
121
|
-
|
|
122
|
-
console.log('\nInstalling semantic search + ONNX reranking engine...');
|
|
123
|
-
console.log(' (sentence-transformers 4+, ONNX Runtime, Fisher-Rao geometry)');
|
|
124
|
-
if (pipInstall(searchDeps, 'search')) {
|
|
125
|
-
console.log('✓ Search engine installed (sentence-transformers + ONNX + Fisher-Rao)');
|
|
126
|
-
console.log(' Cross-encoder reranking enabled for ALL modes (+30pp quality)');
|
|
127
|
-
console.log('');
|
|
128
|
-
console.log(' Models auto-download on first use:');
|
|
129
|
-
console.log(' - Embedding: nomic-ai/nomic-embed-text-v1.5 (~500MB)');
|
|
130
|
-
console.log(' - Reranker: cross-encoder/ms-marco-MiniLM-L-6-v2 (~90MB)');
|
|
131
|
-
console.log(' To pre-download now, run: slm warmup');
|
|
132
|
-
} else {
|
|
133
|
-
console.log('⚠ Search engine installation failed (BM25 keyword search still works).');
|
|
134
|
-
console.log(' For full 6-channel retrieval + reranking, run:');
|
|
135
|
-
console.log(' pip install "sentence-transformers[onnx]>=4.0.0" einops geoopt onnxruntime');
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Dashboard dependencies (IMPORTANT — enables web dashboard + MCP server)
|
|
139
|
-
const dashboardDeps = ['fastapi[all]>=0.135.1', 'uvicorn>=0.42.0', 'websockets>=16.0'];
|
|
140
|
-
console.log('\nInstalling dashboard & server dependencies...');
|
|
141
|
-
if (pipInstall(dashboardDeps, 'dashboard')) {
|
|
142
|
-
console.log('✓ Dashboard & MCP server dependencies installed (fastapi + uvicorn)');
|
|
143
|
-
} else {
|
|
144
|
-
console.log('⚠ Dashboard installation failed.');
|
|
145
|
-
console.log(' Run manually: pip install \'fastapi[all]\' uvicorn websockets');
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Learning dependencies (enables adaptive retrieval after 200+ signals)
|
|
149
|
-
const learningDeps = ['lightgbm>=4.0.0'];
|
|
150
|
-
console.log('\nInstalling learning engine...');
|
|
151
|
-
if (pipInstall(learningDeps, 'learning')) {
|
|
152
|
-
console.log('✓ Learning engine installed (lightgbm — adaptive ranking)');
|
|
153
|
-
} else {
|
|
154
|
-
console.log('⚠ Learning installation failed (retrieval still works without it).');
|
|
155
|
-
console.log(' Run manually: pip install lightgbm');
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Performance dependencies (optional — improves caching and JSON speed)
|
|
159
|
-
const perfDeps = ['diskcache>=5.6.0', 'orjson>=3.9.0'];
|
|
160
|
-
console.log('\nInstalling performance optimizations...');
|
|
161
|
-
if (pipInstall(perfDeps, 'performance')) {
|
|
162
|
-
console.log('✓ Performance optimizations installed (diskcache + orjson)');
|
|
163
|
-
} else {
|
|
164
|
-
console.log('⚠ Performance deps skipped (system works fine without them).');
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// V3.4.3: Unified Brain dependencies (health monitor, structured logging, file watching)
|
|
168
|
-
const brainDeps = ['psutil>=5.9.0', 'structlog>=24.0.0', 'watchdog>=4.0.0'];
|
|
169
|
-
console.log('\nInstalling Unified Brain dependencies (health monitor, file watcher)...');
|
|
170
|
-
if (pipInstall(brainDeps, 'brain')) {
|
|
171
|
-
console.log('✓ Unified Brain deps installed (psutil + structlog + watchdog)');
|
|
172
|
-
console.log(' Health monitoring, structured logging, and file watching enabled');
|
|
173
|
-
} else {
|
|
174
|
-
console.log('⚠ Unified Brain deps partially installed (health monitoring may be limited).');
|
|
175
|
-
console.log(' Run manually: pip install psutil structlog watchdog');
|
|
176
|
-
}
|
|
100
|
+
// Install the superlocalmemory package and all its pinned dependencies
|
|
101
|
+
// in one shot. pyproject.toml is the single source of truth for versions,
|
|
102
|
+
// so users via npm get exactly the same dep set as users via pip.
|
|
103
|
+
console.log('\nInstalling SuperLocalMemory and all dependencies...');
|
|
104
|
+
console.log(' (Single pip install — versions pinned in pyproject.toml)');
|
|
105
|
+
console.log(' This may take 1-3 minutes (downloads ~500MB of models on first use).');
|
|
106
|
+
console.log('');
|
|
107
|
+
console.log(' Includes: numpy, scipy, fastapi, sentence-transformers, onnxruntime,');
|
|
108
|
+
console.log(' torch, transformers, sqlite-vec, lightgbm, mcp, and more.');
|
|
177
109
|
|
|
178
110
|
// --- Step 3b: Install the superlocalmemory package itself ---
|
|
179
111
|
// This ensures `python -m superlocalmemory.cli.main` always resolves the
|
|
@@ -208,6 +140,28 @@ if (pipInstallPkg.status === 0) {
|
|
|
208
140
|
}
|
|
209
141
|
}
|
|
210
142
|
|
|
143
|
+
// --- Step 3c: Verify critical dependency versions ---
|
|
144
|
+
// sentence-transformers and onnxruntime MUST be exact versions to avoid
|
|
145
|
+
// memory blow-up on Apple Silicon. pip resolver can override pins via
|
|
146
|
+
// transitive deps — this step catches and fixes that.
|
|
147
|
+
console.log('\nVerifying critical dependency versions...');
|
|
148
|
+
const criticalDeps = { 'sentence_transformers': '5.3.0', 'onnxruntime': '1.24.4' };
|
|
149
|
+
for (const [mod, expected] of Object.entries(criticalDeps)) {
|
|
150
|
+
const check = spawnSync(pythonParts[0], [
|
|
151
|
+
...pythonParts.slice(1), '-c',
|
|
152
|
+
`import ${mod}; v=getattr(${mod},'__version__',''); print(v)`,
|
|
153
|
+
], { stdio: 'pipe', timeout: 10000, env: { ...process.env, PATH: '/opt/homebrew/bin:/usr/local/bin:/usr/bin:' + (process.env.PATH || '') } });
|
|
154
|
+
const actual = (check.stdout || '').toString().trim();
|
|
155
|
+
if (actual && actual !== expected) {
|
|
156
|
+
const pipName = mod.replace('_', '-');
|
|
157
|
+
console.log(`⚠ ${pipName} is ${actual}, expected ${expected}. Fixing...`);
|
|
158
|
+
pipInstall([`${pipName}==${expected}`], pipName);
|
|
159
|
+
console.log(`✓ ${pipName}==${expected} installed`);
|
|
160
|
+
} else if (actual === expected) {
|
|
161
|
+
console.log(`✓ ${mod}==${expected}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
211
165
|
// --- Step 4: Detect V2 installation ---
|
|
212
166
|
const V2_HOME = path.join(os.homedir(), '.claude-memory');
|
|
213
167
|
if (fs.existsSync(V2_HOME) && fs.existsSync(path.join(V2_HOME, 'memory.db'))) {
|
|
@@ -1,3 +1,29 @@
|
|
|
1
1
|
"""SuperLocalMemory — information-geometric agent memory."""
|
|
2
2
|
|
|
3
|
-
__version__ = "3.4.
|
|
3
|
+
__version__ = "3.4.45"
|
|
4
|
+
|
|
5
|
+
_REQUIRED_VERSIONS = {
|
|
6
|
+
"sentence_transformers": "5.3.0",
|
|
7
|
+
"onnxruntime": "1.24.4",
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _check_critical_deps() -> None:
|
|
12
|
+
"""Warn if embedding-critical packages have wrong versions."""
|
|
13
|
+
import warnings
|
|
14
|
+
for mod_name, expected in _REQUIRED_VERSIONS.items():
|
|
15
|
+
try:
|
|
16
|
+
mod = __import__(mod_name)
|
|
17
|
+
actual = getattr(mod, "__version__", None)
|
|
18
|
+
if actual and actual != expected:
|
|
19
|
+
warnings.warn(
|
|
20
|
+
f"SuperLocalMemory requires {mod_name}=={expected} but "
|
|
21
|
+
f"{actual} is installed. This causes memory blow-up on "
|
|
22
|
+
f"Apple Silicon. Fix: pip install {mod_name}=={expected}",
|
|
23
|
+
stacklevel=2,
|
|
24
|
+
)
|
|
25
|
+
except ImportError:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_check_critical_deps()
|
|
@@ -53,24 +53,25 @@ def _start_parent_watchdog() -> None:
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
def _load_embedding_model(name: str) -> tuple:
|
|
56
|
-
"""Load embedding model. ONNX first
|
|
57
|
-
|
|
58
|
-
V3.3.17: PyTorch SentenceTransformer on ARM64 Mac leaks memory —
|
|
59
|
-
grows from 300MB to 17GB after ~200 encode calls. ONNX Runtime
|
|
60
|
-
has no such issue. Same approach as CrossEncoder ONNX migration.
|
|
56
|
+
"""Load embedding model. ONNX CPU-only first, PyTorch fallback.
|
|
61
57
|
|
|
62
58
|
Returns (model, backend_name) or (None, "").
|
|
63
59
|
"""
|
|
64
60
|
from sentence_transformers import SentenceTransformer
|
|
65
61
|
|
|
66
|
-
#
|
|
62
|
+
# ONNX with explicit CPU provider — avoids CoreML EP memory overhead.
|
|
67
63
|
try:
|
|
68
|
-
m = SentenceTransformer(
|
|
64
|
+
m = SentenceTransformer(
|
|
65
|
+
name,
|
|
66
|
+
backend="onnx",
|
|
67
|
+
trust_remote_code=True,
|
|
68
|
+
model_kwargs={"provider": "CPUExecutionProvider"},
|
|
69
|
+
)
|
|
69
70
|
return m, "onnx"
|
|
70
71
|
except Exception:
|
|
71
72
|
pass
|
|
72
73
|
|
|
73
|
-
#
|
|
74
|
+
# PyTorch CPU fallback.
|
|
74
75
|
try:
|
|
75
76
|
import torch
|
|
76
77
|
with torch.inference_mode():
|
|
@@ -78,38 +78,19 @@ def init_embedder(config: SLMConfig) -> Any | None:
|
|
|
78
78
|
emb_cfg = config.embedding
|
|
79
79
|
provider = emb_cfg.provider
|
|
80
80
|
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
# Why: The store pipeline calls embed() 200+ times per remember
|
|
88
|
-
# (scene_builder, type_router, consolidator, entropy_gate, etc.).
|
|
89
|
-
# Ollama HTTP: 200 * 45ms = 9s minimum + cold starts.
|
|
90
|
-
# sentence-transformers subprocess: 200 embeds batched = ~1s.
|
|
91
|
-
#
|
|
92
|
-
# The embedding model is the SAME (nomic-embed-text-v1.5, 768d) —
|
|
93
|
-
# identical vectors, zero quality difference. Only the transport changes.
|
|
81
|
+
# All modes use sentence-transformers subprocess as primary so the
|
|
82
|
+
# embedding space matches stored vectors. Ollama is fallback only —
|
|
83
|
+
# Ollama's nomic-embed-text and sentence-transformers nomic-embed-text-v1.5
|
|
84
|
+
# produce different vectors, so mixing them against an ST-indexed
|
|
85
|
+
# corpus degrades semantic recall quality.
|
|
94
86
|
if provider == "ollama":
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
logger.info(
|
|
100
|
-
"Mode B hybrid: using sentence-transformers subprocess "
|
|
101
|
-
"for embeddings (fast batched). Ollama used for LLM only."
|
|
102
|
-
)
|
|
103
|
-
return st_emb
|
|
104
|
-
# Fallback: if subprocess unavailable, use Ollama embeddings
|
|
105
|
-
logger.info("Mode B: sentence-transformers unavailable, using Ollama embeddings")
|
|
106
|
-
result = _try_ollama_embedder(emb_cfg)
|
|
107
|
-
if result is not None:
|
|
108
|
-
return result
|
|
109
|
-
return None
|
|
110
|
-
# Mode A/C with explicit ollama: use Ollama embeddings
|
|
87
|
+
st_emb = _try_service_embedder(EmbeddingService, emb_cfg)
|
|
88
|
+
if st_emb is not None:
|
|
89
|
+
logger.info("Using sentence-transformers subprocess (matches stored embedding space)")
|
|
90
|
+
return st_emb
|
|
111
91
|
result = _try_ollama_embedder(emb_cfg)
|
|
112
92
|
if result is not None:
|
|
93
|
+
logger.warning("sentence-transformers unavailable; falling back to Ollama (semantic quality may degrade)")
|
|
113
94
|
return result
|
|
114
95
|
return None
|
|
115
96
|
|
|
@@ -1342,7 +1342,7 @@ def _start_memory_watchdog() -> None:
|
|
|
1342
1342
|
"""
|
|
1343
1343
|
import threading
|
|
1344
1344
|
|
|
1345
|
-
MAX_WORKER_MB =
|
|
1345
|
+
MAX_WORKER_MB = int(os.environ.get("SLM_MAX_WORKER_MB", "2500"))
|
|
1346
1346
|
|
|
1347
1347
|
def watchdog_loop():
|
|
1348
1348
|
while True:
|
|
@@ -1418,12 +1418,23 @@ def _start_pending_materializer() -> None:
|
|
|
1418
1418
|
for item in pending:
|
|
1419
1419
|
if _materializer_stop.is_set():
|
|
1420
1420
|
break
|
|
1421
|
-
# Yield to recalls: wait until none in flight
|
|
1422
1421
|
waits = 0
|
|
1423
1422
|
while _recalls_in_flight() > 0 and waits < 60:
|
|
1424
1423
|
time.sleep(0.5)
|
|
1425
1424
|
waits += 1
|
|
1426
1425
|
try:
|
|
1426
|
+
import hashlib
|
|
1427
|
+
content = item["content"]
|
|
1428
|
+
# Dedup: skip if identical content already stored.
|
|
1429
|
+
content_hash = hashlib.md5(content.encode()).hexdigest()
|
|
1430
|
+
dup = engine._db.execute(
|
|
1431
|
+
"SELECT 1 FROM atomic_facts WHERE "
|
|
1432
|
+
"content = ? LIMIT 1",
|
|
1433
|
+
(content,),
|
|
1434
|
+
)
|
|
1435
|
+
if dup:
|
|
1436
|
+
mark_done(item["id"])
|
|
1437
|
+
continue
|
|
1427
1438
|
import json as _json
|
|
1428
1439
|
md_str = item.get("metadata") or "{}"
|
|
1429
1440
|
try:
|
|
@@ -1432,7 +1443,29 @@ def _start_pending_materializer() -> None:
|
|
|
1432
1443
|
md = {}
|
|
1433
1444
|
if item.get("tags"):
|
|
1434
1445
|
md.setdefault("tags", item["tags"])
|
|
1435
|
-
|
|
1446
|
+
# Create memory row (FK target for atomic_facts)
|
|
1447
|
+
from datetime import datetime, timezone
|
|
1448
|
+
from superlocalmemory.storage.models import (
|
|
1449
|
+
AtomicFact, FactType,
|
|
1450
|
+
)
|
|
1451
|
+
mem_id = content_hash[:16]
|
|
1452
|
+
engine._db.execute(
|
|
1453
|
+
"INSERT OR IGNORE INTO memories "
|
|
1454
|
+
"(memory_id, profile_id, content, "
|
|
1455
|
+
"session_id, speaker, role, created_at, "
|
|
1456
|
+
"metadata_json) VALUES (?,?,?,?,?,?,?,?)",
|
|
1457
|
+
(mem_id, engine._profile_id, content,
|
|
1458
|
+
"", "", "user",
|
|
1459
|
+
datetime.now(timezone.utc).isoformat(),
|
|
1460
|
+
_json.dumps(md)),
|
|
1461
|
+
)
|
|
1462
|
+
fact = AtomicFact(
|
|
1463
|
+
content=content,
|
|
1464
|
+
fact_type=FactType.EPISODIC,
|
|
1465
|
+
memory_id=mem_id,
|
|
1466
|
+
profile_id=engine._profile_id,
|
|
1467
|
+
)
|
|
1468
|
+
engine.store_fact_direct(fact)
|
|
1436
1469
|
mark_done(item["id"])
|
|
1437
1470
|
except Exception as exc:
|
|
1438
1471
|
logger.warning(
|