superlocalmemory 3.6.22 → 3.7.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 +52 -0
- package/README.md +275 -72
- package/bin/slm-npm +43 -89
- package/docs/pi-dev-integration.md +43 -0
- package/ide/configs/antigravity-mcp.json +2 -2
- package/ide/configs/chatgpt-desktop-mcp.json +1 -1
- package/ide/configs/claude-desktop-mcp.json +2 -2
- package/ide/configs/windsurf-mcp.json +2 -2
- package/ide/hooks/context-hook.js +6 -2
- package/ide/hooks/post-recall-hook.js +7 -3
- package/ide/hooks/tool-event-hook.sh +2 -1
- package/package.json +19 -10
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/_GENERATED.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/pyproject.toml +40 -8
- package/scripts/postinstall-interactive.js +17 -94
- package/scripts/postinstall.js +185 -258
- package/scripts/preuninstall.js +9 -50
- package/src/superlocalmemory/__init__.py +2 -2
- package/src/superlocalmemory/attribution/mathematical_dna.py +1 -1
- package/src/superlocalmemory/attribution/signer.py +34 -19
- package/src/superlocalmemory/attribution/watermark.py +1 -1
- package/src/superlocalmemory/cli/_lazy_init.py +3 -5
- package/src/superlocalmemory/cli/commands.py +490 -195
- package/src/superlocalmemory/cli/context_commands.py +5 -4
- package/src/superlocalmemory/cli/daemon.py +282 -187
- package/src/superlocalmemory/cli/db_migrate.py +3 -1
- package/src/superlocalmemory/cli/diagnostics_cmd.py +28 -0
- package/src/superlocalmemory/cli/evidence_cmd.py +103 -0
- package/src/superlocalmemory/cli/ingest_cmd.py +7 -3
- package/src/superlocalmemory/cli/main.py +128 -31
- package/src/superlocalmemory/cli/pending_store.py +54 -38
- package/src/superlocalmemory/cli/scale_engine_cmd.py +37 -0
- package/src/superlocalmemory/cli/service_installer.py +57 -52
- package/src/superlocalmemory/cli/setup_wizard.py +142 -88
- package/src/superlocalmemory/cli/version_banner.py +2 -1
- package/src/superlocalmemory/code_graph/config.py +3 -1
- package/src/superlocalmemory/core/backend_orchestrator.py +81 -21
- package/src/superlocalmemory/core/config.py +65 -20
- package/src/superlocalmemory/core/consolidation_engine.py +9 -7
- package/src/superlocalmemory/core/context_cache.py +56 -8
- package/src/superlocalmemory/core/derivation_lineage.py +246 -0
- package/src/superlocalmemory/core/embedding_worker.py +32 -20
- package/src/superlocalmemory/core/embeddings.py +54 -18
- package/src/superlocalmemory/core/engine.py +150 -104
- package/src/superlocalmemory/core/engine_ingestion.py +513 -0
- package/src/superlocalmemory/core/engine_wiring.py +2 -0
- package/src/superlocalmemory/core/evidence_bundle.py +526 -0
- package/src/superlocalmemory/core/fact_consolidator.py +5 -11
- package/src/superlocalmemory/core/graph_analyzer.py +2 -2
- package/src/superlocalmemory/core/health_monitor.py +4 -2
- package/src/superlocalmemory/core/ingestion_command.py +636 -0
- package/src/superlocalmemory/core/injection.py +69 -18
- package/src/superlocalmemory/core/lifecycle_state.py +153 -0
- package/src/superlocalmemory/core/maintenance.py +23 -22
- package/src/superlocalmemory/core/maintenance_scheduler.py +51 -35
- package/src/superlocalmemory/core/mutations.py +143 -0
- package/src/superlocalmemory/core/platform_utils.py +7 -4
- package/src/superlocalmemory/core/ram_lock.py +16 -5
- package/src/superlocalmemory/core/rate_limit.py +1 -1
- package/src/superlocalmemory/core/recall_pipeline.py +60 -101
- package/src/superlocalmemory/core/recall_worker.py +76 -59
- package/src/superlocalmemory/core/registry.py +1 -1
- package/src/superlocalmemory/core/scale_engine.py +293 -0
- package/src/superlocalmemory/core/score_contract.py +62 -0
- package/src/superlocalmemory/core/security_primitives.py +3 -1
- package/src/superlocalmemory/core/slm_disabled.py +3 -5
- package/src/superlocalmemory/core/store_pipeline.py +172 -40
- package/src/superlocalmemory/core/tier_manager.py +32 -20
- package/src/superlocalmemory/core/worker_pool.py +13 -4
- package/src/superlocalmemory/dynamics/activation_guided_quantization.py +1 -1
- package/src/superlocalmemory/dynamics/eap_scheduler.py +10 -3
- package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +1 -1
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +1 -1
- package/src/superlocalmemory/encoding/auto_linker.py +1 -1
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +7 -16
- package/src/superlocalmemory/encoding/consolidator.py +22 -5
- package/src/superlocalmemory/encoding/fact_extractor.py +1 -1
- package/src/superlocalmemory/encoding/foresight.py +2 -0
- package/src/superlocalmemory/encoding/graph_builder.py +1 -1
- package/src/superlocalmemory/encoding/temporal_parser.py +2 -0
- package/src/superlocalmemory/evaluation/__init__.py +13 -0
- package/src/superlocalmemory/evaluation/calibration.py +308 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +2 -1
- package/src/superlocalmemory/graph/cozo_backend.py +256 -23
- package/src/superlocalmemory/hooks/_outcome_common.py +21 -11
- package/src/superlocalmemory/hooks/antigravity_adapter.py +10 -31
- package/src/superlocalmemory/hooks/auto_invoker.py +25 -27
- package/src/superlocalmemory/hooks/auto_recall.py +31 -6
- package/src/superlocalmemory/hooks/auto_recall_hook.py +13 -33
- package/src/superlocalmemory/hooks/before_web_hook.py +9 -7
- package/src/superlocalmemory/hooks/claude_code_hooks.py +126 -39
- package/src/superlocalmemory/hooks/codex_assets.py +59 -0
- package/src/superlocalmemory/hooks/codex_hooks.py +186 -0
- package/src/superlocalmemory/hooks/context_payload.py +1 -1
- package/src/superlocalmemory/hooks/copilot_adapter.py +9 -24
- package/src/superlocalmemory/hooks/cursor_adapter.py +10 -32
- package/src/superlocalmemory/hooks/hook_daemon.py +4 -2
- package/src/superlocalmemory/hooks/hook_handlers.py +241 -55
- package/src/superlocalmemory/hooks/memory_protocol.py +5 -3
- package/src/superlocalmemory/hooks/post_tool_async_hook.py +4 -2
- package/src/superlocalmemory/hooks/session_registry.py +15 -8
- package/src/superlocalmemory/hooks/stop_outcome_hook.py +10 -6
- package/src/superlocalmemory/hooks/topic_shift_hook.py +42 -12
- package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -14
- package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +19 -11
- package/src/superlocalmemory/infra/auth_middleware.py +38 -5
- package/src/superlocalmemory/infra/backup.py +7 -5
- package/src/superlocalmemory/infra/cloud_backup.py +18 -8
- package/src/superlocalmemory/infra/daemon_identity.py +248 -0
- package/src/superlocalmemory/infra/data_root.py +199 -0
- package/src/superlocalmemory/infra/event_bus.py +3 -1
- package/src/superlocalmemory/infra/local_diagnostics.py +327 -0
- package/src/superlocalmemory/infra/process_reaper.py +23 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +27 -9
- package/src/superlocalmemory/ingestion/base_adapter.py +25 -31
- package/src/superlocalmemory/ingestion/calendar_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/credentials.py +14 -7
- package/src/superlocalmemory/ingestion/gmail_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/transcript_adapter.py +7 -2
- package/src/superlocalmemory/learning/consolidation_quantization_worker.py +1 -1
- package/src/superlocalmemory/learning/ensemble.py +11 -0
- package/src/superlocalmemory/learning/entity_compiler.py +1 -1
- package/src/superlocalmemory/learning/feedback.py +1 -1
- package/src/superlocalmemory/learning/forgetting_scheduler.py +12 -7
- package/src/superlocalmemory/learning/quantization_scheduler.py +1 -1
- package/src/superlocalmemory/learning/ranker.py +4 -1
- package/src/superlocalmemory/learning/source_quality.py +1 -1
- package/src/superlocalmemory/learning/trigram_index.py +3 -2
- package/src/superlocalmemory/llm/backbone.py +13 -8
- package/src/superlocalmemory/math/ebbinghaus.py +1 -1
- package/src/superlocalmemory/math/fisher.py +1 -1
- package/src/superlocalmemory/math/fisher_quantized.py +1 -1
- package/src/superlocalmemory/math/hopfield.py +1 -1
- package/src/superlocalmemory/math/langevin.py +1 -1
- package/src/superlocalmemory/math/polar_quant.py +3 -4
- package/src/superlocalmemory/math/qjl.py +1 -1
- package/src/superlocalmemory/math/sheaf.py +1 -1
- package/src/superlocalmemory/math/turbo_quant.py +3 -2
- package/src/superlocalmemory/mcp/_daemon_proxy.py +12 -11
- package/src/superlocalmemory/mcp/_pool_adapter.py +27 -0
- package/src/superlocalmemory/mcp/http_transport.py +53 -0
- package/src/superlocalmemory/mcp/server.py +39 -13
- package/src/superlocalmemory/mcp/shared.py +69 -3
- package/src/superlocalmemory/mcp/tools_active.py +141 -31
- package/src/superlocalmemory/mcp/tools_core.py +128 -29
- package/src/superlocalmemory/mcp/tools_evolution.py +5 -7
- package/src/superlocalmemory/mcp/tools_learning.py +42 -2
- package/src/superlocalmemory/mcp/tools_mesh.py +7 -23
- package/src/superlocalmemory/mcp/tools_optimize.py +8 -1
- package/src/superlocalmemory/mcp/tools_v28.py +23 -2
- package/src/superlocalmemory/mcp/tools_v3.py +26 -1
- package/src/superlocalmemory/mcp/tools_v33.py +56 -17
- package/src/superlocalmemory/mesh/broker.py +2 -0
- package/src/superlocalmemory/mesh/remote_sync.py +50 -12
- package/src/superlocalmemory/optimize/cache/manager.py +77 -1
- package/src/superlocalmemory/optimize/cache/semantic.py +23 -3
- package/src/superlocalmemory/optimize/compress/ccr.py +4 -0
- package/src/superlocalmemory/optimize/compress/router.py +6 -1
- package/src/superlocalmemory/optimize/config/__init__.py +5 -0
- package/src/superlocalmemory/optimize/config/store.py +6 -4
- package/src/superlocalmemory/optimize/proxy/_helpers.py +15 -5
- package/src/superlocalmemory/optimize/proxy/capture.py +3 -2
- package/src/superlocalmemory/optimize/proxy/server.py +2 -2
- package/src/superlocalmemory/optimize/storage/db.py +14 -13
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/ann_index.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +35 -11
- package/src/superlocalmemory/retrieval/bridge_discovery.py +73 -8
- package/src/superlocalmemory/retrieval/engine.py +169 -79
- package/src/superlocalmemory/retrieval/entity_channel.py +289 -67
- package/src/superlocalmemory/retrieval/forgetting_filter.py +1 -1
- package/src/superlocalmemory/retrieval/fusion.py +1 -1
- package/src/superlocalmemory/retrieval/hopfield_channel.py +118 -30
- package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
- package/src/superlocalmemory/retrieval/quantization_aware_search.py +16 -10
- package/src/superlocalmemory/retrieval/reranker.py +56 -20
- package/src/superlocalmemory/retrieval/scope_policy.py +85 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +122 -14
- package/src/superlocalmemory/retrieval/spreading_activation.py +141 -25
- package/src/superlocalmemory/retrieval/strategy.py +1 -1
- package/src/superlocalmemory/retrieval/temporal_channel.py +30 -15
- package/src/superlocalmemory/retrieval/vector_store.py +1 -1
- package/src/superlocalmemory/server/api.py +10 -7
- package/src/superlocalmemory/server/bandit_loops.py +4 -2
- package/src/superlocalmemory/server/recall_serializer.py +24 -0
- package/src/superlocalmemory/server/route_mutations.py +84 -0
- package/src/superlocalmemory/server/routes/agents.py +8 -6
- package/src/superlocalmemory/server/routes/brain.py +14 -12
- package/src/superlocalmemory/server/routes/chat.py +29 -12
- package/src/superlocalmemory/server/routes/data_io.py +55 -24
- package/src/superlocalmemory/server/routes/helpers.py +29 -4
- package/src/superlocalmemory/server/routes/ingest.py +53 -36
- package/src/superlocalmemory/server/routes/memories.py +104 -43
- package/src/superlocalmemory/server/routes/mesh.py +31 -0
- package/src/superlocalmemory/server/routes/profiles.py +26 -4
- package/src/superlocalmemory/server/routes/tiers.py +43 -11
- package/src/superlocalmemory/server/routes/timeline.py +5 -1
- package/src/superlocalmemory/server/routes/v3_api.py +76 -21
- package/src/superlocalmemory/server/security_middleware.py +1 -1
- package/src/superlocalmemory/server/ui.py +6 -3
- package/src/superlocalmemory/server/unified_daemon.py +680 -293
- package/src/superlocalmemory/server/write_identity.py +147 -0
- package/src/superlocalmemory/storage/access_log.py +4 -3
- package/src/superlocalmemory/storage/database.py +118 -25
- package/src/superlocalmemory/storage/migration_runner.py +84 -1
- package/src/superlocalmemory/storage/migration_v33.py +1 -1
- package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +6 -60
- package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +120 -0
- package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +54 -0
- package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +52 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +5 -0
- package/src/superlocalmemory/storage/models.py +16 -0
- package/src/superlocalmemory/storage/quantized_store.py +20 -3
- package/src/superlocalmemory/storage/v2_migrator.py +5 -3
- package/src/superlocalmemory/ui/favicon.svg +5 -0
- package/src/superlocalmemory/ui/index.html +1 -0
- package/src/superlocalmemory/ui/js/compliance.js +1 -1
- package/src/superlocalmemory/ui/js/core.js +49 -8
- package/src/superlocalmemory/ui/js/dashboard.js +23 -2
- package/src/superlocalmemory/ui/js/feedback.js +1 -1
- package/src/superlocalmemory/ui/js/graph-filters.js +1 -1
- package/src/superlocalmemory/ui/js/graph-ui.js +1 -1
- package/src/superlocalmemory/ui/js/lifecycle.js +1 -1
- package/src/superlocalmemory/ui/js/ng-mesh.js +15 -49
- package/src/superlocalmemory/ui/js/settings.js +4 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +57 -9
- package/bin/slm +0 -59
- package/bin/slm.bat +0 -77
- package/bin/slm.cmd +0 -5
- package/ide/integrations/langchain/README.md +0 -106
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +0 -9
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +0 -201
- package/ide/integrations/langchain/pyproject.toml +0 -38
- package/ide/integrations/langchain/tests/__init__.py +0 -3
- package/ide/integrations/langchain/tests/test_chat_message_history.py +0 -215
- package/ide/integrations/langchain/tests/test_security.py +0 -117
- package/ide/integrations/llamaindex/README.md +0 -81
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +0 -9
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +0 -316
- package/ide/integrations/llamaindex/pyproject.toml +0 -43
- package/ide/integrations/llamaindex/tests/__init__.py +0 -3
- package/ide/integrations/llamaindex/tests/test_chat_store.py +0 -294
- package/ide/integrations/llamaindex/tests/test_security.py +0 -241
- package/plugin-src/.mcp.json +0 -12
- package/plugin-src/agents/slm-memory-advisor.md +0 -44
- package/plugin-src/agents/slm-optimize-advisor.md +0 -38
- package/plugin-src/hooks/.gitkeep +0 -0
- package/plugin-src/hooks/hooks.json +0 -23
- package/plugin-src/manifest.json +0 -25
- package/plugin-src/requirements.txt +0 -1
- package/plugin-src/rules/CLAUDE.md.fragment +0 -44
- package/plugin-src/scripts/ensure-venv.bat +0 -122
- package/plugin-src/scripts/ensure-venv.sh +0 -105
- package/plugin-src/scripts/slm-launch +0 -15
- package/plugin-src/scripts/slm-launch.bat +0 -17
- package/plugin-src/settings.json +0 -16
- package/plugin-src/skills/slm-cache/SKILL.md +0 -140
- package/plugin-src/skills/slm-compress/SKILL.md +0 -143
- package/plugin-src/skills/slm-graph/SKILL.md +0 -300
- package/plugin-src/skills/slm-recall/SKILL.md +0 -204
- package/plugin-src/skills/slm-remember/SKILL.md +0 -194
- package/plugin-src/skills/slm-session/SKILL.md +0 -207
- package/plugin-src/skills/slm-status/SKILL.md +0 -149
- package/scripts/__tests__/build-plugin.test.mjs +0 -613
- package/scripts/_savings_math.py +0 -270
- package/scripts/build-dmg.sh +0 -417
- package/scripts/build-plugin.js +0 -742
- package/scripts/build-slm-hook.ps1 +0 -40
- package/scripts/build-slm-hook.sh +0 -45
- package/scripts/build_entry.py +0 -452
- package/scripts/ci/stage5b_gate.sh +0 -50
- package/scripts/dogfood_savings.py +0 -490
- package/scripts/generate-thumbnails.py +0 -218
- package/scripts/install-skills.ps1 +0 -4
- package/scripts/install-skills.sh +0 -5
- package/scripts/install.ps1 +0 -701
- package/scripts/install.sh +0 -1015
- package/scripts/postinstall_binary.js +0 -287
- package/scripts/prepack.js +0 -33
- package/scripts/release_manifest.py +0 -273
- package/scripts/slm-hook.spec +0 -56
- package/scripts/start-dashboard.ps1 +0 -52
- package/scripts/start-dashboard.sh +0 -41
- package/scripts/sync-wiki.ps1 +0 -127
- package/scripts/sync-wiki.sh +0 -82
- package/scripts/test-dmg.sh +0 -161
- package/scripts/test-npm-package.ps1 +0 -252
- package/scripts/test-npm-package.sh +0 -207
- package/scripts/verify-install.ps1 +0 -294
- package/scripts/verify-install.sh +0 -266
- package/scripts/verify-v27.ps1 +0 -301
- package/scripts/verify-v27.sh +0 -233
- package/src/superlocalmemory.egg-info/PKG-INFO +0 -513
- package/src/superlocalmemory.egg-info/SOURCES.txt +0 -529
- package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
- package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
- package/src/superlocalmemory.egg-info/requires.txt +0 -71
- package/src/superlocalmemory.egg-info/top_level.txt +0 -1
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
|
|
4
|
+
"""Staged lifecycle for optional CozoDB and LanceDB projections.
|
|
5
|
+
|
|
6
|
+
SQLite and sqlite-vec remain the canonical store. The Scale Engine is a
|
|
7
|
+
derived projection: it is prepared outside the active paths, checked against
|
|
8
|
+
the canonical store, and only then promoted. This prevents an installed
|
|
9
|
+
optional dependency from mutating an existing user's data root at daemon
|
|
10
|
+
startup.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import hashlib
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import shutil
|
|
19
|
+
import sqlite3
|
|
20
|
+
import uuid
|
|
21
|
+
from datetime import UTC, datetime
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any, Callable
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ScaleEngineError(RuntimeError):
|
|
27
|
+
"""A scale projection cannot safely advance to its next lifecycle state."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _utc_now() -> str:
|
|
31
|
+
return datetime.now(UTC).isoformat()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ScaleEngineManager:
|
|
35
|
+
"""Prepare, verify, promote, and roll back derived scale projections.
|
|
36
|
+
|
|
37
|
+
``backend_factory`` is intentionally injectable. It keeps lifecycle tests
|
|
38
|
+
independent from native optional packages while production uses the real
|
|
39
|
+
CozoDB and LanceDB wrappers.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
MANIFEST_NAME = "scale-engine.json"
|
|
43
|
+
SCHEMA_VERSION = 1
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
config: Any,
|
|
48
|
+
*,
|
|
49
|
+
backend_factory: Callable[[Path, Path], tuple[Any, Any]] | None = None,
|
|
50
|
+
) -> None:
|
|
51
|
+
self.config = config
|
|
52
|
+
self.data_dir = Path(getattr(config, "data_dir", None) or config.base_dir)
|
|
53
|
+
self.db_path = Path(getattr(config, "db_path", None) or self.data_dir / "memory.db")
|
|
54
|
+
self.profile_id = getattr(config, "active_profile", "default")
|
|
55
|
+
self._backend_factory = backend_factory or self._real_backend_factory
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def staging_root(self) -> Path:
|
|
59
|
+
return self.data_dir / "scale-staging"
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def backup_root(self) -> Path:
|
|
63
|
+
return self.data_dir / "scale-backups"
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def active_paths(self) -> tuple[Path, Path]:
|
|
67
|
+
return self.data_dir / "cozo", self.data_dir / "lance"
|
|
68
|
+
|
|
69
|
+
def status(self) -> dict[str, Any]:
|
|
70
|
+
manifests: list[dict[str, Any]] = []
|
|
71
|
+
if self.staging_root.exists():
|
|
72
|
+
for path in sorted(self.staging_root.glob(f"*/{self.MANIFEST_NAME}")):
|
|
73
|
+
try:
|
|
74
|
+
manifests.append(json.loads(path.read_text()))
|
|
75
|
+
except (OSError, json.JSONDecodeError):
|
|
76
|
+
manifests.append({"stage_id": path.parent.name, "state": "corrupt"})
|
|
77
|
+
return {
|
|
78
|
+
"state": getattr(self.config, "scale_engine_state", "local_core"),
|
|
79
|
+
"active": {"cozo": self.active_paths[0].exists(), "lance": self.active_paths[1].exists()},
|
|
80
|
+
"stages": manifests,
|
|
81
|
+
"backups": sorted(p.name for p in self.backup_root.glob("*") if p.is_dir()) if self.backup_root.exists() else [],
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
def prepare(self) -> dict[str, Any]:
|
|
85
|
+
"""Build a new projection in a private staging directory."""
|
|
86
|
+
self._require_default_profile()
|
|
87
|
+
self._require_canonical_db()
|
|
88
|
+
stage_id = f"{datetime.now(UTC).strftime('%Y%m%dT%H%M%SZ')}-{uuid.uuid4().hex[:8]}"
|
|
89
|
+
stage_dir = self.staging_root / stage_id
|
|
90
|
+
cozo_dir, lance_dir = stage_dir / "cozo", stage_dir / "lance"
|
|
91
|
+
stage_dir.mkdir(parents=True, exist_ok=False)
|
|
92
|
+
cozo = lance = None
|
|
93
|
+
try:
|
|
94
|
+
cozo, lance = self._backend_factory(cozo_dir, lance_dir)
|
|
95
|
+
with self._readonly_connection() as conn:
|
|
96
|
+
cozo.bulk_import_from_sqlite(conn, self.profile_id)
|
|
97
|
+
lance.bulk_import_from_sqlite(conn, self.profile_id)
|
|
98
|
+
canonical = self._canonical_counts(conn)
|
|
99
|
+
observed = self._observed_counts(cozo, lance)
|
|
100
|
+
manifest = {
|
|
101
|
+
"schema_version": self.SCHEMA_VERSION,
|
|
102
|
+
"stage_id": stage_id,
|
|
103
|
+
"state": "prepared",
|
|
104
|
+
"created_at": _utc_now(),
|
|
105
|
+
"profile_id": self.profile_id,
|
|
106
|
+
"canonical": canonical,
|
|
107
|
+
"observed": observed,
|
|
108
|
+
"source_fingerprint": self._source_fingerprint(canonical),
|
|
109
|
+
}
|
|
110
|
+
self._write_manifest(stage_dir, manifest)
|
|
111
|
+
self.config.scale_engine_state = "prepared"
|
|
112
|
+
self._save_config()
|
|
113
|
+
return manifest
|
|
114
|
+
except Exception:
|
|
115
|
+
shutil.rmtree(stage_dir, ignore_errors=True)
|
|
116
|
+
raise
|
|
117
|
+
finally:
|
|
118
|
+
self._close(cozo)
|
|
119
|
+
self._close(lance)
|
|
120
|
+
|
|
121
|
+
def verify(self, stage_id: str) -> dict[str, Any]:
|
|
122
|
+
"""Prove a staged projection matches the current canonical SQLite data."""
|
|
123
|
+
stage_dir, manifest = self._load_stage(stage_id)
|
|
124
|
+
self._validate_manifest(manifest, state="prepared")
|
|
125
|
+
self._require_default_profile()
|
|
126
|
+
cozo = lance = None
|
|
127
|
+
try:
|
|
128
|
+
cozo, lance = self._backend_factory(stage_dir / "cozo", stage_dir / "lance")
|
|
129
|
+
with self._readonly_connection() as conn:
|
|
130
|
+
canonical = self._canonical_counts(conn)
|
|
131
|
+
observed = self._observed_counts(cozo, lance)
|
|
132
|
+
if manifest["source_fingerprint"] != self._source_fingerprint(canonical):
|
|
133
|
+
raise ScaleEngineError("canonical SQLite changed after preparation; prepare a new stage")
|
|
134
|
+
if canonical != manifest["canonical"] or observed != canonical:
|
|
135
|
+
raise ScaleEngineError(
|
|
136
|
+
f"projection parity failed: canonical={canonical}, observed={observed}"
|
|
137
|
+
)
|
|
138
|
+
manifest.update({"state": "verified", "verified_at": _utc_now(), "observed": observed})
|
|
139
|
+
self._write_manifest(stage_dir, manifest)
|
|
140
|
+
self.config.scale_engine_state = "verified"
|
|
141
|
+
self._save_config()
|
|
142
|
+
return manifest
|
|
143
|
+
finally:
|
|
144
|
+
self._close(cozo)
|
|
145
|
+
self._close(lance)
|
|
146
|
+
|
|
147
|
+
def promote(self, stage_id: str) -> dict[str, Any]:
|
|
148
|
+
"""Move a verified stage into active paths, preserving a rollback copy."""
|
|
149
|
+
stage_dir, manifest = self._load_stage(stage_id)
|
|
150
|
+
self._validate_manifest(manifest, state="verified")
|
|
151
|
+
staged = (stage_dir / "cozo", stage_dir / "lance")
|
|
152
|
+
if not all(path.exists() for path in staged):
|
|
153
|
+
raise ScaleEngineError("verified stage is incomplete; prepare a new stage")
|
|
154
|
+
backup_dir = self.backup_root / f"{stage_id}-{uuid.uuid4().hex[:6]}"
|
|
155
|
+
active = self.active_paths
|
|
156
|
+
self.backup_root.mkdir(parents=True, exist_ok=True)
|
|
157
|
+
# Keep an explicit empty rollback point as well: a first promotion has
|
|
158
|
+
# no former projection directories, but rollback must still be able to
|
|
159
|
+
# return the installation to Local Core without deleting anything.
|
|
160
|
+
backup_dir.mkdir(parents=True, exist_ok=False)
|
|
161
|
+
moved_active: list[tuple[Path, Path]] = []
|
|
162
|
+
moved_stage: list[tuple[Path, Path]] = []
|
|
163
|
+
try:
|
|
164
|
+
for name, source, destination in zip(("cozo", "lance"), active, staged):
|
|
165
|
+
if source.exists():
|
|
166
|
+
target = backup_dir / name
|
|
167
|
+
os.replace(source, target)
|
|
168
|
+
moved_active.append((source, target))
|
|
169
|
+
os.replace(destination, source)
|
|
170
|
+
moved_stage.append((destination, source))
|
|
171
|
+
manifest.update({"state": "promoted", "promoted_at": _utc_now(), "backup_id": backup_dir.name})
|
|
172
|
+
self._write_manifest(stage_dir, manifest)
|
|
173
|
+
self.config.scale_engine_state = "promoted"
|
|
174
|
+
self.config.graph_backend = "cozo"
|
|
175
|
+
self.config.vector_backend = "lancedb"
|
|
176
|
+
self._save_config()
|
|
177
|
+
return manifest
|
|
178
|
+
except Exception as exc:
|
|
179
|
+
for staged_path, active_path in reversed(moved_stage):
|
|
180
|
+
if active_path.exists():
|
|
181
|
+
os.replace(active_path, staged_path)
|
|
182
|
+
for active_path, backup_path in reversed(moved_active):
|
|
183
|
+
if backup_path.exists():
|
|
184
|
+
os.replace(backup_path, active_path)
|
|
185
|
+
raise ScaleEngineError(f"promotion rolled back: {exc}") from exc
|
|
186
|
+
|
|
187
|
+
def rollback(self, backup_id: str) -> dict[str, Any]:
|
|
188
|
+
"""Restore an explicitly named pre-promotion backup."""
|
|
189
|
+
backup_dir = self.backup_root / backup_id
|
|
190
|
+
if not backup_dir.is_dir():
|
|
191
|
+
raise ScaleEngineError(f"backup does not exist: {backup_id}")
|
|
192
|
+
backup_paths = (backup_dir / "cozo", backup_dir / "lance")
|
|
193
|
+
active = self.active_paths
|
|
194
|
+
displaced = self.backup_root / f"rollback-displaced-{uuid.uuid4().hex[:8]}"
|
|
195
|
+
try:
|
|
196
|
+
for name, source, target in zip(("cozo", "lance"), active, backup_paths):
|
|
197
|
+
if source.exists():
|
|
198
|
+
displaced.mkdir(parents=True, exist_ok=True)
|
|
199
|
+
os.replace(source, displaced / name)
|
|
200
|
+
if target.exists():
|
|
201
|
+
os.replace(target, source)
|
|
202
|
+
self.config.scale_engine_state = "local_core"
|
|
203
|
+
self.config.graph_backend = "auto"
|
|
204
|
+
self.config.vector_backend = "auto"
|
|
205
|
+
self._save_config()
|
|
206
|
+
return {"state": "local_core", "restored_backup": backup_id, "displaced": displaced.name}
|
|
207
|
+
except Exception as exc:
|
|
208
|
+
raise ScaleEngineError(f"rollback failed; inspect {backup_dir}: {exc}") from exc
|
|
209
|
+
|
|
210
|
+
def _real_backend_factory(self, cozo_dir: Path, lance_dir: Path) -> tuple[Any, Any]:
|
|
211
|
+
from superlocalmemory.graph.cozo_backend import CozoDBGraphBackend
|
|
212
|
+
from superlocalmemory.vector.lancedb_backend import LanceDBVectorBackend
|
|
213
|
+
return CozoDBGraphBackend(str(cozo_dir / "graph")), LanceDBVectorBackend(str(lance_dir))
|
|
214
|
+
|
|
215
|
+
def _readonly_connection(self) -> sqlite3.Connection:
|
|
216
|
+
conn = sqlite3.connect(f"file:{self.db_path}?mode=ro", uri=True)
|
|
217
|
+
conn.execute("PRAGMA query_only=ON")
|
|
218
|
+
return conn
|
|
219
|
+
|
|
220
|
+
def _canonical_counts(self, conn: sqlite3.Connection) -> dict[str, int]:
|
|
221
|
+
nodes = conn.execute(
|
|
222
|
+
"SELECT COUNT(*) FROM canonical_entities WHERE profile_id=?",
|
|
223
|
+
(self.profile_id,),
|
|
224
|
+
).fetchone()[0]
|
|
225
|
+
edges = conn.execute(
|
|
226
|
+
"SELECT COUNT(*) FROM graph_edges WHERE profile_id=?", (self.profile_id,)
|
|
227
|
+
).fetchone()[0]
|
|
228
|
+
try:
|
|
229
|
+
vectors = conn.execute(
|
|
230
|
+
"SELECT COUNT(*) FROM fact_embeddings_rowids fer "
|
|
231
|
+
"JOIN atomic_facts af ON af.fact_id = fer.fact_id "
|
|
232
|
+
"WHERE af.profile_id = ?",
|
|
233
|
+
(self.profile_id,),
|
|
234
|
+
).fetchone()[0]
|
|
235
|
+
except sqlite3.OperationalError:
|
|
236
|
+
vectors = 0
|
|
237
|
+
return {"entities": int(nodes), "edges": int(edges), "vectors": int(vectors)}
|
|
238
|
+
|
|
239
|
+
def _observed_counts(self, cozo: Any, lance: Any) -> dict[str, int]:
|
|
240
|
+
graph = cozo.health_check()
|
|
241
|
+
vector = lance.health_check()
|
|
242
|
+
if graph.get("status") != "active" or vector.get("status") != "active":
|
|
243
|
+
raise ScaleEngineError(f"projection health failed: cozo={graph}, lancedb={vector}")
|
|
244
|
+
return {"entities": int(graph["entities"]), "edges": int(graph["edges"]), "vectors": int(vector["vectors"])}
|
|
245
|
+
|
|
246
|
+
def _source_fingerprint(self, counts: dict[str, int]) -> str:
|
|
247
|
+
digest = hashlib.sha256()
|
|
248
|
+
digest.update(json.dumps(counts, sort_keys=True).encode())
|
|
249
|
+
for path in (self.db_path, self.db_path.with_name(self.db_path.name + "-wal")):
|
|
250
|
+
if path.exists():
|
|
251
|
+
digest.update(path.name.encode())
|
|
252
|
+
with path.open("rb") as handle:
|
|
253
|
+
for block in iter(lambda: handle.read(1024 * 1024), b""):
|
|
254
|
+
digest.update(block)
|
|
255
|
+
return digest.hexdigest()
|
|
256
|
+
|
|
257
|
+
def _load_stage(self, stage_id: str) -> tuple[Path, dict[str, Any]]:
|
|
258
|
+
stage_dir = self.staging_root / stage_id
|
|
259
|
+
try:
|
|
260
|
+
manifest = json.loads((stage_dir / self.MANIFEST_NAME).read_text())
|
|
261
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
262
|
+
raise ScaleEngineError(f"invalid scale stage: {stage_id}") from exc
|
|
263
|
+
return stage_dir, manifest
|
|
264
|
+
|
|
265
|
+
def _write_manifest(self, stage_dir: Path, manifest: dict[str, Any]) -> None:
|
|
266
|
+
target = stage_dir / self.MANIFEST_NAME
|
|
267
|
+
temporary = target.with_suffix(".tmp")
|
|
268
|
+
temporary.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n")
|
|
269
|
+
os.replace(temporary, target)
|
|
270
|
+
|
|
271
|
+
def _validate_manifest(self, manifest: dict[str, Any], *, state: str) -> None:
|
|
272
|
+
if manifest.get("schema_version") != self.SCHEMA_VERSION or manifest.get("state") != state:
|
|
273
|
+
raise ScaleEngineError(f"stage must be {state}, got {manifest.get('state')!r}")
|
|
274
|
+
|
|
275
|
+
def _require_default_profile(self) -> None:
|
|
276
|
+
if self.profile_id != "default":
|
|
277
|
+
raise ScaleEngineError("Scale Engine promotion currently supports the default profile only")
|
|
278
|
+
|
|
279
|
+
def _require_canonical_db(self) -> None:
|
|
280
|
+
if not self.db_path.exists():
|
|
281
|
+
raise ScaleEngineError(f"canonical SQLite database not found: {self.db_path}")
|
|
282
|
+
|
|
283
|
+
def _save_config(self) -> None:
|
|
284
|
+
save = getattr(self.config, "save", None)
|
|
285
|
+
if callable(save):
|
|
286
|
+
save()
|
|
287
|
+
|
|
288
|
+
@staticmethod
|
|
289
|
+
def _close(resource: Any) -> None:
|
|
290
|
+
if resource is not None:
|
|
291
|
+
close = getattr(resource, "close", None)
|
|
292
|
+
if callable(close):
|
|
293
|
+
close()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
|
|
4
|
+
"""Truthful public retrieval score contract.
|
|
5
|
+
|
|
6
|
+
Retrieval produces an ordering, not an answer probability. This module keeps
|
|
7
|
+
query relevance, stored assertion confidence, trust, and internal ranking
|
|
8
|
+
utility distinct while preserving the V3.6 aliases for one compatibility
|
|
9
|
+
release.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import math
|
|
15
|
+
|
|
16
|
+
from superlocalmemory.storage.models import RecallResponse
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _bounded(value: object, default: float = 0.0) -> float:
|
|
20
|
+
try:
|
|
21
|
+
number = float(value)
|
|
22
|
+
except (TypeError, ValueError):
|
|
23
|
+
number = default
|
|
24
|
+
if not math.isfinite(number):
|
|
25
|
+
number = default
|
|
26
|
+
return min(1.0, max(0.0, number))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def finalize_score_contract(response: RecallResponse) -> RecallResponse:
|
|
30
|
+
"""Finalize aliases, rank positions, and response abstention metadata."""
|
|
31
|
+
for position, result in enumerate(response.results or (), start=1):
|
|
32
|
+
relevance = _bounded(
|
|
33
|
+
result.relevance_score
|
|
34
|
+
if result.relevance_score is not None
|
|
35
|
+
else result.score
|
|
36
|
+
)
|
|
37
|
+
memory_confidence = _bounded(
|
|
38
|
+
result.memory_confidence
|
|
39
|
+
if result.memory_confidence is not None
|
|
40
|
+
else getattr(result.fact, "confidence", 0.0)
|
|
41
|
+
)
|
|
42
|
+
result.relevance_score = relevance
|
|
43
|
+
result.score = relevance
|
|
44
|
+
result.memory_confidence = memory_confidence
|
|
45
|
+
result.confidence = memory_confidence
|
|
46
|
+
result.rank_position = position
|
|
47
|
+
|
|
48
|
+
response.score_contract_version = "2"
|
|
49
|
+
response.calibration_status = "uncalibrated"
|
|
50
|
+
response.calibration_id = None
|
|
51
|
+
response.answer_confidence = None
|
|
52
|
+
response.abstained = not bool(response.results)
|
|
53
|
+
if response.abstained:
|
|
54
|
+
response.abstention_reason = (
|
|
55
|
+
"evidence_floor" if response.no_confident_match else "no_candidates"
|
|
56
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
response.abstention_reason = None
|
|
59
|
+
return response
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
__all__ = ["finalize_score_contract"]
|
|
@@ -401,7 +401,9 @@ def redact_secrets(text: str, *, entropy_threshold: float = 4.5,
|
|
|
401
401
|
|
|
402
402
|
def _install_token_path() -> Path: # pragma: no cover — monkeypatched in tests
|
|
403
403
|
"""Default install-token location — override in tests via monkeypatch."""
|
|
404
|
-
|
|
404
|
+
from superlocalmemory.infra.data_root import state_path
|
|
405
|
+
|
|
406
|
+
return state_path(".install_token")
|
|
405
407
|
|
|
406
408
|
|
|
407
409
|
def ensure_install_token() -> str:
|
|
@@ -30,11 +30,9 @@ _ENV_NAME = "SLM_DISABLE"
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def _slm_home() -> Path:
|
|
33
|
-
"""Return the SLM state directory.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return Path(override)
|
|
37
|
-
return Path.home() / ".superlocalmemory"
|
|
33
|
+
"""Return the canonical SLM runtime-state directory."""
|
|
34
|
+
from superlocalmemory.infra.data_root import canonical_data_root
|
|
35
|
+
return canonical_data_root()
|
|
38
36
|
|
|
39
37
|
|
|
40
38
|
def marker_path() -> Path:
|