superlocalmemory 3.4.9 → 3.4.11
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/README.md +23 -3
- package/docs/cloud-backup.md +174 -0
- package/docs/skill-evolution.md +256 -0
- package/ide/hooks/tool-event-hook.sh +101 -11
- package/package.json +1 -1
- package/pyproject.toml +3 -2
- package/src/superlocalmemory/cli/commands.py +359 -0
- package/src/superlocalmemory/cli/ingest_cmd.py +81 -29
- package/src/superlocalmemory/cli/main.py +32 -0
- package/src/superlocalmemory/cli/setup_wizard.py +54 -11
- package/src/superlocalmemory/core/config.py +35 -0
- package/src/superlocalmemory/core/consolidation_engine.py +138 -0
- package/src/superlocalmemory/core/embedding_worker.py +1 -1
- package/src/superlocalmemory/core/engine.py +19 -0
- package/src/superlocalmemory/core/fact_consolidator.py +425 -0
- package/src/superlocalmemory/core/graph_pruner.py +290 -0
- package/src/superlocalmemory/core/maintenance_scheduler.py +44 -3
- package/src/superlocalmemory/core/recall_pipeline.py +9 -0
- package/src/superlocalmemory/core/tier_manager.py +325 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +96 -28
- package/src/superlocalmemory/evolution/__init__.py +29 -0
- package/src/superlocalmemory/evolution/blind_verifier.py +115 -0
- package/src/superlocalmemory/evolution/evolution_store.py +302 -0
- package/src/superlocalmemory/evolution/mutation_generator.py +181 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +555 -0
- package/src/superlocalmemory/evolution/triggers.py +367 -0
- package/src/superlocalmemory/evolution/types.py +92 -0
- package/src/superlocalmemory/hooks/hook_handlers.py +13 -0
- package/src/superlocalmemory/infra/backup.py +63 -20
- package/src/superlocalmemory/infra/cloud_backup.py +703 -0
- package/src/superlocalmemory/learning/skill_performance_miner.py +422 -0
- package/src/superlocalmemory/mcp/server.py +4 -0
- package/src/superlocalmemory/mcp/tools_evolution.py +338 -0
- package/src/superlocalmemory/retrieval/engine.py +64 -4
- package/src/superlocalmemory/retrieval/forgetting_filter.py +22 -7
- package/src/superlocalmemory/retrieval/strategy.py +2 -2
- package/src/superlocalmemory/server/routes/backup.py +512 -8
- package/src/superlocalmemory/server/routes/behavioral.py +39 -17
- package/src/superlocalmemory/server/routes/evolution.py +213 -0
- package/src/superlocalmemory/server/routes/tiers.py +195 -0
- package/src/superlocalmemory/server/unified_daemon.py +36 -5
- package/src/superlocalmemory/storage/schema_v3410.py +159 -0
- package/src/superlocalmemory/storage/schema_v3411.py +149 -0
- package/src/superlocalmemory/ui/index.html +59 -3
- package/src/superlocalmemory/ui/js/core.js +3 -0
- package/src/superlocalmemory/ui/js/lifecycle.js +83 -0
- package/src/superlocalmemory/ui/js/ng-entities.js +27 -3
- package/src/superlocalmemory/ui/js/ng-shell.js +33 -0
- package/src/superlocalmemory/ui/js/ng-skills.js +611 -0
- package/src/superlocalmemory/ui/js/settings.js +311 -1
- package/src/superlocalmemory.egg-info/PKG-INFO +16 -1
- package/src/superlocalmemory.egg-info/SOURCES.txt +18 -0
|
@@ -265,7 +265,7 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
265
265
|
print()
|
|
266
266
|
|
|
267
267
|
# -- Step 1: System check --
|
|
268
|
-
print("─── Step 1/
|
|
268
|
+
print("─── Step 1/10: System Check ───")
|
|
269
269
|
print()
|
|
270
270
|
py_ver = platform.python_version()
|
|
271
271
|
py_ok = sys.version_info >= (3, 11)
|
|
@@ -293,7 +293,7 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
293
293
|
|
|
294
294
|
# -- Step 2: Mode selection --
|
|
295
295
|
print()
|
|
296
|
-
print("─── Step 2/
|
|
296
|
+
print("─── Step 2/10: Choose Operating Mode ───")
|
|
297
297
|
print()
|
|
298
298
|
print(" [A] Local Guardian (recommended)")
|
|
299
299
|
print(" Zero cloud. Zero LLM. Full privacy.")
|
|
@@ -342,7 +342,7 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
342
342
|
|
|
343
343
|
# -- Step 3: Code Knowledge Graph --
|
|
344
344
|
print()
|
|
345
|
-
print("─── Step 3/
|
|
345
|
+
print("─── Step 3/10: Code Knowledge Graph ───")
|
|
346
346
|
print()
|
|
347
347
|
print(" CodeGraph builds a structural map of your codebase using Tree-sitter.")
|
|
348
348
|
print(" It gives your AI assistant blast-radius analysis, call graphs,")
|
|
@@ -375,7 +375,7 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
375
375
|
|
|
376
376
|
# -- Step 4: Download models --
|
|
377
377
|
print()
|
|
378
|
-
print("─── Step 4/
|
|
378
|
+
print("─── Step 4/10: Download Embedding Model ───")
|
|
379
379
|
|
|
380
380
|
if not st_ok:
|
|
381
381
|
print(" ⚠ Skipped (sentence-transformers not installed)")
|
|
@@ -386,7 +386,7 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
386
386
|
print(" ⚠ Model will download on first use (may take a few minutes)")
|
|
387
387
|
|
|
388
388
|
print()
|
|
389
|
-
print("─── Step 4b/
|
|
389
|
+
print("─── Step 4b/10: Download Reranker Model ───")
|
|
390
390
|
|
|
391
391
|
if not st_ok:
|
|
392
392
|
print(" ⚠ Skipped (sentence-transformers not installed)")
|
|
@@ -395,7 +395,7 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
395
395
|
|
|
396
396
|
# -- Step 5: Daemon Configuration (v3.4.3) --
|
|
397
397
|
print()
|
|
398
|
-
print("─── Step 5/
|
|
398
|
+
print("─── Step 5/10: Daemon Configuration ───")
|
|
399
399
|
print()
|
|
400
400
|
print(" The SLM daemon runs in the background for instant memory access.")
|
|
401
401
|
print()
|
|
@@ -425,7 +425,7 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
425
425
|
|
|
426
426
|
# -- Step 6: Mesh Communication (v3.4.3) --
|
|
427
427
|
print()
|
|
428
|
-
print("─── Step 6/
|
|
428
|
+
print("─── Step 6/10: Mesh Communication ───")
|
|
429
429
|
print()
|
|
430
430
|
print(" SLM Mesh enables agent-to-agent P2P communication.")
|
|
431
431
|
print(" Multiple AI sessions can share knowledge in real-time.")
|
|
@@ -446,7 +446,7 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
446
446
|
|
|
447
447
|
# -- Step 7: Ingestion Adapters (v3.4.3) --
|
|
448
448
|
print()
|
|
449
|
-
print("─── Step 7/
|
|
449
|
+
print("─── Step 7/10: Ingestion Adapters ───")
|
|
450
450
|
print()
|
|
451
451
|
print(" These let SLM learn from your email, calendar, and meetings.")
|
|
452
452
|
print(" All adapters are OFF by default. You can enable them later.")
|
|
@@ -486,7 +486,7 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
486
486
|
|
|
487
487
|
# -- Step 8: Entity Compilation (v3.4.3) --
|
|
488
488
|
print()
|
|
489
|
-
print("─── Step 8/
|
|
489
|
+
print("─── Step 8/10: Entity Compilation ───")
|
|
490
490
|
print()
|
|
491
491
|
print(" Entity compilation builds knowledge summaries per person,")
|
|
492
492
|
print(" project, and concept. Runs automatically during consolidation.")
|
|
@@ -505,9 +505,50 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
505
505
|
config.save()
|
|
506
506
|
print(f"\n ✓ Entity compilation {'enabled' if config.entity_compilation_enabled else 'disabled'}")
|
|
507
507
|
|
|
508
|
-
# -- Step 9:
|
|
508
|
+
# -- Step 9: Skill Evolution (v3.4.11) --
|
|
509
509
|
print()
|
|
510
|
-
print("─── Step 9/
|
|
510
|
+
print("─── Step 9/10: Skill Evolution ───")
|
|
511
|
+
print()
|
|
512
|
+
print(" SLM can automatically evolve skills that underperform.")
|
|
513
|
+
print(" It detects degradation, generates improvements, and verifies them blindly.")
|
|
514
|
+
print(" Requires an LLM backend (Claude CLI, Ollama, or API key).")
|
|
515
|
+
print()
|
|
516
|
+
print(" [Y] Enable Skill Evolution")
|
|
517
|
+
print(" [N] Disable (can enable later: slm config set evolution.enabled true)")
|
|
518
|
+
print()
|
|
519
|
+
|
|
520
|
+
if interactive:
|
|
521
|
+
evo_choice = _prompt(" Enable Skill Evolution? [Y/n] (default: Y): ", "y").lower()
|
|
522
|
+
else:
|
|
523
|
+
evo_choice = "y"
|
|
524
|
+
print(" Auto-enabling Skill Evolution (non-interactive)")
|
|
525
|
+
|
|
526
|
+
evolution_enabled = evo_choice in ("", "y", "yes")
|
|
527
|
+
|
|
528
|
+
# Write evolution config to config.json directly
|
|
529
|
+
# (SLMConfig.save() doesn't serialize evolution)
|
|
530
|
+
_SLM_HOME.mkdir(parents=True, exist_ok=True)
|
|
531
|
+
evo_config_path = _SLM_HOME / "config.json"
|
|
532
|
+
evo_cfg: dict = {}
|
|
533
|
+
if evo_config_path.exists():
|
|
534
|
+
try:
|
|
535
|
+
evo_cfg = json.loads(evo_config_path.read_text())
|
|
536
|
+
except (json.JSONDecodeError, OSError):
|
|
537
|
+
pass
|
|
538
|
+
evo_cfg["evolution"] = {
|
|
539
|
+
"enabled": evolution_enabled,
|
|
540
|
+
"backend": "auto",
|
|
541
|
+
}
|
|
542
|
+
evo_config_path.write_text(json.dumps(evo_cfg, indent=2) + "\n")
|
|
543
|
+
|
|
544
|
+
if evolution_enabled:
|
|
545
|
+
print(f"\n ✓ Skill Evolution enabled (backend: auto-detect)")
|
|
546
|
+
else:
|
|
547
|
+
print(f"\n ✓ Skill Evolution disabled")
|
|
548
|
+
|
|
549
|
+
# -- Step 10: Verification --
|
|
550
|
+
print()
|
|
551
|
+
print("─── Step 10/10: Verification ───")
|
|
511
552
|
|
|
512
553
|
if st_ok:
|
|
513
554
|
verified = _verify_installation()
|
|
@@ -537,6 +578,8 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
537
578
|
print(", Entity Compilation", end="")
|
|
538
579
|
if code_graph_enabled:
|
|
539
580
|
print(", CodeGraph", end="")
|
|
581
|
+
if evolution_enabled:
|
|
582
|
+
print(", Skill Evolution", end="")
|
|
540
583
|
print()
|
|
541
584
|
if enabled_adapters:
|
|
542
585
|
print(f" Adapters: {', '.join(enabled_adapters)}")
|
|
@@ -490,6 +490,25 @@ class TemporalValidatorConfig:
|
|
|
490
490
|
include_expired_in_history: bool = True
|
|
491
491
|
|
|
492
492
|
|
|
493
|
+
@dataclass(frozen=True)
|
|
494
|
+
class EvolutionConfig:
|
|
495
|
+
"""Configuration for Skill Evolution Engine (v3.4.10).
|
|
496
|
+
|
|
497
|
+
OFF by default — opt in via `slm setup` (interactive) or
|
|
498
|
+
`slm config set evolution.enabled true` (CLI).
|
|
499
|
+
|
|
500
|
+
Backend auto-detection priority:
|
|
501
|
+
1. `claude` CLI available → spawn `claude --model haiku` (ECC pattern, free)
|
|
502
|
+
2. Ollama running → use Ollama (free, local)
|
|
503
|
+
3. API key set → use Anthropic/OpenAI API (paid)
|
|
504
|
+
4. Nothing → dashboard-only (show candidates, manual evolution)
|
|
505
|
+
"""
|
|
506
|
+
|
|
507
|
+
enabled: bool = False # OFF by default, opt-in
|
|
508
|
+
backend: str = "auto" # auto, claude, ollama, anthropic, openai
|
|
509
|
+
max_evolutions_per_cycle: int = 3 # Budget cap per consolidation
|
|
510
|
+
|
|
511
|
+
|
|
493
512
|
@dataclass(frozen=True)
|
|
494
513
|
class AutoInvokeConfig:
|
|
495
514
|
"""Configuration for the Auto-Invoke Engine (Phase 2).
|
|
@@ -580,6 +599,7 @@ class SLMConfig:
|
|
|
580
599
|
parameterization: ParameterizationConfig = field(
|
|
581
600
|
default_factory=ParameterizationConfig,
|
|
582
601
|
)
|
|
602
|
+
evolution: EvolutionConfig = field(default_factory=EvolutionConfig)
|
|
583
603
|
|
|
584
604
|
# v3.4.3: Daemon configuration
|
|
585
605
|
daemon_idle_timeout: int = 0 # 0 = 24/7 (no auto-kill). >0 = seconds before auto-kill.
|
|
@@ -657,6 +677,14 @@ class SLMConfig:
|
|
|
657
677
|
)
|
|
658
678
|
config.mesh_enabled = data.get("mesh_enabled", True)
|
|
659
679
|
|
|
680
|
+
# V3.4.10: Evolution config
|
|
681
|
+
evo = data.get("evolution", {})
|
|
682
|
+
if evo:
|
|
683
|
+
config.evolution = EvolutionConfig(**{
|
|
684
|
+
k: v for k, v in evo.items()
|
|
685
|
+
if k in EvolutionConfig.__dataclass_fields__
|
|
686
|
+
})
|
|
687
|
+
|
|
660
688
|
return config
|
|
661
689
|
|
|
662
690
|
def save(self, config_path: Path | None = None) -> None:
|
|
@@ -696,6 +724,13 @@ class SLMConfig:
|
|
|
696
724
|
},
|
|
697
725
|
}
|
|
698
726
|
|
|
727
|
+
# V3.4.11: Persist evolution config (C-CONFIGSAVE fix)
|
|
728
|
+
data["evolution"] = {
|
|
729
|
+
"enabled": self.evolution.enabled,
|
|
730
|
+
"backend": self.evolution.backend,
|
|
731
|
+
"max_evolutions_per_cycle": self.evolution.max_evolutions_per_cycle,
|
|
732
|
+
}
|
|
733
|
+
|
|
699
734
|
# Preserve existing V3.3 config sections that aren't in for_mode()
|
|
700
735
|
for key in ("forgetting", "quantization", "sagq", "embedding_signature", "auto_invoke"):
|
|
701
736
|
if key in existing:
|
|
@@ -178,6 +178,37 @@ class ConsolidationEngine:
|
|
|
178
178
|
logger.debug("Soft prompt generation (non-fatal): %s", exc)
|
|
179
179
|
results["soft_prompts"] = {"error": str(exc)}
|
|
180
180
|
|
|
181
|
+
# Step 10 (v3.4.10): Mine skill performance from tool events.
|
|
182
|
+
# Creates per-skill assertions and skill correlation patterns.
|
|
183
|
+
try:
|
|
184
|
+
from superlocalmemory.learning.skill_performance_miner import SkillPerformanceMiner
|
|
185
|
+
spm = SkillPerformanceMiner(self._db.db_path)
|
|
186
|
+
results["skill_performance"] = spm.mine(profile_id)
|
|
187
|
+
except Exception as exc:
|
|
188
|
+
logger.debug("Skill performance mining (non-fatal): %s", exc)
|
|
189
|
+
results["skill_performance"] = {"error": str(exc)}
|
|
190
|
+
|
|
191
|
+
# Step 11 (v3.4.10): Skill evolution — 3-trigger system.
|
|
192
|
+
# Runs degradation + health check triggers. Post-session
|
|
193
|
+
# trigger runs separately from the Stop hook.
|
|
194
|
+
# Never on recall/remember hot path. Budget: max 3 per cycle.
|
|
195
|
+
try:
|
|
196
|
+
from superlocalmemory.evolution.skill_evolver import SkillEvolver
|
|
197
|
+
evolver = SkillEvolver(self._db.db_path)
|
|
198
|
+
results["skill_evolution"] = evolver.run_consolidation_cycle(profile_id)
|
|
199
|
+
except Exception as exc:
|
|
200
|
+
logger.debug("Skill evolution (non-fatal): %s", exc)
|
|
201
|
+
results["skill_evolution"] = {"error": str(exc)}
|
|
202
|
+
|
|
203
|
+
# Step 12 (v3.4.11): Generate soft prompts for evolved skills.
|
|
204
|
+
# Queries promoted evolutions and creates/updates custom soft
|
|
205
|
+
# prompts so the AI prefers evolved skill variants.
|
|
206
|
+
try:
|
|
207
|
+
results["evolution_soft_prompts"] = self._step12_evolution_soft_prompts(profile_id)
|
|
208
|
+
except Exception as exc:
|
|
209
|
+
logger.debug("Evolution soft prompts (non-fatal): %s", exc)
|
|
210
|
+
results["evolution_soft_prompts"] = {"error": str(exc)}
|
|
211
|
+
|
|
181
212
|
results["success"] = True
|
|
182
213
|
except Exception as exc:
|
|
183
214
|
logger.warning(
|
|
@@ -588,6 +619,113 @@ class ConsolidationEngine:
|
|
|
588
619
|
logger.warning("CCQ step failed (non-fatal): %s", exc)
|
|
589
620
|
return {"error": str(exc)}
|
|
590
621
|
|
|
622
|
+
# ------------------------------------------------------------------
|
|
623
|
+
# Step 12: Evolution Soft Prompts
|
|
624
|
+
# ------------------------------------------------------------------
|
|
625
|
+
|
|
626
|
+
def _step12_evolution_soft_prompts(self, profile_id: str) -> dict[str, Any]:
|
|
627
|
+
"""Generate soft prompts for promoted evolved skills.
|
|
628
|
+
|
|
629
|
+
Queries skill_evolution_log for promoted evolutions and creates
|
|
630
|
+
or updates soft_prompt_templates with category='custom' so the
|
|
631
|
+
AI agent prefers evolved skill variants over originals.
|
|
632
|
+
|
|
633
|
+
Uses 'custom' category because the soft_prompt_templates CHECK
|
|
634
|
+
constraint does not include 'skill_evolution'. The content is
|
|
635
|
+
prefixed with [SKILL_EVOLUTION] for easy filtering.
|
|
636
|
+
"""
|
|
637
|
+
import sqlite3 as _sqlite3
|
|
638
|
+
|
|
639
|
+
db_path = str(self._db.db_path)
|
|
640
|
+
|
|
641
|
+
conn = _sqlite3.connect(db_path, timeout=10)
|
|
642
|
+
conn.row_factory = _sqlite3.Row
|
|
643
|
+
|
|
644
|
+
# Fetch promoted evolutions
|
|
645
|
+
try:
|
|
646
|
+
promoted_rows = conn.execute(
|
|
647
|
+
"SELECT id, skill_name, parent_skill_id, evolution_type, "
|
|
648
|
+
"mutation_summary, created_at "
|
|
649
|
+
"FROM skill_evolution_log "
|
|
650
|
+
"WHERE status = 'promoted' "
|
|
651
|
+
"ORDER BY created_at DESC LIMIT 20",
|
|
652
|
+
).fetchall()
|
|
653
|
+
except _sqlite3.OperationalError:
|
|
654
|
+
# Table may not exist yet
|
|
655
|
+
conn.close()
|
|
656
|
+
return {"created": 0, "message": "skill_evolution_log table not found"}
|
|
657
|
+
|
|
658
|
+
created_count = 0
|
|
659
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
660
|
+
|
|
661
|
+
for row in promoted_rows:
|
|
662
|
+
r = dict(row)
|
|
663
|
+
skill_name = r["skill_name"]
|
|
664
|
+
parent = r.get("parent_skill_id") or skill_name
|
|
665
|
+
evo_type = r["evolution_type"]
|
|
666
|
+
summary = r.get("mutation_summary", "")
|
|
667
|
+
evo_id = r["id"]
|
|
668
|
+
|
|
669
|
+
# Build prompt content
|
|
670
|
+
content = (
|
|
671
|
+
f"[SKILL_EVOLUTION] Evolved skill: '{skill_name}' "
|
|
672
|
+
f"({'replaces' if evo_type == 'fix' else 'extends'} '{parent}' "
|
|
673
|
+
f"via {evo_type}). {summary}. "
|
|
674
|
+
f"Use the evolved version for better results."
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
# Use a deterministic prompt_id based on the evolution record
|
|
678
|
+
prompt_id = f"evo-{evo_id}"
|
|
679
|
+
|
|
680
|
+
# Check if prompt already exists
|
|
681
|
+
existing = conn.execute(
|
|
682
|
+
"SELECT prompt_id FROM soft_prompt_templates WHERE prompt_id = ?",
|
|
683
|
+
(prompt_id,),
|
|
684
|
+
).fetchone()
|
|
685
|
+
|
|
686
|
+
if existing:
|
|
687
|
+
# M-REPLACE: Update existing record instead of INSERT OR REPLACE
|
|
688
|
+
# to avoid silently dropping columns with defaults
|
|
689
|
+
try:
|
|
690
|
+
conn.execute(
|
|
691
|
+
"UPDATE soft_prompt_templates "
|
|
692
|
+
"SET content = ?, updated_at = ? "
|
|
693
|
+
"WHERE prompt_id = ?",
|
|
694
|
+
(content, now, prompt_id),
|
|
695
|
+
)
|
|
696
|
+
except _sqlite3.OperationalError as upd_exc:
|
|
697
|
+
logger.debug("Failed to update soft prompt %s: %s", prompt_id, upd_exc)
|
|
698
|
+
continue
|
|
699
|
+
|
|
700
|
+
try:
|
|
701
|
+
conn.execute(
|
|
702
|
+
"INSERT OR IGNORE INTO soft_prompt_templates "
|
|
703
|
+
"(prompt_id, profile_id, category, content, source_pattern_ids, "
|
|
704
|
+
" confidence, effectiveness, token_count, retention_score, "
|
|
705
|
+
" active, version, created_at, updated_at) "
|
|
706
|
+
"VALUES (?, ?, 'custom', ?, ?, 0.8, 0.5, ?, 1.0, 1, 1, ?, ?)",
|
|
707
|
+
(prompt_id, profile_id, content,
|
|
708
|
+
json.dumps([evo_id]),
|
|
709
|
+
len(content.split()), # Rough token estimate
|
|
710
|
+
now, now),
|
|
711
|
+
)
|
|
712
|
+
if conn.total_changes:
|
|
713
|
+
created_count += 1
|
|
714
|
+
except _sqlite3.IntegrityError:
|
|
715
|
+
# Unique constraint on (profile_id, category) WHERE active=1
|
|
716
|
+
logger.debug(
|
|
717
|
+
"Skipping soft prompt for %s: unique constraint on active custom",
|
|
718
|
+
skill_name,
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
conn.commit()
|
|
722
|
+
conn.close()
|
|
723
|
+
|
|
724
|
+
return {
|
|
725
|
+
"promoted_skills_found": len(promoted_rows),
|
|
726
|
+
"soft_prompts_created": created_count,
|
|
727
|
+
}
|
|
728
|
+
|
|
591
729
|
# ------------------------------------------------------------------
|
|
592
730
|
# Core Memory Block Storage
|
|
593
731
|
# ------------------------------------------------------------------
|
|
@@ -170,7 +170,7 @@ def _worker_main() -> None:
|
|
|
170
170
|
# a fresh worker on next request (existing mechanism in embeddings.py).
|
|
171
171
|
# V3.3.21: Configurable via SLM_EMBED_WORKER_RSS_LIMIT_MB (default 2500MB).
|
|
172
172
|
import resource
|
|
173
|
-
_rss_limit = int(os.environ.get("SLM_EMBED_WORKER_RSS_LIMIT_MB",
|
|
173
|
+
_rss_limit = int(os.environ.get("SLM_EMBED_WORKER_RSS_LIMIT_MB", 4000))
|
|
174
174
|
rss_mb = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024 / 1024
|
|
175
175
|
if rss_mb > _rss_limit:
|
|
176
176
|
sys.exit(0)
|
|
@@ -139,6 +139,20 @@ class MemoryEngine:
|
|
|
139
139
|
except Exception as exc:
|
|
140
140
|
logger.debug("V3.4.7 schema migration: %s", exc)
|
|
141
141
|
|
|
142
|
+
# V3.4.10: Apply "Fortress" schema (backup_destinations, entity_blacklist)
|
|
143
|
+
try:
|
|
144
|
+
from superlocalmemory.storage.schema_v3410 import apply_v3410_schema
|
|
145
|
+
apply_v3410_schema(str(self._db.db_path))
|
|
146
|
+
except Exception as exc:
|
|
147
|
+
logger.debug("V3.4.10 schema migration: %s", exc)
|
|
148
|
+
|
|
149
|
+
# V3.4.11: Apply "Scale-Ready" schema (pinned_facts, backend_status, fact_consolidations)
|
|
150
|
+
try:
|
|
151
|
+
from superlocalmemory.storage.schema_v3411 import apply_v3411_schema
|
|
152
|
+
apply_v3411_schema(str(self._db.db_path))
|
|
153
|
+
except Exception as exc:
|
|
154
|
+
logger.debug("V3.4.11 schema migration: %s", exc)
|
|
155
|
+
|
|
142
156
|
self._embedder = init_embedder(self._config)
|
|
143
157
|
|
|
144
158
|
if self._caps.llm_fact_extraction:
|
|
@@ -377,6 +391,11 @@ class MemoryEngine:
|
|
|
377
391
|
def close(self) -> None:
|
|
378
392
|
if self._maintenance_scheduler is not None:
|
|
379
393
|
self._maintenance_scheduler.stop()
|
|
394
|
+
if self._db is not None:
|
|
395
|
+
try:
|
|
396
|
+
self._db.close()
|
|
397
|
+
except Exception:
|
|
398
|
+
pass
|
|
380
399
|
self._initialized = False
|
|
381
400
|
|
|
382
401
|
@property
|