superlocalmemory 3.7.8 → 3.8.1
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/.claude-plugin/marketplace.json +1 -1
- package/ATTRIBUTION.md +1 -3
- package/CHANGELOG.md +129 -0
- package/README.md +205 -123
- package/package.json +12 -3
- package/plugin/.claude-plugin/plugin.json +2 -3
- package/plugin/CLAUDE.md +8 -8
- package/plugin/agents/slm-governance-advisor.md +80 -0
- package/plugin/agents/slm-loop-runner.md +71 -0
- package/plugin/agents/slm-memory-advisor.md +10 -5
- package/plugin/agents/slm-optimize-advisor.md +9 -3
- package/plugin/commands/slm-loop.md +31 -0
- package/plugin/hooks/hooks.json +79 -0
- package/plugin/requirements.txt +1 -1
- package/plugin/scripts/slm-launch +46 -7
- package/plugin/settings.json +9 -0
- package/plugin/skills/slm-cache/SKILL.md +9 -1
- package/plugin/skills/slm-compress/SKILL.md +8 -1
- package/plugin/skills/slm-governance/SKILL.md +248 -0
- package/plugin/skills/slm-graph/SKILL.md +17 -3
- package/plugin/skills/slm-loop/SKILL.md +99 -0
- package/plugin/skills/slm-mesh/SKILL.md +282 -0
- package/plugin/skills/slm-profile/SKILL.md +148 -0
- package/plugin/skills/slm-recall/SKILL.md +46 -10
- package/plugin/skills/slm-remember/SKILL.md +48 -1
- package/plugin/skills/slm-scope/SKILL.md +176 -0
- package/plugin/skills/slm-session/SKILL.md +24 -1
- package/plugin/skills/slm-status/SKILL.md +18 -1
- package/plugin-src/rules/AGENTS.md +57 -18
- package/plugin-src/skills/slm-cache/SKILL.md +9 -1
- package/plugin-src/skills/slm-compress/SKILL.md +8 -1
- package/plugin-src/skills/slm-graph/SKILL.md +17 -3
- package/plugin-src/skills/slm-recall/SKILL.md +46 -10
- package/plugin-src/skills/slm-remember/SKILL.md +48 -1
- package/plugin-src/skills/slm-session/SKILL.md +24 -1
- package/plugin-src/skills/slm-status/SKILL.md +18 -1
- package/pyproject.toml +2 -1
- package/scripts/postinstall/validation.js +2 -0
- package/scripts/postinstall-interactive.js +74 -2
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/access/__init__.py +3 -0
- package/src/superlocalmemory/access/rbac.py +477 -0
- package/src/superlocalmemory/cli/commands.py +228 -17
- package/src/superlocalmemory/cli/compress_cmd.py +17 -7
- package/src/superlocalmemory/cli/daemon.py +7 -0
- package/src/superlocalmemory/cli/loop_cmd.py +187 -0
- package/src/superlocalmemory/cli/main.py +49 -8
- package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
- package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
- package/src/superlocalmemory/cli/pending_store.py +49 -13
- package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
- package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
- package/src/superlocalmemory/cli/setup_wizard.py +22 -13
- package/src/superlocalmemory/cli/version_banner.py +17 -3
- package/src/superlocalmemory/compliance/audit.py +6 -0
- package/src/superlocalmemory/compliance/gdpr.py +128 -138
- package/src/superlocalmemory/compliance/retention.py +176 -45
- package/src/superlocalmemory/core/backend_orchestrator.py +23 -59
- package/src/superlocalmemory/core/community_summary.py +267 -0
- package/src/superlocalmemory/core/config.py +216 -3
- package/src/superlocalmemory/core/consolidation_engine.py +95 -22
- package/src/superlocalmemory/core/context_cache.py +61 -18
- package/src/superlocalmemory/core/embedding_worker.py +21 -7
- package/src/superlocalmemory/core/embeddings.py +131 -46
- package/src/superlocalmemory/core/engine.py +41 -22
- package/src/superlocalmemory/core/engine_ingestion.py +359 -43
- package/src/superlocalmemory/core/engine_wiring.py +13 -0
- package/src/superlocalmemory/core/entity_community.py +178 -0
- package/src/superlocalmemory/core/graph_analyzer.py +39 -2
- package/src/superlocalmemory/core/graph_pruner.py +13 -8
- package/src/superlocalmemory/core/ingestion_command.py +134 -25
- package/src/superlocalmemory/core/injection.py +12 -7
- package/src/superlocalmemory/core/key_expander.py +138 -0
- package/src/superlocalmemory/core/maintenance.py +23 -0
- package/src/superlocalmemory/core/maintenance_scheduler.py +17 -7
- package/src/superlocalmemory/core/modes.py +1 -1
- package/src/superlocalmemory/core/mutations.py +2 -2
- package/src/superlocalmemory/core/pii.py +105 -0
- package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
- package/src/superlocalmemory/core/recall_pipeline.py +7 -3
- package/src/superlocalmemory/core/recall_worker.py +20 -6
- package/src/superlocalmemory/core/scale_engine.py +60 -1
- package/src/superlocalmemory/core/security_primitives.py +40 -2
- package/src/superlocalmemory/core/store_pipeline.py +186 -29
- package/src/superlocalmemory/core/worker_pool.py +21 -6
- package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
- package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
- package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
- package/src/superlocalmemory/evolution/evolution_store.py +122 -45
- package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
- package/src/superlocalmemory/evolution/model_selection.py +160 -0
- package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
- package/src/superlocalmemory/evolution/triggers.py +22 -13
- package/src/superlocalmemory/graph/cozo_backend.py +43 -20
- package/src/superlocalmemory/hooks/adapter_base.py +5 -1
- package/src/superlocalmemory/hooks/auto_recall.py +13 -1
- package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
- package/src/superlocalmemory/hooks/codex_assets.py +64 -5
- package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
- package/src/superlocalmemory/hooks/hook_handlers.py +6 -1
- package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
- package/src/superlocalmemory/hooks/portable_kit.py +148 -3
- package/src/superlocalmemory/infra/backup.py +12 -1
- package/src/superlocalmemory/infra/daemon_identity.py +40 -4
- package/src/superlocalmemory/infra/data_root.py +43 -4
- package/src/superlocalmemory/infra/event_bus.py +107 -24
- package/src/superlocalmemory/infra/rate_limiter.py +93 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
- package/src/superlocalmemory/ingestion/credentials.py +1 -1
- package/src/superlocalmemory/learning/cross_project.py +28 -19
- package/src/superlocalmemory/learning/model_rollback.py +3 -0
- package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
- package/src/superlocalmemory/learning/reward.py +50 -0
- package/src/superlocalmemory/learning/reward_proxy.py +42 -9
- package/src/superlocalmemory/learning/source_quality.py +523 -1
- package/src/superlocalmemory/loops/__init__.py +56 -0
- package/src/superlocalmemory/loops/budget.py +58 -0
- package/src/superlocalmemory/loops/engine.py +164 -0
- package/src/superlocalmemory/loops/ledger.py +263 -0
- package/src/superlocalmemory/loops/models.py +152 -0
- package/src/superlocalmemory/loops/rules.py +52 -0
- package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
- package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
- package/src/superlocalmemory/mcp/profiles.py +103 -0
- package/src/superlocalmemory/mcp/server.py +32 -79
- package/src/superlocalmemory/mcp/tools_active.py +4 -7
- package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
- package/src/superlocalmemory/mcp/tools_core.py +12 -4
- package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
- package/src/superlocalmemory/mcp/tools_learning.py +2 -2
- package/src/superlocalmemory/mcp/tools_loops.py +300 -0
- package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
- package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
- package/src/superlocalmemory/mesh/broker.py +237 -129
- package/src/superlocalmemory/mesh/remote_sync.py +50 -8
- package/src/superlocalmemory/optimize/NOTICE +1 -6
- package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
- package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
- package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
- package/src/superlocalmemory/optimize/compress/align.py +32 -26
- package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
- package/src/superlocalmemory/optimize/compress/router.py +105 -22
- package/src/superlocalmemory/optimize/config/defaults.py +1 -1
- package/src/superlocalmemory/optimize/config/schema.py +87 -4
- package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
- package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
- package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
- package/src/superlocalmemory/optimize/storage/db.py +38 -9
- package/src/superlocalmemory/optimize/storage/schema.py +10 -0
- package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
- package/src/superlocalmemory/retrieval/engine.py +221 -47
- package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
- package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
- package/src/superlocalmemory/retrieval/reranker.py +3 -4
- package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
- package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
- package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
- package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
- package/src/superlocalmemory/retrieval/time_window.py +181 -0
- package/src/superlocalmemory/server/api.py +4 -4
- package/src/superlocalmemory/server/config_file.py +90 -0
- package/src/superlocalmemory/server/origin.py +50 -0
- package/src/superlocalmemory/server/profile_runtime.py +125 -8
- package/src/superlocalmemory/server/rbac_enforce.py +142 -0
- package/src/superlocalmemory/server/recall_health.py +24 -3
- package/src/superlocalmemory/server/recall_serializer.py +19 -1
- package/src/superlocalmemory/server/routes/abstraction.py +115 -0
- package/src/superlocalmemory/server/routes/agents.py +128 -38
- package/src/superlocalmemory/server/routes/backup.py +317 -70
- package/src/superlocalmemory/server/routes/behavioral.py +349 -71
- package/src/superlocalmemory/server/routes/brain.py +69 -12
- package/src/superlocalmemory/server/routes/chat.py +10 -5
- package/src/superlocalmemory/server/routes/compliance.py +171 -21
- package/src/superlocalmemory/server/routes/config_api.py +438 -0
- package/src/superlocalmemory/server/routes/data_io.py +30 -8
- package/src/superlocalmemory/server/routes/entity.py +108 -26
- package/src/superlocalmemory/server/routes/events.py +24 -8
- package/src/superlocalmemory/server/routes/evolution.py +189 -68
- package/src/superlocalmemory/server/routes/helpers.py +16 -1
- package/src/superlocalmemory/server/routes/ingest.py +7 -4
- package/src/superlocalmemory/server/routes/insights.py +3 -3
- package/src/superlocalmemory/server/routes/learning.py +289 -118
- package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
- package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
- package/src/superlocalmemory/server/routes/memories.py +182 -57
- package/src/superlocalmemory/server/routes/mesh.py +200 -31
- package/src/superlocalmemory/server/routes/optimize.py +33 -1
- package/src/superlocalmemory/server/routes/prewarm.py +2 -0
- package/src/superlocalmemory/server/routes/profiles.py +63 -17
- package/src/superlocalmemory/server/routes/ratelimit.py +132 -0
- package/src/superlocalmemory/server/routes/rbac.py +367 -0
- package/src/superlocalmemory/server/routes/stats.py +103 -158
- package/src/superlocalmemory/server/routes/tiers.py +11 -9
- package/src/superlocalmemory/server/routes/token.py +3 -13
- package/src/superlocalmemory/server/routes/v3_api.py +247 -89
- package/src/superlocalmemory/server/routes/ws.py +5 -2
- package/src/superlocalmemory/server/security_middleware.py +12 -5
- package/src/superlocalmemory/server/ui.py +20 -5
- package/src/superlocalmemory/server/unified_daemon.py +827 -72
- package/src/superlocalmemory/server/write_identity.py +38 -8
- package/src/superlocalmemory/storage/database.py +265 -53
- package/src/superlocalmemory/storage/migration_runner.py +132 -1
- package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -0
- package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +108 -0
- package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +86 -0
- package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +194 -0
- package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +87 -0
- package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +90 -0
- package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +136 -0
- package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +163 -0
- package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +270 -0
- package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
- package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
- package/src/superlocalmemory/storage/models.py +4 -0
- package/src/superlocalmemory/storage/schema.py +136 -1
- package/src/superlocalmemory/storage/schema_v32.py +2 -0
- package/src/superlocalmemory/storage/schema_v343.py +24 -12
- package/src/superlocalmemory/storage/schema_v347.py +4 -0
- package/src/superlocalmemory/trust/gate.py +49 -8
- package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
- package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
- package/src/superlocalmemory/ui/css/design-system.css +621 -0
- package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
- package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
- package/src/superlocalmemory/ui/favicon.svg +35 -4
- package/src/superlocalmemory/ui/index.html +303 -173
- package/src/superlocalmemory/ui/js/brain.js +5 -20
- package/src/superlocalmemory/ui/js/core.js +100 -41
- package/src/superlocalmemory/ui/js/dashboard.js +403 -65
- package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
- package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
- package/src/superlocalmemory/ui/js/math-health.js +1 -1
- package/src/superlocalmemory/ui/js/memories.js +15 -4
- package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
- package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
- package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
- package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
- package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
- package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
- package/src/superlocalmemory/ui/js/od-agents.js +544 -0
- package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
- package/src/superlocalmemory/ui/js/od-backup.js +871 -0
- package/src/superlocalmemory/ui/js/od-brain.js +816 -0
- package/src/superlocalmemory/ui/js/od-entities.js +579 -0
- package/src/superlocalmemory/ui/js/od-graph.js +600 -0
- package/src/superlocalmemory/ui/js/od-health.js +539 -0
- package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
- package/src/superlocalmemory/ui/js/od-memories.js +929 -0
- package/src/superlocalmemory/ui/js/od-mesh.js +553 -0
- package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
- package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
- package/src/superlocalmemory/ui/js/od-settings.js +1107 -0
- package/src/superlocalmemory/ui/js/od-shell.js +809 -0
- package/src/superlocalmemory/ui/js/od-skills.js +600 -0
- package/src/superlocalmemory/ui/js/od-team.js +258 -0
- package/src/superlocalmemory/ui/js/profiles.js +159 -46
- package/src/superlocalmemory/ui/js/settings.js +17 -3
- package/src/superlocalmemory/ui/js/timeline.js +34 -5
- package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
- 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 -23
- package/plugin-src/scripts/slm-launch.bat +0 -23
- package/plugin-src/settings.json +0 -16
- package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
|
@@ -17,8 +17,6 @@ Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
19
|
import logging
|
|
20
|
-
import sqlite3
|
|
21
|
-
import threading
|
|
22
20
|
from pathlib import Path
|
|
23
21
|
from typing import TYPE_CHECKING, Any
|
|
24
22
|
|
|
@@ -54,7 +52,7 @@ class BackendOrchestrator:
|
|
|
54
52
|
"""Central coordinator for multi-backend architecture.
|
|
55
53
|
|
|
56
54
|
Lifecycle:
|
|
57
|
-
on_daemon_start() →
|
|
55
|
+
on_daemon_start() → initialize bounded backend state → ready
|
|
58
56
|
sync_new_fact() → called from store_pipeline after SQLite write
|
|
59
57
|
health_check() → returns status of all backends
|
|
60
58
|
"""
|
|
@@ -73,7 +71,7 @@ class BackendOrchestrator:
|
|
|
73
71
|
# ------------------------------------------------------------------
|
|
74
72
|
|
|
75
73
|
def on_daemon_start(self) -> None:
|
|
76
|
-
"""
|
|
74
|
+
"""Initialize bounded backend state without delaying daemon readiness."""
|
|
77
75
|
logger.info("BackendOrchestrator: daemon starting")
|
|
78
76
|
|
|
79
77
|
# 1. Apply schema (if not already applied)
|
|
@@ -84,18 +82,13 @@ class BackendOrchestrator:
|
|
|
84
82
|
try:
|
|
85
83
|
from superlocalmemory.core.tier_manager import evaluate_tiers
|
|
86
84
|
self._tiers = evaluate_tiers
|
|
87
|
-
logger.info("BackendOrchestrator:
|
|
85
|
+
logger.info("BackendOrchestrator: tier evaluator registered")
|
|
88
86
|
except Exception as exc:
|
|
89
87
|
logger.warning("TierManager init failed (non-fatal): %s", exc)
|
|
90
88
|
|
|
91
|
-
#
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
result = rebalance(self._db)
|
|
95
|
-
logger.info("BackendOrchestrator: initial rebalance — %s",
|
|
96
|
-
result.get("total_evaluated", "?"))
|
|
97
|
-
except Exception as exc:
|
|
98
|
-
logger.warning("Initial rebalance failed (non-fatal): %s", exc)
|
|
89
|
+
# Full-database tier evaluation belongs to MaintenanceScheduler. Running
|
|
90
|
+
# it here blocks FastAPI lifespan readiness on mature upgrade databases
|
|
91
|
+
# and occurs before optional projection backends are fully registered.
|
|
99
92
|
|
|
100
93
|
self._recover_interrupted_scale_promotion()
|
|
101
94
|
|
|
@@ -109,12 +102,12 @@ class BackendOrchestrator:
|
|
|
109
102
|
)
|
|
110
103
|
return
|
|
111
104
|
|
|
112
|
-
#
|
|
105
|
+
# 3. Initialize CozoDB if available
|
|
113
106
|
cozo_available = self._detect_cozo()
|
|
114
107
|
if cozo_available:
|
|
115
108
|
self._init_cozo()
|
|
116
109
|
|
|
117
|
-
#
|
|
110
|
+
# 4. Initialize LanceDB if available
|
|
118
111
|
lancedb_available = self._detect_lancedb()
|
|
119
112
|
if lancedb_available:
|
|
120
113
|
self._init_lancedb()
|
|
@@ -141,13 +134,19 @@ class BackendOrchestrator:
|
|
|
141
134
|
try:
|
|
142
135
|
from superlocalmemory.core.scale_engine import ScaleEngineManager
|
|
143
136
|
|
|
144
|
-
result = ScaleEngineManager(
|
|
137
|
+
result = ScaleEngineManager(
|
|
138
|
+
self._config,
|
|
139
|
+
profile_id="default",
|
|
140
|
+
).recover_interrupted_promotion()
|
|
145
141
|
if result:
|
|
146
142
|
logger.warning("Scale Engine promotion recovery: %s", result)
|
|
147
143
|
except Exception as exc:
|
|
148
144
|
# A scale projection is derived data. Startup must keep serving
|
|
149
145
|
# canonical SQLite even if optional recovery itself is unhealthy.
|
|
150
|
-
logger.error(
|
|
146
|
+
logger.error(
|
|
147
|
+
"Scale Engine recovery requires repair; Local Core remains active: %s",
|
|
148
|
+
exc,
|
|
149
|
+
)
|
|
151
150
|
|
|
152
151
|
# ------------------------------------------------------------------
|
|
153
152
|
# Incremental Sync (F-04: called from store_pipeline)
|
|
@@ -366,47 +365,11 @@ class BackendOrchestrator:
|
|
|
366
365
|
logger.warning("LanceDB init failed: %s", exc)
|
|
367
366
|
self._lancedb = None
|
|
368
367
|
|
|
369
|
-
#
|
|
370
|
-
#
|
|
371
|
-
#
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
self._update_status("cozo", "migrating")
|
|
375
|
-
|
|
376
|
-
def _run():
|
|
377
|
-
conn = sqlite3.connect(str(self._data_dir / "memory.db"))
|
|
378
|
-
conn.execute("PRAGMA journal_mode=WAL")
|
|
379
|
-
conn.execute("PRAGMA query_only=ON") # F-07: read-only in migration thread
|
|
380
|
-
try:
|
|
381
|
-
count = self._cozo.bulk_import_from_sqlite(conn)
|
|
382
|
-
self._update_status("cozo", "active", count)
|
|
383
|
-
logger.info("CozoDB migration complete: %d edges", count)
|
|
384
|
-
except Exception as exc:
|
|
385
|
-
logger.error("CozoDB migration failed: %s", exc)
|
|
386
|
-
self._update_status("cozo", "failed", error=str(exc))
|
|
387
|
-
finally:
|
|
388
|
-
conn.close()
|
|
389
|
-
|
|
390
|
-
threading.Thread(target=_run, daemon=True).start()
|
|
391
|
-
|
|
392
|
-
def _migrate_lancedb(self) -> None:
|
|
393
|
-
self._update_status("lancedb", "migrating")
|
|
394
|
-
|
|
395
|
-
def _run():
|
|
396
|
-
conn = sqlite3.connect(str(self._data_dir / "memory.db"))
|
|
397
|
-
conn.execute("PRAGMA journal_mode=WAL")
|
|
398
|
-
conn.execute("PRAGMA query_only=ON")
|
|
399
|
-
try:
|
|
400
|
-
count = self._lancedb.bulk_import_from_sqlite(conn)
|
|
401
|
-
self._update_status("lancedb", "active", count)
|
|
402
|
-
logger.info("LanceDB migration complete: %d vectors", count)
|
|
403
|
-
except Exception as exc:
|
|
404
|
-
logger.error("LanceDB migration failed: %s", exc)
|
|
405
|
-
self._update_status("lancedb", "failed", error=str(exc))
|
|
406
|
-
finally:
|
|
407
|
-
conn.close()
|
|
408
|
-
|
|
409
|
-
threading.Thread(target=_run, daemon=True).start()
|
|
368
|
+
# v3.7.9 (scale MEDIUM-2): _migrate_cozo/_migrate_lancedb were dead code —
|
|
369
|
+
# never called anywhere — and bypassed the staged prepare→verify→promote
|
|
370
|
+
# safety envelope (no fingerprint, no parity, no backup). Removed so a future
|
|
371
|
+
# caller cannot re-import outside the lifecycle. Emergency re-imports must go
|
|
372
|
+
# through the Scale Engine lifecycle.
|
|
410
373
|
|
|
411
374
|
# ------------------------------------------------------------------
|
|
412
375
|
# Internal: Status
|
|
@@ -439,7 +402,8 @@ class BackendOrchestrator:
|
|
|
439
402
|
def _apply_schema_v345(self) -> None:
|
|
440
403
|
try:
|
|
441
404
|
from superlocalmemory.storage.schema_v345 import (
|
|
442
|
-
apply_migration,
|
|
405
|
+
apply_migration,
|
|
406
|
+
schema_version_applied,
|
|
443
407
|
)
|
|
444
408
|
# #47 fix: use raw_connection() — DatabaseManager has no `.conn`,
|
|
445
409
|
# so the old code raised AttributeError that was silently swallowed,
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""Community summaries (Wave Q2) — one synthesized report per entity community.
|
|
6
|
+
|
|
7
|
+
Rides on the entity-community backbone (core.entity_community). For each
|
|
8
|
+
community it gathers the member entities' facts, EXCLUDES superseded facts
|
|
9
|
+
(bi-temporal — market CRIT-3), then produces:
|
|
10
|
+
|
|
11
|
+
- a keyword-dense signal (always, Mode A, zero-LLM), and
|
|
12
|
+
- a summary: Mode B/C LLM synthesis via the shared core.Summarizer
|
|
13
|
+
(which itself falls back to a heuristic), else the Mode A keyword-dense
|
|
14
|
+
line. Fail-open — a summarizer error never breaks generation.
|
|
15
|
+
|
|
16
|
+
Surfacing is on-device-safe: summaries are PRECOMPUTED here in the background
|
|
17
|
+
and later matched to a query as a single thematic-context block (Q2b) — never
|
|
18
|
+
a GraphRAG-style per-query LLM fan-out (market CRIT-1). member_fact_ids gives
|
|
19
|
+
drill-down back to the source atoms (Q3).
|
|
20
|
+
|
|
21
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
22
|
+
License: AGPL-3.0-or-later
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
import logging
|
|
29
|
+
from collections import Counter, defaultdict
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
from superlocalmemory.core.entity_community import EntityCommunityBuilder
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
_STOPWORDS = frozenset({
|
|
37
|
+
"the", "a", "an", "is", "was", "were", "are", "be", "been", "being",
|
|
38
|
+
"have", "has", "had", "do", "does", "did", "will", "would", "could",
|
|
39
|
+
"should", "may", "might", "shall", "can", "to", "of", "in", "for", "on",
|
|
40
|
+
"with", "at", "by", "from", "as", "into", "through", "and", "but", "or",
|
|
41
|
+
"not", "no", "this", "that", "these", "those", "it", "its", "they",
|
|
42
|
+
"them", "their", "he", "she", "his", "her", "we", "our", "you", "your",
|
|
43
|
+
"i", "my", "me", "his", "was", "who", "what", "when", "where", "how",
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class CommunitySummaryBuilder:
|
|
48
|
+
"""Generate + persist one summary per entity community (background)."""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
db: Any,
|
|
53
|
+
summarizer: Any = None,
|
|
54
|
+
max_communities: int = 50,
|
|
55
|
+
min_facts: int = 2,
|
|
56
|
+
max_facts_per_community: int = 30,
|
|
57
|
+
max_keywords: int = 8,
|
|
58
|
+
summary_max_chars: int = 512,
|
|
59
|
+
) -> None:
|
|
60
|
+
self._db = db
|
|
61
|
+
self._summarizer = summarizer
|
|
62
|
+
self._max_communities = max(1, int(max_communities))
|
|
63
|
+
self._min_facts = max(1, int(min_facts))
|
|
64
|
+
self._max_facts = max(1, int(max_facts_per_community))
|
|
65
|
+
self._max_keywords = max(1, int(max_keywords))
|
|
66
|
+
self._summary_max_chars = max(64, int(summary_max_chars))
|
|
67
|
+
|
|
68
|
+
# ------------------------------------------------------------------
|
|
69
|
+
# Generation
|
|
70
|
+
# ------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
def compute_and_store(self, profile_id: str) -> dict[str, int]:
|
|
73
|
+
communities = EntityCommunityBuilder(self._db).get_communities(profile_id)
|
|
74
|
+
try:
|
|
75
|
+
self._db.execute(
|
|
76
|
+
"DELETE FROM community_summaries WHERE profile_id = ?",
|
|
77
|
+
(profile_id,),
|
|
78
|
+
)
|
|
79
|
+
except Exception as exc:
|
|
80
|
+
logger.debug("community_summaries clear failed: %s", exc)
|
|
81
|
+
if not communities:
|
|
82
|
+
return {"summaries_written": 0, "communities": 0}
|
|
83
|
+
|
|
84
|
+
entity_to_cid: dict[str, int] = {
|
|
85
|
+
e: cid for cid, ents in communities.items() for e in ents
|
|
86
|
+
}
|
|
87
|
+
cid_facts = self._gather_facts(profile_id, entity_to_cid)
|
|
88
|
+
name_map = self._entity_names(profile_id, communities)
|
|
89
|
+
|
|
90
|
+
written = 0
|
|
91
|
+
ordered = sorted(
|
|
92
|
+
cid_facts.items(), key=lambda kv: len(kv[1]), reverse=True,
|
|
93
|
+
)
|
|
94
|
+
for cid, facts in ordered:
|
|
95
|
+
if written >= self._max_communities:
|
|
96
|
+
break
|
|
97
|
+
seen: set[str] = set()
|
|
98
|
+
vf: list[tuple[str, str]] = []
|
|
99
|
+
for fid, content in facts:
|
|
100
|
+
if fid in seen:
|
|
101
|
+
continue
|
|
102
|
+
seen.add(fid)
|
|
103
|
+
vf.append((fid, content))
|
|
104
|
+
if len(vf) < self._min_facts:
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
fact_ids = [fid for fid, _ in vf]
|
|
108
|
+
contents = [c for _, c in vf][: self._max_facts]
|
|
109
|
+
entity_ids = list(communities.get(cid, []))
|
|
110
|
+
entity_names = [name_map.get(e, e) for e in entity_ids]
|
|
111
|
+
keywords = self._keywords(contents)
|
|
112
|
+
summary = self._summary(contents, entity_names, keywords)
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
self._db.execute(
|
|
116
|
+
"INSERT OR REPLACE INTO community_summaries "
|
|
117
|
+
"(profile_id, community_id, summary, keywords, "
|
|
118
|
+
" entity_ids_json, fact_ids_json, fact_count, computed_at) "
|
|
119
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))",
|
|
120
|
+
(
|
|
121
|
+
profile_id, cid, summary, keywords,
|
|
122
|
+
json.dumps(entity_ids), json.dumps(fact_ids),
|
|
123
|
+
len(fact_ids),
|
|
124
|
+
),
|
|
125
|
+
)
|
|
126
|
+
written += 1
|
|
127
|
+
except Exception as exc:
|
|
128
|
+
logger.debug("community_summaries write failed (%s): %s", cid, exc)
|
|
129
|
+
|
|
130
|
+
return {"summaries_written": written, "communities": len(communities)}
|
|
131
|
+
|
|
132
|
+
# ------------------------------------------------------------------
|
|
133
|
+
# Read API
|
|
134
|
+
# ------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
def get_summaries(self, profile_id: str) -> list[dict]:
|
|
137
|
+
try:
|
|
138
|
+
rows = self._db.execute(
|
|
139
|
+
"SELECT * FROM community_summaries WHERE profile_id = ? "
|
|
140
|
+
"ORDER BY fact_count DESC",
|
|
141
|
+
(profile_id,),
|
|
142
|
+
)
|
|
143
|
+
except Exception as exc:
|
|
144
|
+
logger.debug("get_summaries failed: %s", exc)
|
|
145
|
+
return []
|
|
146
|
+
return [dict(r) for r in rows]
|
|
147
|
+
|
|
148
|
+
def get_summary(self, profile_id: str, community_id: int) -> dict | None:
|
|
149
|
+
try:
|
|
150
|
+
rows = self._db.execute(
|
|
151
|
+
"SELECT * FROM community_summaries "
|
|
152
|
+
"WHERE profile_id = ? AND community_id = ?",
|
|
153
|
+
(profile_id, int(community_id)),
|
|
154
|
+
)
|
|
155
|
+
except Exception as exc:
|
|
156
|
+
logger.debug("get_summary failed: %s", exc)
|
|
157
|
+
return None
|
|
158
|
+
return dict(rows[0]) if rows else None
|
|
159
|
+
|
|
160
|
+
# ------------------------------------------------------------------
|
|
161
|
+
# Internal
|
|
162
|
+
# ------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
def _gather_facts(
|
|
165
|
+
self, profile_id: str, entity_to_cid: dict[str, int],
|
|
166
|
+
) -> dict[int, list[tuple[str, str]]]:
|
|
167
|
+
"""One scan → {community_id -> [(fact_id, content)]}, superseded dropped."""
|
|
168
|
+
try:
|
|
169
|
+
rows = self._db.execute(
|
|
170
|
+
"SELECT fact_id, canonical_entities_json, content "
|
|
171
|
+
"FROM atomic_facts WHERE profile_id = ?",
|
|
172
|
+
(profile_id,),
|
|
173
|
+
)
|
|
174
|
+
except Exception as exc:
|
|
175
|
+
logger.debug("community fact scan failed: %s", exc)
|
|
176
|
+
return {}
|
|
177
|
+
|
|
178
|
+
cid_facts: dict[int, list[tuple[str, str]]] = defaultdict(list)
|
|
179
|
+
for row in rows:
|
|
180
|
+
d = dict(row)
|
|
181
|
+
raw = d.get("canonical_entities_json")
|
|
182
|
+
if not raw:
|
|
183
|
+
continue
|
|
184
|
+
try:
|
|
185
|
+
ents = json.loads(raw)
|
|
186
|
+
except (ValueError, TypeError):
|
|
187
|
+
continue
|
|
188
|
+
if not isinstance(ents, list):
|
|
189
|
+
continue
|
|
190
|
+
cids = {
|
|
191
|
+
entity_to_cid[str(e).strip()]
|
|
192
|
+
for e in ents
|
|
193
|
+
if str(e).strip() in entity_to_cid
|
|
194
|
+
}
|
|
195
|
+
if not cids:
|
|
196
|
+
continue
|
|
197
|
+
fid = str(d["fact_id"])
|
|
198
|
+
content = d.get("content") or ""
|
|
199
|
+
for cid in cids:
|
|
200
|
+
cid_facts[cid].append((fid, content))
|
|
201
|
+
|
|
202
|
+
all_fids = list({fid for lst in cid_facts.values() for fid, _ in lst})
|
|
203
|
+
invalid: set[str] = set()
|
|
204
|
+
if all_fids:
|
|
205
|
+
try:
|
|
206
|
+
invalid = self._db.get_invalidated_fact_ids(all_fids, profile_id)
|
|
207
|
+
except Exception as exc:
|
|
208
|
+
logger.debug("invalidated-fact lookup failed: %s", exc)
|
|
209
|
+
if invalid:
|
|
210
|
+
cid_facts = {
|
|
211
|
+
cid: [(fid, c) for fid, c in lst if fid not in invalid]
|
|
212
|
+
for cid, lst in cid_facts.items()
|
|
213
|
+
}
|
|
214
|
+
return cid_facts
|
|
215
|
+
|
|
216
|
+
def _entity_names(
|
|
217
|
+
self, profile_id: str, communities: dict[int, list[str]],
|
|
218
|
+
) -> dict[str, str]:
|
|
219
|
+
all_eids = list({e for ents in communities.values() for e in ents})
|
|
220
|
+
name_map: dict[str, str] = {}
|
|
221
|
+
chunk = 900
|
|
222
|
+
for start in range(0, len(all_eids), chunk):
|
|
223
|
+
batch = all_eids[start:start + chunk]
|
|
224
|
+
ph = ",".join("?" for _ in batch)
|
|
225
|
+
try:
|
|
226
|
+
rows = self._db.execute(
|
|
227
|
+
"SELECT entity_id, canonical_name FROM canonical_entities "
|
|
228
|
+
f"WHERE profile_id = ? AND entity_id IN ({ph})",
|
|
229
|
+
(profile_id, *batch),
|
|
230
|
+
)
|
|
231
|
+
except Exception as exc:
|
|
232
|
+
logger.debug("entity-name lookup failed: %s", exc)
|
|
233
|
+
continue
|
|
234
|
+
for r in rows:
|
|
235
|
+
d = dict(r)
|
|
236
|
+
name_map[str(d["entity_id"])] = str(d.get("canonical_name") or "")
|
|
237
|
+
return name_map
|
|
238
|
+
|
|
239
|
+
def _keywords(self, contents: list[str]) -> str:
|
|
240
|
+
tokens: list[str] = []
|
|
241
|
+
for text in contents:
|
|
242
|
+
for word in text.lower().split():
|
|
243
|
+
w = word.strip(".,;:!?\"'()[]{}")
|
|
244
|
+
if len(w) > 2 and w not in _STOPWORDS:
|
|
245
|
+
tokens.append(w)
|
|
246
|
+
top = [w for w, _ in Counter(tokens).most_common(self._max_keywords)]
|
|
247
|
+
return ", ".join(top)
|
|
248
|
+
|
|
249
|
+
def _summary(
|
|
250
|
+
self, contents: list[str], entity_names: list[str], keywords: str,
|
|
251
|
+
) -> str:
|
|
252
|
+
if self._summarizer is not None:
|
|
253
|
+
try:
|
|
254
|
+
text = self._summarizer.summarize_cluster(
|
|
255
|
+
[{"content": c} for c in contents],
|
|
256
|
+
)
|
|
257
|
+
if text and text.strip():
|
|
258
|
+
return text.strip()[: self._summary_max_chars]
|
|
259
|
+
except Exception as exc:
|
|
260
|
+
logger.debug("community summarizer failed (fail-open): %s", exc)
|
|
261
|
+
# Mode A keyword-dense fallback.
|
|
262
|
+
names = [n for n in entity_names if n]
|
|
263
|
+
topic = ", ".join(names[:6]) if names else "related memories"
|
|
264
|
+
base = f"Topics: {topic}."
|
|
265
|
+
if keywords:
|
|
266
|
+
base += f" Key terms: {keywords}."
|
|
267
|
+
return base[: self._summary_max_chars]
|