superlocalmemory 3.6.23 → 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 +271 -71
- package/bin/slm-npm +43 -89
- 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 +18 -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 +453 -191
- 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 +1 -1
- 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 +123 -35
- 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 +219 -32
- 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 +1 -1
- 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 +12 -12
- 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 +8 -63
- 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/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 -516
- 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
|
@@ -12,6 +12,7 @@ Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
14
|
import logging
|
|
15
|
+
import json
|
|
15
16
|
from typing import TYPE_CHECKING, Any
|
|
16
17
|
|
|
17
18
|
if TYPE_CHECKING:
|
|
@@ -181,6 +182,13 @@ def run_store(
|
|
|
181
182
|
auto_linker: Any = None,
|
|
182
183
|
context_generator: Any = None,
|
|
183
184
|
consolidation_engine: Any = None,
|
|
185
|
+
existing_memory_id: str | None = None,
|
|
186
|
+
queryable_fact_ids: tuple[str, ...] = (),
|
|
187
|
+
trusted_actor_id: str = "",
|
|
188
|
+
pre_authorized: bool = False,
|
|
189
|
+
ingestion_source_type: str = "store",
|
|
190
|
+
ingestion_operation_id: str = "",
|
|
191
|
+
derivation_report: dict[str, bool] | None = None,
|
|
184
192
|
) -> list[str]:
|
|
185
193
|
"""Store content and extract structured facts. Returns fact_ids.
|
|
186
194
|
|
|
@@ -190,11 +198,15 @@ def run_store(
|
|
|
190
198
|
# Pre-operation hooks (trust gate, ABAC, rate limiter)
|
|
191
199
|
hook_ctx = {
|
|
192
200
|
"operation": "store",
|
|
193
|
-
"agent_id":
|
|
201
|
+
"agent_id": (
|
|
202
|
+
trusted_actor_id
|
|
203
|
+
or (metadata.get("agent_id", "unknown") if metadata else "unknown")
|
|
204
|
+
),
|
|
194
205
|
"profile_id": profile_id,
|
|
195
206
|
"content_preview": content[:100],
|
|
196
207
|
}
|
|
197
|
-
|
|
208
|
+
if not pre_authorized:
|
|
209
|
+
hooks.run_pre("store", hook_ctx)
|
|
198
210
|
|
|
199
211
|
if entropy_gate and not entropy_gate.should_pass(content):
|
|
200
212
|
return []
|
|
@@ -216,19 +228,65 @@ def run_store(
|
|
|
216
228
|
parser = temporal_parser or TemporalParser()
|
|
217
229
|
parsed_date = parser.parse_session_date(session_date) if session_date else None
|
|
218
230
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
231
|
+
queryable_ids = frozenset(queryable_fact_ids)
|
|
232
|
+
queryable_facts: list[AtomicFact] = []
|
|
233
|
+
if existing_memory_id:
|
|
234
|
+
memory_rows = db.execute(
|
|
235
|
+
"SELECT * FROM memories WHERE memory_id=?",
|
|
236
|
+
(existing_memory_id,),
|
|
237
|
+
)
|
|
238
|
+
if not memory_rows:
|
|
239
|
+
raise ValueError("queryable ingestion memory does not exist")
|
|
240
|
+
memory = dict(memory_rows[0])
|
|
241
|
+
if memory["profile_id"] != profile_id:
|
|
242
|
+
raise ValueError("queryable ingestion memory profile mismatch")
|
|
243
|
+
if memory["content"] != content:
|
|
244
|
+
raise ValueError("queryable ingestion memory content mismatch")
|
|
245
|
+
stored_shared = json.loads(memory.get("shared_with") or "null")
|
|
246
|
+
if (memory.get("scope") or "personal") != scope:
|
|
247
|
+
raise ValueError("queryable ingestion memory scope mismatch")
|
|
248
|
+
if (stored_shared or None) != (shared_with or None):
|
|
249
|
+
raise ValueError("queryable ingestion shared scope mismatch")
|
|
250
|
+
queryable_facts = db.get_facts_by_ids(list(queryable_ids), profile_id)
|
|
251
|
+
if len(queryable_facts) != len(queryable_ids):
|
|
252
|
+
raise ValueError("queryable ingestion fact profile mismatch or missing fact")
|
|
253
|
+
if any(fact.memory_id != existing_memory_id for fact in queryable_facts):
|
|
254
|
+
raise ValueError("queryable ingestion fact belongs to another memory")
|
|
255
|
+
record = MemoryRecord(
|
|
256
|
+
memory_id=existing_memory_id,
|
|
257
|
+
profile_id=profile_id,
|
|
258
|
+
content=content,
|
|
259
|
+
session_id=memory.get("session_id") or session_id,
|
|
260
|
+
speaker=memory.get("speaker") or speaker,
|
|
261
|
+
role=memory.get("role") or role,
|
|
262
|
+
session_date=memory.get("session_date") or parsed_date,
|
|
263
|
+
created_at=memory["created_at"],
|
|
264
|
+
metadata=json.loads(memory.get("metadata_json") or "{}"),
|
|
265
|
+
scope=scope,
|
|
266
|
+
shared_with=shared_with,
|
|
267
|
+
)
|
|
268
|
+
else:
|
|
269
|
+
record = MemoryRecord(
|
|
270
|
+
profile_id=profile_id, content=content,
|
|
271
|
+
session_id=session_id, speaker=speaker, role=role,
|
|
272
|
+
session_date=parsed_date, metadata=metadata or {},
|
|
273
|
+
scope=scope, shared_with=shared_with,
|
|
274
|
+
)
|
|
275
|
+
db.store_memory(record)
|
|
276
|
+
|
|
277
|
+
extraction_complete = False
|
|
278
|
+
consolidation_complete = consolidator is not None
|
|
279
|
+
canonicalization_complete = entity_resolver is not None
|
|
280
|
+
graph_complete = graph_builder is not None
|
|
281
|
+
temporal_complete = True
|
|
282
|
+
provenance_complete = provenance is not None
|
|
226
283
|
|
|
227
284
|
try:
|
|
228
285
|
facts = fact_extractor.extract_facts(
|
|
229
286
|
turns=[content], session_id=session_id,
|
|
230
287
|
session_date=parsed_date, speaker_a=speaker,
|
|
231
288
|
)
|
|
289
|
+
extraction_complete = facts is not None
|
|
232
290
|
except Exception as _extract_exc:
|
|
233
291
|
# P0-1 (remember-write-04): an extractor EXCEPTION (transient LLM/embed
|
|
234
292
|
# backend error) must NOT orphan the already-committed memory. The None
|
|
@@ -256,7 +314,8 @@ def run_store(
|
|
|
256
314
|
# V3.3.20: Stronger verbatim filter — skip greetings, filler, short phrases.
|
|
257
315
|
# Verbatim facts with just "Hey! How are you?" dilute embeddings and add noise.
|
|
258
316
|
_MIN_VERBATIM_WORDS = 8
|
|
259
|
-
if (
|
|
317
|
+
if (not queryable_facts
|
|
318
|
+
and content.strip()
|
|
260
319
|
and len(content.strip()) >= 40
|
|
261
320
|
and len(content.strip().split()) >= _MIN_VERBATIM_WORDS):
|
|
262
321
|
import uuid
|
|
@@ -285,6 +344,19 @@ def run_store(
|
|
|
285
344
|
if verbatim.content.strip().lower() not in extracted_texts:
|
|
286
345
|
facts.append(verbatim)
|
|
287
346
|
|
|
347
|
+
if queryable_facts:
|
|
348
|
+
# Replace any extractor-produced copy of the complete raw turn with the
|
|
349
|
+
# already-queryable projection, then promote that stable fact ID in
|
|
350
|
+
# place. Large inputs may have a clamped queryable projection, so the
|
|
351
|
+
# projection itself is authoritative for the verbatim retrieval unit.
|
|
352
|
+
raw_text = content.strip().lower()
|
|
353
|
+
facts = [
|
|
354
|
+
fact for fact in facts
|
|
355
|
+
if fact.content.strip().lower() != raw_text
|
|
356
|
+
and fact.fact_id not in queryable_ids
|
|
357
|
+
]
|
|
358
|
+
facts.extend(queryable_facts)
|
|
359
|
+
|
|
288
360
|
# V3.3.21: If fact extraction produced nothing (short input like "this is test"),
|
|
289
361
|
# store the raw content as a minimal fact. User explicitly called `slm remember` —
|
|
290
362
|
# their data should NEVER be silently dropped. The min-length and min-word filters
|
|
@@ -319,9 +391,39 @@ def run_store(
|
|
|
319
391
|
temporal_parser=temporal_parser,
|
|
320
392
|
)
|
|
321
393
|
|
|
394
|
+
is_queryable_promotion = fact.fact_id in queryable_ids
|
|
395
|
+
if is_queryable_promotion:
|
|
396
|
+
db.update_fact(fact.fact_id, {
|
|
397
|
+
"content": fact.content,
|
|
398
|
+
"fact_type": fact.fact_type,
|
|
399
|
+
"entities_json": fact.entities,
|
|
400
|
+
"canonical_entities_json": fact.canonical_entities,
|
|
401
|
+
"observation_date": fact.observation_date,
|
|
402
|
+
"referenced_date": fact.referenced_date,
|
|
403
|
+
"interval_start": fact.interval_start,
|
|
404
|
+
"interval_end": fact.interval_end,
|
|
405
|
+
"confidence": fact.confidence,
|
|
406
|
+
"importance": fact.importance,
|
|
407
|
+
"evidence_count": fact.evidence_count,
|
|
408
|
+
"access_count": fact.access_count,
|
|
409
|
+
"source_turn_ids_json": fact.source_turn_ids,
|
|
410
|
+
"session_id": fact.session_id,
|
|
411
|
+
"embedding": fact.embedding,
|
|
412
|
+
"fisher_mean": fact.fisher_mean,
|
|
413
|
+
"fisher_variance": fact.fisher_variance,
|
|
414
|
+
"lifecycle": fact.lifecycle,
|
|
415
|
+
"langevin_position": fact.langevin_position,
|
|
416
|
+
"emotional_valence": fact.emotional_valence,
|
|
417
|
+
"emotional_arousal": fact.emotional_arousal,
|
|
418
|
+
"signal_type": fact.signal_type,
|
|
419
|
+
})
|
|
322
420
|
if consolidator:
|
|
323
421
|
try:
|
|
324
|
-
action = consolidator.consolidate(
|
|
422
|
+
action = consolidator.consolidate(
|
|
423
|
+
fact,
|
|
424
|
+
profile_id,
|
|
425
|
+
exclude_fact_ids=queryable_ids,
|
|
426
|
+
)
|
|
325
427
|
except Exception as _consolidate_exc:
|
|
326
428
|
# P0-1 (remember-write-03): a consolidate failure (e.g. LLM
|
|
327
429
|
# timeout) must NOT orphan the already-committed memory. Fall
|
|
@@ -331,19 +433,31 @@ def run_store(
|
|
|
331
433
|
"consolidate() failed for fact %s — storing raw fact as "
|
|
332
434
|
"fallback: %s", fact.fact_id, _consolidate_exc,
|
|
333
435
|
)
|
|
436
|
+
consolidation_complete = False
|
|
334
437
|
action = None
|
|
335
438
|
|
|
336
439
|
if action is not None:
|
|
337
440
|
if action.action_type.value == "noop":
|
|
338
|
-
|
|
441
|
+
# A canonical ingestion projection already exists before
|
|
442
|
+
# enrichment. Reconcile it against pre-existing facts and
|
|
443
|
+
# remove it when consolidation proves it is a duplicate.
|
|
444
|
+
target_id = action.existing_fact_id
|
|
445
|
+
if is_queryable_promotion and target_id:
|
|
446
|
+
db.delete_fact(fact.fact_id)
|
|
447
|
+
existing_fact = db.get_fact(target_id) if target_id else None
|
|
448
|
+
if existing_fact is None:
|
|
449
|
+
continue
|
|
450
|
+
fact = existing_fact
|
|
339
451
|
|
|
340
452
|
# Opinion confidence tracking: reinforce or decay
|
|
341
453
|
if fact.fact_type == FactType.OPINION and action.action_type.value == "update":
|
|
342
454
|
try:
|
|
343
|
-
existing = db.get_fact(
|
|
455
|
+
existing = db.get_fact(
|
|
456
|
+
action.existing_fact_id or action.new_fact_id
|
|
457
|
+
)
|
|
344
458
|
if existing and existing.fact_type == FactType.OPINION:
|
|
345
459
|
new_conf = min(1.0, existing.confidence + 0.1)
|
|
346
|
-
db.update_fact(
|
|
460
|
+
db.update_fact(existing.fact_id, {"confidence": new_conf})
|
|
347
461
|
except Exception:
|
|
348
462
|
pass
|
|
349
463
|
elif fact.fact_type == FactType.OPINION and action.action_type.value == "supersede":
|
|
@@ -358,35 +472,37 @@ def run_store(
|
|
|
358
472
|
pass
|
|
359
473
|
|
|
360
474
|
if action.action_type.value in ("update", "supersede"):
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
475
|
+
target_id = (
|
|
476
|
+
(action.existing_fact_id or action.new_fact_id)
|
|
477
|
+
if action.action_type.value == "update"
|
|
478
|
+
else action.new_fact_id
|
|
479
|
+
)
|
|
480
|
+
if is_queryable_promotion and target_id != fact.fact_id:
|
|
481
|
+
db.delete_fact(fact.fact_id)
|
|
482
|
+
updated_fact = db.get_fact(target_id)
|
|
483
|
+
if updated_fact is None:
|
|
484
|
+
raise RuntimeError(
|
|
485
|
+
f"consolidation {action.action_type.value} produced "
|
|
486
|
+
f"missing fact {target_id}"
|
|
369
487
|
)
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
eid, updated_fact, profile_id,
|
|
376
|
-
)
|
|
377
|
-
stored_ids.append(action.new_fact_id)
|
|
378
|
-
continue
|
|
488
|
+
# Continue through the shared index/graph/temporal/
|
|
489
|
+
# provenance stages. The previous early continue made
|
|
490
|
+
# UPDATE/SUPERSEDE facts look stored while skipping half of
|
|
491
|
+
# canonical materialization.
|
|
492
|
+
fact = updated_fact
|
|
379
493
|
# ADD case: consolidator already stored the fact (F8 fix)
|
|
380
494
|
# Fall through to post-processing below
|
|
381
495
|
else:
|
|
382
496
|
# Consolidate failed → store the raw fact ourselves so the
|
|
383
497
|
# memory is never left without a retrievable fact, then fall
|
|
384
498
|
# through to post-processing (embeddings, graph, context).
|
|
385
|
-
|
|
386
|
-
|
|
499
|
+
if not is_queryable_promotion:
|
|
500
|
+
db.store_fact(fact)
|
|
501
|
+
elif not is_queryable_promotion:
|
|
387
502
|
db.store_fact(fact)
|
|
388
503
|
|
|
389
|
-
|
|
504
|
+
if fact.fact_id not in stored_ids:
|
|
505
|
+
stored_ids.append(fact.fact_id)
|
|
390
506
|
|
|
391
507
|
# Dual-write embedding to ANN index + vector store (embed on-demand if
|
|
392
508
|
# a consolidated ADD fact arrived without one). See _upsert_fact_vectors.
|
|
@@ -461,6 +577,7 @@ def run_store(
|
|
|
461
577
|
len(invalidations), fact.fact_id,
|
|
462
578
|
)
|
|
463
579
|
except Exception as exc:
|
|
580
|
+
temporal_complete = False
|
|
464
581
|
logger.debug(
|
|
465
582
|
"Temporal validation skipped for fact %s: %s",
|
|
466
583
|
fact.fact_id, exc,
|
|
@@ -488,6 +605,8 @@ def run_store(
|
|
|
488
605
|
event = TemporalEvent(
|
|
489
606
|
profile_id=profile_id, entity_id=eid,
|
|
490
607
|
fact_id=fact.fact_id,
|
|
608
|
+
scope=fact.scope,
|
|
609
|
+
shared_with=fact.shared_with,
|
|
491
610
|
observation_date=fact.observation_date,
|
|
492
611
|
referenced_date=fact.referenced_date,
|
|
493
612
|
interval_start=fact.interval_start,
|
|
@@ -506,6 +625,8 @@ def run_store(
|
|
|
506
625
|
profile_id=profile_id,
|
|
507
626
|
entity_id=sig.get("entity_id", ""),
|
|
508
627
|
fact_id=fact.fact_id,
|
|
628
|
+
scope=fact.scope,
|
|
629
|
+
shared_with=fact.shared_with,
|
|
509
630
|
interval_start=sig.get("start_time"),
|
|
510
631
|
interval_end=sig.get("end_time"),
|
|
511
632
|
description=sig.get("description", ""),
|
|
@@ -525,12 +646,12 @@ def run_store(
|
|
|
525
646
|
provenance.record(
|
|
526
647
|
fact_id=fact.fact_id,
|
|
527
648
|
profile_id=profile_id,
|
|
528
|
-
source_type=
|
|
529
|
-
source_id=session_id,
|
|
530
|
-
created_by=speaker or "unknown",
|
|
649
|
+
source_type=ingestion_source_type,
|
|
650
|
+
source_id=ingestion_operation_id or session_id,
|
|
651
|
+
created_by=trusted_actor_id or speaker or "unknown",
|
|
531
652
|
)
|
|
532
653
|
except Exception:
|
|
533
|
-
|
|
654
|
+
provenance_complete = False
|
|
534
655
|
|
|
535
656
|
logger.info("Stored %d facts (session=%s)", len(stored_ids), session_id)
|
|
536
657
|
|
|
@@ -539,6 +660,16 @@ def run_store(
|
|
|
539
660
|
hook_ctx["fact_count"] = len(stored_ids)
|
|
540
661
|
hooks.run_post("store", hook_ctx)
|
|
541
662
|
|
|
663
|
+
if derivation_report is not None:
|
|
664
|
+
derivation_report.update({
|
|
665
|
+
"extraction": extraction_complete,
|
|
666
|
+
"canonicalization": canonicalization_complete,
|
|
667
|
+
"consolidation": consolidation_complete,
|
|
668
|
+
"graph": graph_complete,
|
|
669
|
+
"temporal": temporal_complete,
|
|
670
|
+
"provenance": provenance_complete,
|
|
671
|
+
})
|
|
672
|
+
|
|
542
673
|
# Phase 5: Step-count trigger for lightweight consolidation (L7)
|
|
543
674
|
if consolidation_engine is not None:
|
|
544
675
|
try:
|
|
@@ -602,8 +733,6 @@ def run_store_fact_direct(
|
|
|
602
733
|
)
|
|
603
734
|
fact.canonical_entities = list(canonical.values())
|
|
604
735
|
db.store_fact(fact)
|
|
605
|
-
# v3.4.5: Incremental sync to CozoDB/LanceDB (F-04)
|
|
606
|
-
_sync_to_graph_backends(fact)
|
|
607
736
|
if fact.embedding and ann_index:
|
|
608
737
|
ann_index.add(fact.fact_id, fact.embedding)
|
|
609
738
|
# V3.2: VectorStore upsert (dual-write)
|
|
@@ -615,6 +744,9 @@ def run_store_fact_direct(
|
|
|
615
744
|
)
|
|
616
745
|
if graph_builder:
|
|
617
746
|
graph_builder.build_edges(fact, profile_id)
|
|
747
|
+
# The graph projection must run after GraphBuilder: syncing immediately
|
|
748
|
+
# after SQLite fact insertion omitted every new fact edge from Cozo.
|
|
749
|
+
_sync_to_graph_backends(fact)
|
|
618
750
|
# BM25 indexing
|
|
619
751
|
bm25 = getattr(retrieval_engine, '_bm25', None) if retrieval_engine else None
|
|
620
752
|
if bm25:
|
|
@@ -29,6 +29,11 @@ import logging
|
|
|
29
29
|
from datetime import datetime, timedelta, UTC
|
|
30
30
|
from typing import TYPE_CHECKING
|
|
31
31
|
|
|
32
|
+
from superlocalmemory.core.lifecycle_state import (
|
|
33
|
+
reconcile_profile_lifecycle,
|
|
34
|
+
set_fact_lifecycle_zone,
|
|
35
|
+
)
|
|
36
|
+
|
|
32
37
|
if TYPE_CHECKING:
|
|
33
38
|
from superlocalmemory.storage.database import DatabaseManager
|
|
34
39
|
|
|
@@ -77,6 +82,10 @@ def evaluate_tiers(
|
|
|
77
82
|
"total_evaluated": 0,
|
|
78
83
|
}
|
|
79
84
|
|
|
85
|
+
# Repair any rows written by pre-V3.7 split lifecycle paths before making
|
|
86
|
+
# new transition decisions.
|
|
87
|
+
reconcile_profile_lifecycle(db, profile_id)
|
|
88
|
+
|
|
80
89
|
now = datetime.now(UTC)
|
|
81
90
|
pinned_ids = _get_pinned_fact_ids(db, profile_id)
|
|
82
91
|
|
|
@@ -126,13 +135,12 @@ def promote_on_access_batch(db: DatabaseManager, fact_ids: list[str]) -> int:
|
|
|
126
135
|
"""
|
|
127
136
|
if not fact_ids:
|
|
128
137
|
return 0
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
138
|
+
return set_fact_lifecycle_zone(
|
|
139
|
+
db,
|
|
140
|
+
fact_ids,
|
|
141
|
+
"active",
|
|
142
|
+
from_atomic=("warm", "cold"),
|
|
134
143
|
)
|
|
135
|
-
return len(fact_ids)
|
|
136
144
|
|
|
137
145
|
|
|
138
146
|
def promote_on_access(db: DatabaseManager, fact_id: str) -> None:
|
|
@@ -140,10 +148,11 @@ def promote_on_access(db: DatabaseManager, fact_id: str) -> None:
|
|
|
140
148
|
|
|
141
149
|
Kept for backward compatibility. Prefer promote_on_access_batch.
|
|
142
150
|
"""
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
151
|
+
set_fact_lifecycle_zone(
|
|
152
|
+
db,
|
|
153
|
+
[fact_id],
|
|
154
|
+
"active",
|
|
155
|
+
from_atomic=("warm", "cold"),
|
|
147
156
|
)
|
|
148
157
|
|
|
149
158
|
|
|
@@ -164,10 +173,8 @@ def pin_fact(
|
|
|
164
173
|
"(fact_id, profile_id, pinned_at, reason) VALUES (?, ?, ?, ?)",
|
|
165
174
|
(fact_id, profile_id, now, reason),
|
|
166
175
|
)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
"WHERE fact_id = ? AND profile_id = ?",
|
|
170
|
-
(fact_id, profile_id),
|
|
176
|
+
set_fact_lifecycle_zone(
|
|
177
|
+
db, [fact_id], "active", profile_id=profile_id,
|
|
171
178
|
)
|
|
172
179
|
return True
|
|
173
180
|
except Exception as exc:
|
|
@@ -315,11 +322,12 @@ def _demote_tier(
|
|
|
315
322
|
# Batch UPDATE in chunks of 500
|
|
316
323
|
for i in range(0, len(demoted_ids), 500):
|
|
317
324
|
batch = demoted_ids[i:i + 500]
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
325
|
+
set_fact_lifecycle_zone(
|
|
326
|
+
db,
|
|
327
|
+
batch,
|
|
328
|
+
to_tier,
|
|
329
|
+
profile_id=profile_id,
|
|
330
|
+
from_atomic=(from_tier,),
|
|
323
331
|
)
|
|
324
332
|
|
|
325
333
|
return len(demoted_ids)
|
|
@@ -444,6 +452,10 @@ def _sync_tiers_to_backends(
|
|
|
444
452
|
|
|
445
453
|
if _lancedb_backend and hasattr(_lancedb_backend, "bulk_update_tiers_from_sqlite"):
|
|
446
454
|
try:
|
|
447
|
-
|
|
455
|
+
# DatabaseManager intentionally has no public `.conn`; use its
|
|
456
|
+
# transaction boundary so tier synchronization cannot silently
|
|
457
|
+
# fail after a scale projection has been promoted.
|
|
458
|
+
with db.raw_connection() as conn:
|
|
459
|
+
_lancedb_backend.bulk_update_tiers_from_sqlite(conn)
|
|
448
460
|
except Exception as exc:
|
|
449
461
|
logger.warning("LanceDB tier sync failed: %s", exc)
|
|
@@ -101,12 +101,21 @@ class WorkerPool:
|
|
|
101
101
|
})
|
|
102
102
|
|
|
103
103
|
def delete_memory(self, fact_id: str, agent_id: str = "system") -> dict:
|
|
104
|
-
"""Delete
|
|
105
|
-
return self._send({
|
|
104
|
+
"""Delete by fact ID; ``agent_id`` is untrusted audit metadata."""
|
|
105
|
+
return self._send({
|
|
106
|
+
"cmd": "delete_memory",
|
|
107
|
+
"fact_id": fact_id,
|
|
108
|
+
"source_agent_id": agent_id,
|
|
109
|
+
})
|
|
106
110
|
|
|
107
111
|
def update_memory(self, fact_id: str, content: str, agent_id: str = "system") -> dict:
|
|
108
|
-
"""Update content
|
|
109
|
-
return self._send({
|
|
112
|
+
"""Update content; ``agent_id`` is untrusted audit metadata."""
|
|
113
|
+
return self._send({
|
|
114
|
+
"cmd": "update_memory",
|
|
115
|
+
"fact_id": fact_id,
|
|
116
|
+
"content": content,
|
|
117
|
+
"source_agent_id": agent_id,
|
|
118
|
+
})
|
|
110
119
|
|
|
111
120
|
def get_memory_facts(self, memory_id: str) -> dict:
|
|
112
121
|
"""Get original memory text + child atomic facts."""
|
|
@@ -22,7 +22,7 @@ HR-03: Original float32 NEVER deleted unless keep_float32_backup=False
|
|
|
22
22
|
HR-04: Quantization ONLY via EAP scheduler (not ad-hoc).
|
|
23
23
|
|
|
24
24
|
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
25
|
-
License:
|
|
25
|
+
License: AGPL-3.0-or-later
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
28
|
from __future__ import annotations
|
|
@@ -96,7 +96,7 @@ class EAPScheduler:
|
|
|
96
96
|
self._quantized_store = quantized_store
|
|
97
97
|
self._config = config
|
|
98
98
|
|
|
99
|
-
def run_eap_cycle(self, profile_id: str) -> dict:
|
|
99
|
+
def run_eap_cycle(self, profile_id: str, *, dry_run: bool = False) -> dict:
|
|
100
100
|
"""Execute one EAP cycle for a profile.
|
|
101
101
|
|
|
102
102
|
Steps:
|
|
@@ -153,12 +153,16 @@ class EAPScheduler:
|
|
|
153
153
|
|
|
154
154
|
if target_bw == 0:
|
|
155
155
|
# Forgotten -- mark as deleted
|
|
156
|
-
|
|
156
|
+
if not dry_run:
|
|
157
|
+
self._handle_deletion(fact_id, profile_id)
|
|
157
158
|
stats["deleted"] += 1
|
|
158
159
|
continue
|
|
159
160
|
|
|
160
161
|
if target_bw < current_bw:
|
|
161
162
|
# Downgrade -- compress to lower precision
|
|
163
|
+
if dry_run:
|
|
164
|
+
stats["downgrades"] += 1
|
|
165
|
+
continue
|
|
162
166
|
success = self._handle_downgrade(
|
|
163
167
|
fact_id, profile_id, target_bw,
|
|
164
168
|
)
|
|
@@ -168,6 +172,9 @@ class EAPScheduler:
|
|
|
168
172
|
stats["errors"] += 1
|
|
169
173
|
else:
|
|
170
174
|
# Upgrade -- restore to higher precision (only if float32 exists)
|
|
175
|
+
if dry_run:
|
|
176
|
+
stats["upgrades"] += 1
|
|
177
|
+
continue
|
|
171
178
|
success = self._handle_upgrade(
|
|
172
179
|
fact_id, profile_id, target_bw,
|
|
173
180
|
)
|
|
@@ -34,7 +34,7 @@ References:
|
|
|
34
34
|
TurboQuant (ICLR 2026). Recursive polar quantization.
|
|
35
35
|
|
|
36
36
|
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
37
|
-
License:
|
|
37
|
+
License: AGPL-3.0-or-later
|
|
38
38
|
IP Novelty: 92% (no prior art for retention-gated consolidation + polar quantization)
|
|
39
39
|
"""
|
|
40
40
|
|
|
@@ -741,13 +741,13 @@ class CognitiveConsolidator:
|
|
|
741
741
|
cluster_id=cluster.cluster_id,
|
|
742
742
|
)
|
|
743
743
|
|
|
744
|
-
# Archive source facts (HR-04: soft-archive, never delete)
|
|
744
|
+
# Archive source facts (HR-04: soft-archive, never delete) through the
|
|
745
|
+
# lifecycle invariant writer.
|
|
746
|
+
from superlocalmemory.core.lifecycle_state import set_fact_lifecycle_zone
|
|
747
|
+
set_fact_lifecycle_zone(
|
|
748
|
+
self._db, cluster.fact_ids, "archive", profile_id=profile_id,
|
|
749
|
+
)
|
|
745
750
|
for fact_id in cluster.fact_ids:
|
|
746
|
-
self._db.execute(
|
|
747
|
-
"UPDATE atomic_facts SET lifecycle = 'archived' "
|
|
748
|
-
"WHERE fact_id = ? AND profile_id = ?",
|
|
749
|
-
(fact_id, profile_id),
|
|
750
|
-
)
|
|
751
751
|
# Log access event
|
|
752
752
|
self._db.execute(
|
|
753
753
|
"INSERT INTO fact_access_log "
|
|
@@ -756,15 +756,6 @@ class CognitiveConsolidator:
|
|
|
756
756
|
"VALUES (?, ?, ?, datetime('now'), 'consolidation', 'ccq')",
|
|
757
757
|
(_new_id(), fact_id, profile_id),
|
|
758
758
|
)
|
|
759
|
-
# Update fact_retention zone
|
|
760
|
-
self._db.execute(
|
|
761
|
-
"UPDATE fact_retention "
|
|
762
|
-
"SET lifecycle_zone = 'archive', "
|
|
763
|
-
" last_computed_at = datetime('now') "
|
|
764
|
-
"WHERE fact_id = ? AND profile_id = ?",
|
|
765
|
-
(fact_id, profile_id),
|
|
766
|
-
)
|
|
767
|
-
|
|
768
759
|
return block_id
|
|
769
760
|
|
|
770
761
|
# ------------------------------------------------------------------
|
|
@@ -13,7 +13,7 @@ Mode A: keyword-based contradiction detection (zero LLM).
|
|
|
13
13
|
Mode B/C: LLM-assisted contradiction detection when available.
|
|
14
14
|
|
|
15
15
|
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
16
|
-
License:
|
|
16
|
+
License: AGPL-3.0-or-later
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
19
|
from __future__ import annotations
|
|
@@ -100,13 +100,26 @@ class MemoryConsolidator:
|
|
|
100
100
|
# -- Public API ---------------------------------------------------------
|
|
101
101
|
|
|
102
102
|
def consolidate(
|
|
103
|
-
self,
|
|
103
|
+
self,
|
|
104
|
+
new_fact: AtomicFact,
|
|
105
|
+
profile_id: str,
|
|
106
|
+
*,
|
|
107
|
+
exclude_fact_ids: set[str] | frozenset[str] | None = None,
|
|
104
108
|
) -> ConsolidationAction:
|
|
105
109
|
"""Consolidate *new_fact* against existing knowledge.
|
|
106
110
|
|
|
107
111
|
Returns a ``ConsolidationAction`` describing what was done.
|
|
112
|
+
|
|
113
|
+
``exclude_fact_ids`` is reserved for an ingestion operation's own
|
|
114
|
+
queryable projection. That projection is evidence being promoted,
|
|
115
|
+
not pre-existing knowledge, and must not suppress its derived facts as
|
|
116
|
+
near-duplicates.
|
|
108
117
|
"""
|
|
109
|
-
candidates = self._find_candidates(
|
|
118
|
+
candidates = self._find_candidates(
|
|
119
|
+
new_fact,
|
|
120
|
+
profile_id,
|
|
121
|
+
exclude_fact_ids=exclude_fact_ids,
|
|
122
|
+
)
|
|
110
123
|
|
|
111
124
|
if not candidates:
|
|
112
125
|
return self._execute_add(new_fact, profile_id, reason="no matching facts")
|
|
@@ -168,7 +181,11 @@ class MemoryConsolidator:
|
|
|
168
181
|
# -- Candidate search ---------------------------------------------------
|
|
169
182
|
|
|
170
183
|
def _find_candidates(
|
|
171
|
-
self,
|
|
184
|
+
self,
|
|
185
|
+
new_fact: AtomicFact,
|
|
186
|
+
profile_id: str,
|
|
187
|
+
*,
|
|
188
|
+
exclude_fact_ids: set[str] | frozenset[str] | None = None,
|
|
172
189
|
) -> list[tuple[AtomicFact, float]]:
|
|
173
190
|
"""Find and score candidate matches from existing facts.
|
|
174
191
|
|
|
@@ -178,7 +195,7 @@ class MemoryConsolidator:
|
|
|
178
195
|
|
|
179
196
|
Returns sorted list of (fact, combined_score), descending.
|
|
180
197
|
"""
|
|
181
|
-
seen_ids: set[str] = set()
|
|
198
|
+
seen_ids: set[str] = set(exclude_fact_ids or ())
|
|
182
199
|
candidate_facts: list[AtomicFact] = []
|
|
183
200
|
|
|
184
201
|
# --- entity-based candidates ---
|