superlocalmemory 2.8.6 → 3.0.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/LICENSE +9 -1
- package/NOTICE +63 -0
- package/README.md +165 -480
- package/bin/slm +17 -449
- package/bin/slm-npm +1 -1
- package/conftest.py +5 -0
- package/docs/api-reference.md +284 -0
- package/docs/architecture.md +149 -0
- package/docs/auto-memory.md +150 -0
- package/docs/cli-reference.md +276 -0
- package/docs/compliance.md +191 -0
- package/docs/configuration.md +182 -0
- package/docs/getting-started.md +102 -0
- package/docs/ide-setup.md +261 -0
- package/docs/mcp-tools.md +220 -0
- package/docs/migration-from-v2.md +170 -0
- package/docs/profiles.md +173 -0
- package/docs/troubleshooting.md +310 -0
- package/{configs → ide/configs}/antigravity-mcp.json +3 -3
- package/ide/configs/chatgpt-desktop-mcp.json +16 -0
- package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
- package/{configs → ide/configs}/codex-mcp.toml +4 -4
- package/{configs → ide/configs}/continue-mcp.yaml +4 -3
- package/{configs → ide/configs}/continue-skills.yaml +6 -6
- package/ide/configs/cursor-mcp.json +15 -0
- package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
- package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
- package/{configs → ide/configs}/opencode-mcp.json +2 -2
- package/{configs → ide/configs}/perplexity-mcp.json +2 -2
- package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
- package/{configs → ide/configs}/windsurf-mcp.json +3 -3
- package/{configs → ide/configs}/zed-mcp.json +2 -2
- package/{hooks → ide/hooks}/context-hook.js +9 -20
- package/ide/hooks/memory-list-skill.js +70 -0
- package/ide/hooks/memory-profile-skill.js +101 -0
- package/ide/hooks/memory-recall-skill.js +62 -0
- package/ide/hooks/memory-remember-skill.js +68 -0
- package/ide/hooks/memory-reset-skill.js +160 -0
- package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
- package/ide/integrations/langchain/README.md +106 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
- package/ide/integrations/langchain/pyproject.toml +38 -0
- package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
- package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
- package/ide/integrations/langchain/tests/test_security.py +117 -0
- package/ide/integrations/llamaindex/README.md +81 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
- package/ide/integrations/llamaindex/pyproject.toml +43 -0
- package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
- package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
- package/ide/integrations/llamaindex/tests/test_security.py +241 -0
- package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
- package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
- package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
- package/package.json +13 -22
- package/pyproject.toml +85 -0
- package/scripts/build-dmg.sh +417 -0
- package/scripts/install-skills.ps1 +334 -0
- package/scripts/postinstall.js +2 -2
- package/scripts/start-dashboard.ps1 +52 -0
- package/scripts/start-dashboard.sh +41 -0
- package/scripts/sync-wiki.ps1 +127 -0
- package/scripts/sync-wiki.sh +82 -0
- package/scripts/test-dmg.sh +161 -0
- package/scripts/test-npm-package.ps1 +252 -0
- package/scripts/test-npm-package.sh +207 -0
- package/scripts/verify-install.ps1 +294 -0
- package/scripts/verify-install.sh +266 -0
- package/src/superlocalmemory/__init__.py +0 -0
- package/src/superlocalmemory/attribution/__init__.py +9 -0
- package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
- package/src/superlocalmemory/attribution/signer.py +153 -0
- package/src/superlocalmemory/attribution/watermark.py +189 -0
- package/src/superlocalmemory/cli/__init__.py +5 -0
- package/src/superlocalmemory/cli/commands.py +245 -0
- package/src/superlocalmemory/cli/main.py +89 -0
- package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
- package/src/superlocalmemory/cli/post_install.py +99 -0
- package/src/superlocalmemory/cli/setup_wizard.py +129 -0
- package/src/superlocalmemory/compliance/__init__.py +0 -0
- package/src/superlocalmemory/compliance/abac.py +204 -0
- package/src/superlocalmemory/compliance/audit.py +314 -0
- package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
- package/src/superlocalmemory/compliance/gdpr.py +294 -0
- package/src/superlocalmemory/compliance/lifecycle.py +158 -0
- package/src/superlocalmemory/compliance/retention.py +232 -0
- package/src/superlocalmemory/compliance/scheduler.py +148 -0
- package/src/superlocalmemory/core/__init__.py +0 -0
- package/src/superlocalmemory/core/config.py +391 -0
- package/src/superlocalmemory/core/embeddings.py +293 -0
- package/src/superlocalmemory/core/engine.py +701 -0
- package/src/superlocalmemory/core/hooks.py +65 -0
- package/src/superlocalmemory/core/maintenance.py +172 -0
- package/src/superlocalmemory/core/modes.py +140 -0
- package/src/superlocalmemory/core/profiles.py +234 -0
- package/src/superlocalmemory/core/registry.py +117 -0
- package/src/superlocalmemory/dynamics/__init__.py +0 -0
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
- package/src/superlocalmemory/encoding/__init__.py +0 -0
- package/src/superlocalmemory/encoding/consolidator.py +485 -0
- package/src/superlocalmemory/encoding/emotional.py +125 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
- package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
- package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
- package/src/superlocalmemory/encoding/foresight.py +91 -0
- package/src/superlocalmemory/encoding/graph_builder.py +302 -0
- package/src/superlocalmemory/encoding/observation_builder.py +160 -0
- package/src/superlocalmemory/encoding/scene_builder.py +183 -0
- package/src/superlocalmemory/encoding/signal_inference.py +90 -0
- package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
- package/src/superlocalmemory/encoding/type_router.py +235 -0
- package/src/superlocalmemory/hooks/__init__.py +3 -0
- package/src/superlocalmemory/hooks/auto_capture.py +111 -0
- package/src/superlocalmemory/hooks/auto_recall.py +93 -0
- package/src/superlocalmemory/hooks/ide_connector.py +204 -0
- package/src/superlocalmemory/hooks/rules_engine.py +99 -0
- package/src/superlocalmemory/infra/__init__.py +3 -0
- package/src/superlocalmemory/infra/auth_middleware.py +82 -0
- package/src/superlocalmemory/infra/backup.py +317 -0
- package/src/superlocalmemory/infra/cache_manager.py +267 -0
- package/src/superlocalmemory/infra/event_bus.py +381 -0
- package/src/superlocalmemory/infra/rate_limiter.py +135 -0
- package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
- package/src/superlocalmemory/learning/__init__.py +0 -0
- package/src/superlocalmemory/learning/adaptive.py +172 -0
- package/src/superlocalmemory/learning/behavioral.py +490 -0
- package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
- package/src/superlocalmemory/learning/bootstrap.py +298 -0
- package/src/superlocalmemory/learning/cross_project.py +399 -0
- package/src/superlocalmemory/learning/database.py +376 -0
- package/src/superlocalmemory/learning/engagement.py +323 -0
- package/src/superlocalmemory/learning/features.py +138 -0
- package/src/superlocalmemory/learning/feedback.py +316 -0
- package/src/superlocalmemory/learning/outcomes.py +255 -0
- package/src/superlocalmemory/learning/project_context.py +366 -0
- package/src/superlocalmemory/learning/ranker.py +155 -0
- package/src/superlocalmemory/learning/source_quality.py +303 -0
- package/src/superlocalmemory/learning/workflows.py +309 -0
- package/src/superlocalmemory/llm/__init__.py +0 -0
- package/src/superlocalmemory/llm/backbone.py +316 -0
- package/src/superlocalmemory/math/__init__.py +0 -0
- package/src/superlocalmemory/math/fisher.py +356 -0
- package/src/superlocalmemory/math/langevin.py +398 -0
- package/src/superlocalmemory/math/sheaf.py +257 -0
- package/src/superlocalmemory/mcp/__init__.py +0 -0
- package/src/superlocalmemory/mcp/resources.py +245 -0
- package/src/superlocalmemory/mcp/server.py +61 -0
- package/src/superlocalmemory/mcp/tools.py +18 -0
- package/src/superlocalmemory/mcp/tools_core.py +305 -0
- package/src/superlocalmemory/mcp/tools_v28.py +223 -0
- package/src/superlocalmemory/mcp/tools_v3.py +286 -0
- package/src/superlocalmemory/retrieval/__init__.py +0 -0
- package/src/superlocalmemory/retrieval/agentic.py +295 -0
- package/src/superlocalmemory/retrieval/ann_index.py +223 -0
- package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
- package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
- package/src/superlocalmemory/retrieval/engine.py +390 -0
- package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
- package/src/superlocalmemory/retrieval/fusion.py +78 -0
- package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
- package/src/superlocalmemory/retrieval/reranker.py +154 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
- package/src/superlocalmemory/retrieval/strategy.py +96 -0
- package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
- package/src/superlocalmemory/server/__init__.py +1 -0
- package/src/superlocalmemory/server/api.py +248 -0
- package/src/superlocalmemory/server/routes/__init__.py +4 -0
- package/src/superlocalmemory/server/routes/agents.py +107 -0
- package/src/superlocalmemory/server/routes/backup.py +91 -0
- package/src/superlocalmemory/server/routes/behavioral.py +127 -0
- package/src/superlocalmemory/server/routes/compliance.py +160 -0
- package/src/superlocalmemory/server/routes/data_io.py +188 -0
- package/src/superlocalmemory/server/routes/events.py +183 -0
- package/src/superlocalmemory/server/routes/helpers.py +85 -0
- package/src/superlocalmemory/server/routes/learning.py +273 -0
- package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
- package/src/superlocalmemory/server/routes/memories.py +399 -0
- package/src/superlocalmemory/server/routes/profiles.py +219 -0
- package/src/superlocalmemory/server/routes/stats.py +346 -0
- package/src/superlocalmemory/server/routes/v3_api.py +365 -0
- package/src/superlocalmemory/server/routes/ws.py +82 -0
- package/src/superlocalmemory/server/security_middleware.py +57 -0
- package/src/superlocalmemory/server/ui.py +245 -0
- package/src/superlocalmemory/storage/__init__.py +0 -0
- package/src/superlocalmemory/storage/access_control.py +182 -0
- package/src/superlocalmemory/storage/database.py +594 -0
- package/src/superlocalmemory/storage/migrations.py +303 -0
- package/src/superlocalmemory/storage/models.py +406 -0
- package/src/superlocalmemory/storage/schema.py +726 -0
- package/src/superlocalmemory/storage/v2_migrator.py +317 -0
- package/src/superlocalmemory/trust/__init__.py +0 -0
- package/src/superlocalmemory/trust/gate.py +130 -0
- package/src/superlocalmemory/trust/provenance.py +124 -0
- package/src/superlocalmemory/trust/scorer.py +347 -0
- package/src/superlocalmemory/trust/signals.py +153 -0
- package/ui/index.html +278 -5
- package/ui/js/auto-settings.js +70 -0
- package/ui/js/dashboard.js +90 -0
- package/ui/js/fact-detail.js +92 -0
- package/ui/js/feedback.js +2 -2
- package/ui/js/ide-status.js +102 -0
- package/ui/js/math-health.js +98 -0
- package/ui/js/recall-lab.js +127 -0
- package/ui/js/settings.js +2 -2
- package/ui/js/trust-dashboard.js +73 -0
- package/api_server.py +0 -724
- package/bin/aider-smart +0 -72
- package/bin/superlocalmemoryv2-learning +0 -4
- package/bin/superlocalmemoryv2-list +0 -3
- package/bin/superlocalmemoryv2-patterns +0 -4
- package/bin/superlocalmemoryv2-profile +0 -3
- package/bin/superlocalmemoryv2-recall +0 -3
- package/bin/superlocalmemoryv2-remember +0 -3
- package/bin/superlocalmemoryv2-reset +0 -3
- package/bin/superlocalmemoryv2-status +0 -3
- package/configs/chatgpt-desktop-mcp.json +0 -16
- package/configs/cursor-mcp.json +0 -15
- package/hooks/memory-list-skill.js +0 -139
- package/hooks/memory-profile-skill.js +0 -273
- package/hooks/memory-recall-skill.js +0 -114
- package/hooks/memory-remember-skill.js +0 -127
- package/hooks/memory-reset-skill.js +0 -274
- package/mcp_server.py +0 -1808
- package/requirements-core.txt +0 -22
- package/requirements-learning.txt +0 -12
- package/requirements.txt +0 -12
- package/src/agent_registry.py +0 -411
- package/src/auth_middleware.py +0 -61
- package/src/auto_backup.py +0 -459
- package/src/behavioral/__init__.py +0 -49
- package/src/behavioral/behavioral_listener.py +0 -203
- package/src/behavioral/behavioral_patterns.py +0 -275
- package/src/behavioral/cross_project_transfer.py +0 -206
- package/src/behavioral/outcome_inference.py +0 -194
- package/src/behavioral/outcome_tracker.py +0 -193
- package/src/behavioral/tests/__init__.py +0 -4
- package/src/behavioral/tests/test_behavioral_integration.py +0 -108
- package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
- package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
- package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
- package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
- package/src/behavioral/tests/test_outcome_inference.py +0 -107
- package/src/behavioral/tests/test_outcome_tracker.py +0 -96
- package/src/cache_manager.py +0 -518
- package/src/compliance/__init__.py +0 -48
- package/src/compliance/abac_engine.py +0 -149
- package/src/compliance/abac_middleware.py +0 -116
- package/src/compliance/audit_db.py +0 -215
- package/src/compliance/audit_logger.py +0 -148
- package/src/compliance/retention_manager.py +0 -289
- package/src/compliance/retention_scheduler.py +0 -186
- package/src/compliance/tests/__init__.py +0 -4
- package/src/compliance/tests/test_abac_enforcement.py +0 -95
- package/src/compliance/tests/test_abac_engine.py +0 -124
- package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
- package/src/compliance/tests/test_audit_db.py +0 -123
- package/src/compliance/tests/test_audit_logger.py +0 -98
- package/src/compliance/tests/test_mcp_audit.py +0 -128
- package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
- package/src/compliance/tests/test_retention_manager.py +0 -131
- package/src/compliance/tests/test_retention_scheduler.py +0 -99
- package/src/compression/__init__.py +0 -25
- package/src/compression/cli.py +0 -150
- package/src/compression/cold_storage.py +0 -217
- package/src/compression/config.py +0 -72
- package/src/compression/orchestrator.py +0 -133
- package/src/compression/tier2_compressor.py +0 -228
- package/src/compression/tier3_compressor.py +0 -153
- package/src/compression/tier_classifier.py +0 -148
- package/src/db_connection_manager.py +0 -536
- package/src/embedding_engine.py +0 -63
- package/src/embeddings/__init__.py +0 -47
- package/src/embeddings/cache.py +0 -70
- package/src/embeddings/cli.py +0 -113
- package/src/embeddings/constants.py +0 -47
- package/src/embeddings/database.py +0 -91
- package/src/embeddings/engine.py +0 -247
- package/src/embeddings/model_loader.py +0 -145
- package/src/event_bus.py +0 -562
- package/src/graph/__init__.py +0 -36
- package/src/graph/build_helpers.py +0 -74
- package/src/graph/cli.py +0 -87
- package/src/graph/cluster_builder.py +0 -188
- package/src/graph/cluster_summary.py +0 -148
- package/src/graph/constants.py +0 -47
- package/src/graph/edge_builder.py +0 -162
- package/src/graph/entity_extractor.py +0 -95
- package/src/graph/graph_core.py +0 -226
- package/src/graph/graph_search.py +0 -231
- package/src/graph/hierarchical.py +0 -207
- package/src/graph/schema.py +0 -99
- package/src/graph_engine.py +0 -52
- package/src/hnsw_index.py +0 -628
- package/src/hybrid_search.py +0 -46
- package/src/learning/__init__.py +0 -217
- package/src/learning/adaptive_ranker.py +0 -682
- package/src/learning/bootstrap/__init__.py +0 -69
- package/src/learning/bootstrap/constants.py +0 -93
- package/src/learning/bootstrap/db_queries.py +0 -316
- package/src/learning/bootstrap/sampling.py +0 -82
- package/src/learning/bootstrap/text_utils.py +0 -71
- package/src/learning/cross_project_aggregator.py +0 -857
- package/src/learning/db/__init__.py +0 -40
- package/src/learning/db/constants.py +0 -44
- package/src/learning/db/schema.py +0 -279
- package/src/learning/engagement_tracker.py +0 -628
- package/src/learning/feature_extractor.py +0 -708
- package/src/learning/feedback_collector.py +0 -806
- package/src/learning/learning_db.py +0 -915
- package/src/learning/project_context_manager.py +0 -572
- package/src/learning/ranking/__init__.py +0 -33
- package/src/learning/ranking/constants.py +0 -84
- package/src/learning/ranking/helpers.py +0 -278
- package/src/learning/source_quality_scorer.py +0 -676
- package/src/learning/synthetic_bootstrap.py +0 -755
- package/src/learning/tests/test_adaptive_ranker.py +0 -325
- package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
- package/src/learning/tests/test_aggregator.py +0 -306
- package/src/learning/tests/test_auto_retrain_v28.py +0 -35
- package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
- package/src/learning/tests/test_feature_extractor_v28.py +0 -93
- package/src/learning/tests/test_feedback_collector.py +0 -294
- package/src/learning/tests/test_learning_db.py +0 -602
- package/src/learning/tests/test_learning_db_v28.py +0 -110
- package/src/learning/tests/test_learning_init_v28.py +0 -48
- package/src/learning/tests/test_outcome_signals.py +0 -48
- package/src/learning/tests/test_project_context.py +0 -292
- package/src/learning/tests/test_schema_migration.py +0 -319
- package/src/learning/tests/test_signal_inference.py +0 -397
- package/src/learning/tests/test_source_quality.py +0 -351
- package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
- package/src/learning/tests/test_workflow_miner.py +0 -318
- package/src/learning/workflow_pattern_miner.py +0 -655
- package/src/lifecycle/__init__.py +0 -54
- package/src/lifecycle/bounded_growth.py +0 -239
- package/src/lifecycle/compaction_engine.py +0 -226
- package/src/lifecycle/lifecycle_engine.py +0 -355
- package/src/lifecycle/lifecycle_evaluator.py +0 -257
- package/src/lifecycle/lifecycle_scheduler.py +0 -130
- package/src/lifecycle/retention_policy.py +0 -285
- package/src/lifecycle/tests/test_bounded_growth.py +0 -193
- package/src/lifecycle/tests/test_compaction.py +0 -179
- package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
- package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
- package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
- package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
- package/src/lifecycle/tests/test_mcp_compact.py +0 -149
- package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
- package/src/lifecycle/tests/test_retention_policy.py +0 -162
- package/src/mcp_tools_v28.py +0 -281
- package/src/memory/__init__.py +0 -36
- package/src/memory/cli.py +0 -205
- package/src/memory/constants.py +0 -39
- package/src/memory/helpers.py +0 -28
- package/src/memory/schema.py +0 -166
- package/src/memory-profiles.py +0 -595
- package/src/memory-reset.py +0 -491
- package/src/memory_compression.py +0 -989
- package/src/memory_store_v2.py +0 -1155
- package/src/migrate_v1_to_v2.py +0 -629
- package/src/pattern_learner.py +0 -34
- package/src/patterns/__init__.py +0 -24
- package/src/patterns/analyzers.py +0 -251
- package/src/patterns/learner.py +0 -271
- package/src/patterns/scoring.py +0 -171
- package/src/patterns/store.py +0 -225
- package/src/patterns/terminology.py +0 -140
- package/src/provenance_tracker.py +0 -312
- package/src/qualixar_attribution.py +0 -139
- package/src/qualixar_watermark.py +0 -78
- package/src/query_optimizer.py +0 -511
- package/src/rate_limiter.py +0 -83
- package/src/search/__init__.py +0 -20
- package/src/search/cli.py +0 -77
- package/src/search/constants.py +0 -26
- package/src/search/engine.py +0 -241
- package/src/search/fusion.py +0 -122
- package/src/search/index_loader.py +0 -114
- package/src/search/methods.py +0 -162
- package/src/search_engine_v2.py +0 -401
- package/src/setup_validator.py +0 -482
- package/src/subscription_manager.py +0 -391
- package/src/tree/__init__.py +0 -59
- package/src/tree/builder.py +0 -185
- package/src/tree/nodes.py +0 -202
- package/src/tree/queries.py +0 -257
- package/src/tree/schema.py +0 -80
- package/src/tree_manager.py +0 -19
- package/src/trust/__init__.py +0 -45
- package/src/trust/constants.py +0 -66
- package/src/trust/queries.py +0 -157
- package/src/trust/schema.py +0 -95
- package/src/trust/scorer.py +0 -299
- package/src/trust/signals.py +0 -95
- package/src/trust_scorer.py +0 -44
- package/ui/app.js +0 -1588
- package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
- package/ui/js/graph-cytoscape.js +0 -1168
- package/ui/js/graph-d3-backup.js +0 -32
- package/ui/js/graph.js +0 -32
- package/ui_server.py +0 -286
- /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
- /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
- /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
- /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
- /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
- /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
- /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
- /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
- /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
- /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
- /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
- /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
- /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
- /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
- /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
- /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
- /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
- /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
- /package/{completions → ide/completions}/slm.bash +0 -0
- /package/{completions → ide/completions}/slm.zsh +0 -0
- /package/{configs → ide/configs}/cody-commands.json +0 -0
- /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
- /package/{install.ps1 → scripts/install.ps1} +0 -0
- /package/{install.sh → scripts/install.sh} +0 -0
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""SuperLocalMemory V3 — Main Memory Engine.
|
|
6
|
+
|
|
7
|
+
Orchestrates the full memory lifecycle: store, encode, retrieve.
|
|
8
|
+
Single entry point for all memory operations.
|
|
9
|
+
Profile-scoped. Mode-aware (A/B/C).
|
|
10
|
+
|
|
11
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from superlocalmemory.core.config import SLMConfig
|
|
20
|
+
from superlocalmemory.core.modes import get_capabilities
|
|
21
|
+
from superlocalmemory.storage.models import (
|
|
22
|
+
AtomicFact, FactType, MemoryRecord, Mode, RecallResponse,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
from superlocalmemory.core.hooks import HookRegistry
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MemoryEngine:
|
|
31
|
+
"""Main orchestrator for the SuperLocalMemory V3 memory system.
|
|
32
|
+
|
|
33
|
+
Wires encoding (fact extraction, entity resolution, graph building,
|
|
34
|
+
consolidation) with retrieval (4-channel search, RRF fusion,
|
|
35
|
+
reranking) and all supporting layers (trust, learning, compliance).
|
|
36
|
+
|
|
37
|
+
Usage::
|
|
38
|
+
|
|
39
|
+
config = SLMConfig.for_mode(Mode.A)
|
|
40
|
+
engine = MemoryEngine(config)
|
|
41
|
+
engine.store("Alice went to Paris last summer", session_id="s1")
|
|
42
|
+
response = engine.recall("Where did Alice go?")
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, config: SLMConfig) -> None:
|
|
46
|
+
self._config = config
|
|
47
|
+
self._caps = get_capabilities(config.mode)
|
|
48
|
+
self._profile_id = config.active_profile
|
|
49
|
+
self._initialized = False
|
|
50
|
+
|
|
51
|
+
self._db = None
|
|
52
|
+
self._embedder = None
|
|
53
|
+
self._llm = None
|
|
54
|
+
self._fact_extractor = None
|
|
55
|
+
self._entity_resolver = None
|
|
56
|
+
self._temporal_parser = None
|
|
57
|
+
self._type_router = None
|
|
58
|
+
self._graph_builder = None
|
|
59
|
+
self._consolidator = None
|
|
60
|
+
self._observation_builder = None
|
|
61
|
+
self._scene_builder = None
|
|
62
|
+
self._entropy_gate = None
|
|
63
|
+
self._retrieval_engine = None
|
|
64
|
+
self._trust_scorer = None
|
|
65
|
+
self._ann_index = None
|
|
66
|
+
self._sheaf_checker = None
|
|
67
|
+
self._provenance = None
|
|
68
|
+
self._adaptive_learner = None
|
|
69
|
+
self._compliance_checker = None
|
|
70
|
+
self._hooks = HookRegistry()
|
|
71
|
+
|
|
72
|
+
def initialize(self) -> None:
|
|
73
|
+
"""Initialize all components. Call once before use."""
|
|
74
|
+
if self._initialized:
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
from superlocalmemory.storage import schema
|
|
78
|
+
from superlocalmemory.storage.database import DatabaseManager
|
|
79
|
+
from superlocalmemory.core.embeddings import EmbeddingService
|
|
80
|
+
from superlocalmemory.llm.backbone import LLMBackbone
|
|
81
|
+
|
|
82
|
+
self._db = DatabaseManager(self._config.db_path)
|
|
83
|
+
self._db.initialize(schema)
|
|
84
|
+
self._embedder = EmbeddingService(self._config.embedding)
|
|
85
|
+
|
|
86
|
+
if self._caps.llm_fact_extraction:
|
|
87
|
+
self._llm = LLMBackbone(self._config.llm)
|
|
88
|
+
if not self._llm.is_available():
|
|
89
|
+
logger.warning("LLM not available. Falling back to Mode A extraction.")
|
|
90
|
+
self._llm = None
|
|
91
|
+
|
|
92
|
+
from superlocalmemory.trust.scorer import TrustScorer
|
|
93
|
+
from superlocalmemory.trust.provenance import ProvenanceTracker
|
|
94
|
+
from superlocalmemory.learning.adaptive import AdaptiveLearner
|
|
95
|
+
from superlocalmemory.compliance.eu_ai_act import EUAIActChecker
|
|
96
|
+
|
|
97
|
+
self._trust_scorer = TrustScorer(self._db)
|
|
98
|
+
|
|
99
|
+
self._init_encoding()
|
|
100
|
+
self._init_retrieval()
|
|
101
|
+
|
|
102
|
+
self._provenance = ProvenanceTracker(self._db)
|
|
103
|
+
self._adaptive_learner = AdaptiveLearner(self._db)
|
|
104
|
+
self._compliance_checker = EUAIActChecker()
|
|
105
|
+
|
|
106
|
+
# Wire lifecycle hooks
|
|
107
|
+
self._wire_hooks()
|
|
108
|
+
|
|
109
|
+
self._initialized = True
|
|
110
|
+
logger.info("MemoryEngine initialized: mode=%s profile=%s",
|
|
111
|
+
self._config.mode.value, self._profile_id)
|
|
112
|
+
|
|
113
|
+
def store(
|
|
114
|
+
self,
|
|
115
|
+
content: str,
|
|
116
|
+
session_id: str = "",
|
|
117
|
+
session_date: str | None = None,
|
|
118
|
+
speaker: str = "",
|
|
119
|
+
role: str = "user",
|
|
120
|
+
metadata: dict[str, Any] | None = None,
|
|
121
|
+
) -> list[str]:
|
|
122
|
+
"""Store content and extract structured facts. Returns fact_ids."""
|
|
123
|
+
self._ensure_init()
|
|
124
|
+
|
|
125
|
+
# Pre-operation hooks (trust gate, ABAC, rate limiter)
|
|
126
|
+
hook_ctx = {"operation": "store", "agent_id": metadata.get("agent_id", "unknown") if metadata else "unknown",
|
|
127
|
+
"profile_id": self._profile_id, "content_preview": content[:100]}
|
|
128
|
+
self._hooks.run_pre("store", hook_ctx)
|
|
129
|
+
|
|
130
|
+
if self._entropy_gate and not self._entropy_gate.should_pass(content):
|
|
131
|
+
return []
|
|
132
|
+
|
|
133
|
+
from superlocalmemory.encoding.temporal_parser import TemporalParser
|
|
134
|
+
parser = self._temporal_parser or TemporalParser()
|
|
135
|
+
parsed_date = parser.parse_session_date(session_date) if session_date else None
|
|
136
|
+
|
|
137
|
+
record = MemoryRecord(
|
|
138
|
+
profile_id=self._profile_id, content=content,
|
|
139
|
+
session_id=session_id, speaker=speaker, role=role,
|
|
140
|
+
session_date=parsed_date, metadata=metadata or {},
|
|
141
|
+
)
|
|
142
|
+
self._db.store_memory(record)
|
|
143
|
+
|
|
144
|
+
facts = self._fact_extractor.extract_facts(
|
|
145
|
+
turns=[content], session_id=session_id,
|
|
146
|
+
session_date=parsed_date, speaker_a=speaker,
|
|
147
|
+
)
|
|
148
|
+
if not facts:
|
|
149
|
+
return []
|
|
150
|
+
|
|
151
|
+
if self._type_router:
|
|
152
|
+
facts = self._type_router.route_facts(facts)
|
|
153
|
+
|
|
154
|
+
stored_ids: list[str] = []
|
|
155
|
+
for fact in facts:
|
|
156
|
+
fact = self._enrich_fact(fact, record)
|
|
157
|
+
|
|
158
|
+
if self._consolidator:
|
|
159
|
+
action = self._consolidator.consolidate(fact, self._profile_id)
|
|
160
|
+
if action.action_type.value == "noop":
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
# Opinion confidence tracking: reinforce or decay
|
|
164
|
+
# When a new opinion aligns with existing, boost confidence.
|
|
165
|
+
# When contradicted (supersede), reduce old fact's confidence.
|
|
166
|
+
if fact.fact_type == FactType.OPINION and action.action_type.value == "update":
|
|
167
|
+
try:
|
|
168
|
+
existing = self._db.get_fact(action.new_fact_id)
|
|
169
|
+
if existing and existing.fact_type == FactType.OPINION:
|
|
170
|
+
new_conf = min(1.0, existing.confidence + 0.1)
|
|
171
|
+
self._db.update_fact(action.new_fact_id, {"confidence": new_conf})
|
|
172
|
+
except Exception:
|
|
173
|
+
pass
|
|
174
|
+
elif fact.fact_type == FactType.OPINION and action.action_type.value == "supersede":
|
|
175
|
+
try:
|
|
176
|
+
old_id = getattr(action, "old_fact_id", None)
|
|
177
|
+
if old_id:
|
|
178
|
+
old_fact = self._db.get_fact(old_id)
|
|
179
|
+
if old_fact:
|
|
180
|
+
new_conf = max(0.0, old_fact.confidence - 0.2)
|
|
181
|
+
self._db.update_fact(old_id, {"confidence": new_conf})
|
|
182
|
+
except Exception:
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
if action.action_type.value in ("update", "supersede"):
|
|
186
|
+
# Run post-processing on updated facts
|
|
187
|
+
updated_fact = self._db.get_fact(action.new_fact_id)
|
|
188
|
+
if updated_fact:
|
|
189
|
+
if self._graph_builder:
|
|
190
|
+
self._graph_builder.build_edges(updated_fact, self._profile_id)
|
|
191
|
+
if self._observation_builder:
|
|
192
|
+
for eid in updated_fact.canonical_entities:
|
|
193
|
+
self._observation_builder.update_profile(
|
|
194
|
+
eid, updated_fact, self._profile_id,
|
|
195
|
+
)
|
|
196
|
+
stored_ids.append(action.new_fact_id)
|
|
197
|
+
continue
|
|
198
|
+
# ADD case: consolidator already stored the fact (F8 fix)
|
|
199
|
+
# Fall through to post-processing below
|
|
200
|
+
else:
|
|
201
|
+
self._db.store_fact(fact)
|
|
202
|
+
|
|
203
|
+
stored_ids.append(fact.fact_id)
|
|
204
|
+
|
|
205
|
+
if fact.embedding and self._ann_index:
|
|
206
|
+
self._ann_index.add(fact.fact_id, fact.embedding)
|
|
207
|
+
if self._graph_builder:
|
|
208
|
+
self._graph_builder.build_edges(fact, self._profile_id)
|
|
209
|
+
|
|
210
|
+
# Sheaf consistency check (runs after edges exist)
|
|
211
|
+
# Cap edge check to prevent O(N^2) hang on large graphs
|
|
212
|
+
if (self._sheaf_checker
|
|
213
|
+
and fact.embedding
|
|
214
|
+
and fact.canonical_entities):
|
|
215
|
+
from superlocalmemory.storage.models import EdgeType, GraphEdge
|
|
216
|
+
try:
|
|
217
|
+
edges_for_fact = self._db.get_edges_for_node(
|
|
218
|
+
fact.fact_id, self._profile_id,
|
|
219
|
+
)
|
|
220
|
+
# Only run sheaf if edge count is manageable
|
|
221
|
+
# At 18K+ edges, the coboundary computation becomes O(N*dim^2)
|
|
222
|
+
if len(edges_for_fact) < self._config.math.sheaf_max_edges_per_check:
|
|
223
|
+
contradictions = self._sheaf_checker.check_consistency(
|
|
224
|
+
fact, self._profile_id,
|
|
225
|
+
)
|
|
226
|
+
for c in contradictions:
|
|
227
|
+
if c.severity > 0.45:
|
|
228
|
+
edge = GraphEdge(
|
|
229
|
+
profile_id=self._profile_id,
|
|
230
|
+
source_id=fact.fact_id,
|
|
231
|
+
target_id=c.fact_id_b,
|
|
232
|
+
edge_type=EdgeType.SUPERSEDES,
|
|
233
|
+
weight=c.severity,
|
|
234
|
+
)
|
|
235
|
+
self._db.store_edge(edge)
|
|
236
|
+
except Exception as exc:
|
|
237
|
+
logger.debug("Sheaf check skipped: %s", exc)
|
|
238
|
+
|
|
239
|
+
if self._observation_builder:
|
|
240
|
+
for eid in fact.canonical_entities:
|
|
241
|
+
self._observation_builder.update_profile(eid, fact, self._profile_id)
|
|
242
|
+
|
|
243
|
+
# Increment fact_count for each linked canonical entity
|
|
244
|
+
for eid in fact.canonical_entities:
|
|
245
|
+
try:
|
|
246
|
+
self._db.increment_entity_fact_count(eid)
|
|
247
|
+
except Exception:
|
|
248
|
+
pass # Non-critical — entity may have been deleted
|
|
249
|
+
if self._scene_builder:
|
|
250
|
+
self._scene_builder.assign_to_scene(fact, self._profile_id)
|
|
251
|
+
|
|
252
|
+
# Populate temporal_events for temporal retrieval
|
|
253
|
+
has_dates = (fact.observation_date or fact.referenced_date
|
|
254
|
+
or fact.interval_start)
|
|
255
|
+
if fact.canonical_entities and has_dates:
|
|
256
|
+
from superlocalmemory.storage.models import TemporalEvent
|
|
257
|
+
for eid in fact.canonical_entities:
|
|
258
|
+
event = TemporalEvent(
|
|
259
|
+
profile_id=self._profile_id, entity_id=eid,
|
|
260
|
+
fact_id=fact.fact_id,
|
|
261
|
+
observation_date=fact.observation_date,
|
|
262
|
+
referenced_date=fact.referenced_date,
|
|
263
|
+
interval_start=fact.interval_start,
|
|
264
|
+
interval_end=fact.interval_end,
|
|
265
|
+
description=fact.content[:200],
|
|
266
|
+
)
|
|
267
|
+
self._db.store_temporal_event(event)
|
|
268
|
+
|
|
269
|
+
# Foresight: extract time-bounded predictions
|
|
270
|
+
try:
|
|
271
|
+
from superlocalmemory.encoding.foresight import extract_foresight_signals
|
|
272
|
+
from superlocalmemory.storage.models import TemporalEvent as _TE
|
|
273
|
+
foresight_signals = extract_foresight_signals(fact)
|
|
274
|
+
for sig in foresight_signals:
|
|
275
|
+
f_event = _TE(
|
|
276
|
+
profile_id=self._profile_id,
|
|
277
|
+
entity_id=sig.get("entity_id", ""),
|
|
278
|
+
fact_id=fact.fact_id,
|
|
279
|
+
interval_start=sig.get("start_time"),
|
|
280
|
+
interval_end=sig.get("end_time"),
|
|
281
|
+
description=sig.get("description", ""),
|
|
282
|
+
)
|
|
283
|
+
self._db.store_temporal_event(f_event)
|
|
284
|
+
except Exception as exc:
|
|
285
|
+
logger.debug("Foresight extraction: %s", exc)
|
|
286
|
+
|
|
287
|
+
# Persist BM25 tokens at ingestion
|
|
288
|
+
bm25 = getattr(self._retrieval_engine, '_bm25', None) if self._retrieval_engine else None
|
|
289
|
+
if bm25:
|
|
290
|
+
bm25.add(fact.fact_id, fact.content, self._profile_id)
|
|
291
|
+
|
|
292
|
+
# Record provenance for data lineage (EU AI Act Art. 10)
|
|
293
|
+
if self._provenance:
|
|
294
|
+
try:
|
|
295
|
+
self._provenance.record(
|
|
296
|
+
fact_id=fact.fact_id,
|
|
297
|
+
profile_id=self._profile_id,
|
|
298
|
+
source_type="store",
|
|
299
|
+
source_id=session_id,
|
|
300
|
+
created_by=speaker or "unknown",
|
|
301
|
+
)
|
|
302
|
+
except Exception:
|
|
303
|
+
pass
|
|
304
|
+
|
|
305
|
+
logger.info("Stored %d facts (session=%s)", len(stored_ids), session_id)
|
|
306
|
+
|
|
307
|
+
# Post-operation hooks (audit, trust signal, event bus)
|
|
308
|
+
hook_ctx["fact_ids"] = stored_ids
|
|
309
|
+
hook_ctx["fact_count"] = len(stored_ids)
|
|
310
|
+
self._hooks.run_post("store", hook_ctx)
|
|
311
|
+
|
|
312
|
+
return stored_ids
|
|
313
|
+
|
|
314
|
+
def recall(
|
|
315
|
+
self, query: str, profile_id: str | None = None,
|
|
316
|
+
mode: Mode | None = None, limit: int = 20,
|
|
317
|
+
agent_id: str = "unknown",
|
|
318
|
+
) -> RecallResponse:
|
|
319
|
+
"""Recall relevant facts for a query.
|
|
320
|
+
|
|
321
|
+
Pipeline: retrieval → agentic sufficiency (if configured) → post-recall updates.
|
|
322
|
+
Agentic sufficiency (sufficiency check): triggers 2-round re-retrieval when
|
|
323
|
+
initial results are insufficient. Mode C uses LLM judgment; Mode A uses
|
|
324
|
+
heuristic alias expansion.
|
|
325
|
+
"""
|
|
326
|
+
self._ensure_init()
|
|
327
|
+
|
|
328
|
+
# Pre-operation hooks
|
|
329
|
+
hook_ctx = {"operation": "recall", "agent_id": agent_id,
|
|
330
|
+
"profile_id": profile_id or self._profile_id, "query_preview": query[:100]}
|
|
331
|
+
self._hooks.run_pre("recall", hook_ctx)
|
|
332
|
+
|
|
333
|
+
pid = profile_id or self._profile_id
|
|
334
|
+
m = mode or self._config.mode
|
|
335
|
+
|
|
336
|
+
response = self._retrieval_engine.recall(query, pid, m, limit)
|
|
337
|
+
|
|
338
|
+
# Agentic sufficiency verification
|
|
339
|
+
# Only trigger when: (a) configured rounds > 0, (b) results look weak
|
|
340
|
+
agentic_rounds = self._config.retrieval.agentic_max_rounds
|
|
341
|
+
if agentic_rounds > 0 and response.results:
|
|
342
|
+
max_score = max((r.score for r in response.results), default=0.0)
|
|
343
|
+
should_trigger = (
|
|
344
|
+
max_score < self._config.retrieval.agentic_confidence_threshold
|
|
345
|
+
or response.query_type == "multi_hop"
|
|
346
|
+
or len(response.results) < 3
|
|
347
|
+
)
|
|
348
|
+
if should_trigger:
|
|
349
|
+
try:
|
|
350
|
+
from superlocalmemory.retrieval.agentic import AgenticRetriever
|
|
351
|
+
agentic = AgenticRetriever(
|
|
352
|
+
confidence_threshold=self._config.retrieval.agentic_confidence_threshold,
|
|
353
|
+
db=self._db,
|
|
354
|
+
)
|
|
355
|
+
enhanced_facts = agentic.retrieve(
|
|
356
|
+
query=query, profile_id=pid,
|
|
357
|
+
retrieval_engine=self._retrieval_engine,
|
|
358
|
+
llm=self._llm,
|
|
359
|
+
top_k=limit,
|
|
360
|
+
query_type=response.query_type,
|
|
361
|
+
)
|
|
362
|
+
# Replace response results with enhanced facts if we got more
|
|
363
|
+
if len(enhanced_facts) > len(response.results):
|
|
364
|
+
from superlocalmemory.storage.models import RetrievalResult
|
|
365
|
+
enhanced_results = []
|
|
366
|
+
for i, f in enumerate(enhanced_facts):
|
|
367
|
+
# Look up real trust score for agentic results
|
|
368
|
+
fact_trust = 0.5
|
|
369
|
+
if self._trust_scorer:
|
|
370
|
+
try:
|
|
371
|
+
fact_trust = self._trust_scorer.get_fact_trust(
|
|
372
|
+
f.fact_id, pid,
|
|
373
|
+
)
|
|
374
|
+
except Exception:
|
|
375
|
+
pass
|
|
376
|
+
enhanced_results.append(RetrievalResult(
|
|
377
|
+
fact=f, score=1.0 / (i + 1),
|
|
378
|
+
channel_scores={"agentic": 1.0},
|
|
379
|
+
confidence=f.confidence,
|
|
380
|
+
evidence_chain=["agentic_round_2"],
|
|
381
|
+
trust_score=fact_trust,
|
|
382
|
+
))
|
|
383
|
+
response = RecallResponse(
|
|
384
|
+
query=query, mode=m, results=enhanced_results[:limit],
|
|
385
|
+
query_type=response.query_type,
|
|
386
|
+
channel_weights=response.channel_weights,
|
|
387
|
+
total_candidates=response.total_candidates + len(enhanced_facts),
|
|
388
|
+
retrieval_time_ms=response.retrieval_time_ms,
|
|
389
|
+
)
|
|
390
|
+
except Exception as exc:
|
|
391
|
+
logger.debug("Agentic sufficiency skipped: %s", exc)
|
|
392
|
+
|
|
393
|
+
# Reconsolidation: access updates trust + count (neuroscience principle)
|
|
394
|
+
if self._trust_scorer:
|
|
395
|
+
for r in response.results:
|
|
396
|
+
self._trust_scorer.update_on_access("fact", r.fact.fact_id, pid)
|
|
397
|
+
|
|
398
|
+
# Fisher Bayesian update on recall
|
|
399
|
+
q_emb = self._embedder.embed(query) if self._embedder else None
|
|
400
|
+
q_var_arr = None
|
|
401
|
+
if self._embedder and q_emb:
|
|
402
|
+
_, q_var_list = self._embedder.compute_fisher_params(q_emb)
|
|
403
|
+
import numpy as _np
|
|
404
|
+
q_var_arr = _np.array(q_var_list, dtype=_np.float64)
|
|
405
|
+
|
|
406
|
+
for r in response.results:
|
|
407
|
+
updates: dict[str, object] = {
|
|
408
|
+
"access_count": r.fact.access_count + 1,
|
|
409
|
+
}
|
|
410
|
+
# Bayesian variance narrowing after 3+ accesses
|
|
411
|
+
if (q_var_arr is not None
|
|
412
|
+
and r.fact.fisher_variance is not None
|
|
413
|
+
and r.fact.access_count >= 3):
|
|
414
|
+
import numpy as _np
|
|
415
|
+
f_var = _np.array(r.fact.fisher_variance, dtype=_np.float64)
|
|
416
|
+
# Conjugate Gaussian update: 1/new_var = 1/f_var + 1/q_var
|
|
417
|
+
new_var = 1.0 / (1.0 / _np.maximum(f_var, 0.05) + 1.0 / _np.maximum(q_var_arr, 0.05))
|
|
418
|
+
new_var = _np.clip(new_var, 0.05, 2.0)
|
|
419
|
+
updates["fisher_variance"] = new_var.tolist()
|
|
420
|
+
|
|
421
|
+
self._db.update_fact(r.fact.fact_id, updates)
|
|
422
|
+
|
|
423
|
+
# Post-operation hooks (audit, trust signal, learning)
|
|
424
|
+
hook_ctx["result_count"] = len(response.results)
|
|
425
|
+
hook_ctx["query_type"] = response.query_type
|
|
426
|
+
self._hooks.run_post("recall", hook_ctx)
|
|
427
|
+
|
|
428
|
+
return response
|
|
429
|
+
|
|
430
|
+
def store_fact_direct(self, fact: AtomicFact) -> str:
|
|
431
|
+
"""Store a pre-built fact with full enrichment.
|
|
432
|
+
|
|
433
|
+
Ensures embedding, Fisher params, canonical entities, BM25 tokens,
|
|
434
|
+
and graph edges are all populated — even for auxiliary data.
|
|
435
|
+
Creates a parent memory record to satisfy FK constraint.
|
|
436
|
+
"""
|
|
437
|
+
self._ensure_init()
|
|
438
|
+
|
|
439
|
+
# Create parent memory record (FK: atomic_facts.memory_id → memories.memory_id)
|
|
440
|
+
if not fact.memory_id:
|
|
441
|
+
from superlocalmemory.storage.models import _new_id
|
|
442
|
+
record = MemoryRecord(
|
|
443
|
+
profile_id=self._profile_id,
|
|
444
|
+
content=fact.content[:500],
|
|
445
|
+
session_id=fact.session_id,
|
|
446
|
+
)
|
|
447
|
+
self._db.store_memory(record)
|
|
448
|
+
fact.memory_id = record.memory_id
|
|
449
|
+
|
|
450
|
+
if not fact.embedding and self._embedder:
|
|
451
|
+
fact.embedding = self._embedder.embed(fact.content)
|
|
452
|
+
if fact.embedding:
|
|
453
|
+
fact.fisher_mean, fact.fisher_variance = (
|
|
454
|
+
self._embedder.compute_fisher_params(fact.embedding)
|
|
455
|
+
)
|
|
456
|
+
if self._entity_resolver and fact.entities:
|
|
457
|
+
canonical = self._entity_resolver.resolve(
|
|
458
|
+
fact.entities, self._profile_id,
|
|
459
|
+
)
|
|
460
|
+
fact.canonical_entities = list(canonical.values())
|
|
461
|
+
self._db.store_fact(fact)
|
|
462
|
+
if fact.embedding and self._ann_index:
|
|
463
|
+
self._ann_index.add(fact.fact_id, fact.embedding)
|
|
464
|
+
if self._graph_builder:
|
|
465
|
+
self._graph_builder.build_edges(fact, self._profile_id)
|
|
466
|
+
# BM25 indexing
|
|
467
|
+
bm25 = getattr(self._retrieval_engine, '_bm25', None) if self._retrieval_engine else None
|
|
468
|
+
if bm25:
|
|
469
|
+
bm25.add(fact.fact_id, fact.content, self._profile_id)
|
|
470
|
+
return fact.fact_id
|
|
471
|
+
|
|
472
|
+
def create_speaker_entities(self, speaker_a: str, speaker_b: str) -> None:
|
|
473
|
+
"""Pre-create canonical entities for conversation speakers."""
|
|
474
|
+
self._ensure_init()
|
|
475
|
+
if self._entity_resolver:
|
|
476
|
+
self._entity_resolver.create_speaker_entities(
|
|
477
|
+
speaker_a, speaker_b, self._profile_id,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
def close_session(self, session_id: str) -> int:
|
|
481
|
+
"""Create session-level temporal summary for session-level retrieval.
|
|
482
|
+
|
|
483
|
+
Aggregates facts from a completed session into temporal_events
|
|
484
|
+
with session scope. Enables temporal queries like "What happened
|
|
485
|
+
in session 3?"
|
|
486
|
+
|
|
487
|
+
Returns number of session summary events created.
|
|
488
|
+
"""
|
|
489
|
+
self._ensure_init()
|
|
490
|
+
from superlocalmemory.storage.models import TemporalEvent
|
|
491
|
+
|
|
492
|
+
facts = self._db.get_all_facts(self._profile_id)
|
|
493
|
+
session_facts = [f for f in facts if f.session_id == session_id]
|
|
494
|
+
if not session_facts:
|
|
495
|
+
return 0
|
|
496
|
+
|
|
497
|
+
# Group by entity for session-level summaries
|
|
498
|
+
entity_facts: dict[str, list[AtomicFact]] = {}
|
|
499
|
+
for f in session_facts:
|
|
500
|
+
for eid in f.canonical_entities:
|
|
501
|
+
entity_facts.setdefault(eid, []).append(f)
|
|
502
|
+
|
|
503
|
+
count = 0
|
|
504
|
+
session_date = session_facts[0].observation_date or ""
|
|
505
|
+
for eid, efacts in entity_facts.items():
|
|
506
|
+
summary_parts = [f.content[:80] for f in efacts[:5]]
|
|
507
|
+
summary = f"Session {session_id}: " + "; ".join(summary_parts)
|
|
508
|
+
event = TemporalEvent(
|
|
509
|
+
profile_id=self._profile_id,
|
|
510
|
+
entity_id=eid,
|
|
511
|
+
fact_id=efacts[0].fact_id,
|
|
512
|
+
observation_date=session_date,
|
|
513
|
+
description=summary[:500],
|
|
514
|
+
)
|
|
515
|
+
self._db.store_temporal_event(event)
|
|
516
|
+
count += 1
|
|
517
|
+
|
|
518
|
+
logger.info(
|
|
519
|
+
"Session %s closed: %d summary events for %d facts",
|
|
520
|
+
session_id, count, len(session_facts),
|
|
521
|
+
)
|
|
522
|
+
return count
|
|
523
|
+
|
|
524
|
+
def close(self) -> None:
|
|
525
|
+
self._initialized = False
|
|
526
|
+
|
|
527
|
+
@property
|
|
528
|
+
def profile_id(self) -> str:
|
|
529
|
+
return self._profile_id
|
|
530
|
+
|
|
531
|
+
@profile_id.setter
|
|
532
|
+
def profile_id(self, value: str) -> None:
|
|
533
|
+
self._profile_id = value
|
|
534
|
+
|
|
535
|
+
@property
|
|
536
|
+
def fact_count(self) -> int:
|
|
537
|
+
self._ensure_init()
|
|
538
|
+
return self._db.get_fact_count(self._profile_id)
|
|
539
|
+
|
|
540
|
+
# -- Internal ----------------------------------------------------------
|
|
541
|
+
|
|
542
|
+
def _ensure_init(self) -> None:
|
|
543
|
+
if not self._initialized:
|
|
544
|
+
self.initialize()
|
|
545
|
+
|
|
546
|
+
def _init_encoding(self) -> None:
|
|
547
|
+
from superlocalmemory.encoding.fact_extractor import FactExtractor
|
|
548
|
+
from superlocalmemory.encoding.entity_resolver import EntityResolver
|
|
549
|
+
from superlocalmemory.encoding.temporal_parser import TemporalParser
|
|
550
|
+
from superlocalmemory.encoding.type_router import TypeRouter
|
|
551
|
+
from superlocalmemory.encoding.graph_builder import GraphBuilder
|
|
552
|
+
from superlocalmemory.encoding.consolidator import MemoryConsolidator
|
|
553
|
+
from superlocalmemory.encoding.observation_builder import ObservationBuilder
|
|
554
|
+
from superlocalmemory.encoding.scene_builder import SceneBuilder
|
|
555
|
+
from superlocalmemory.encoding.entropy_gate import EntropyGate
|
|
556
|
+
from superlocalmemory.retrieval.ann_index import ANNIndex
|
|
557
|
+
|
|
558
|
+
self._ann_index = ANNIndex(dimension=self._config.embedding.dimension)
|
|
559
|
+
self._fact_extractor = FactExtractor(
|
|
560
|
+
config=self._config.encoding, llm=self._llm,
|
|
561
|
+
embedder=self._embedder, mode=self._config.mode,
|
|
562
|
+
)
|
|
563
|
+
self._entity_resolver = EntityResolver(self._db, self._llm)
|
|
564
|
+
self._temporal_parser = TemporalParser()
|
|
565
|
+
self._type_router = TypeRouter(
|
|
566
|
+
mode=self._config.mode, embedder=self._embedder, llm=self._llm,
|
|
567
|
+
)
|
|
568
|
+
self._graph_builder = GraphBuilder(self._db, self._ann_index)
|
|
569
|
+
self._consolidator = MemoryConsolidator(
|
|
570
|
+
self._db, self._embedder, self._llm, self._config.encoding,
|
|
571
|
+
)
|
|
572
|
+
self._observation_builder = ObservationBuilder(self._db)
|
|
573
|
+
self._scene_builder = SceneBuilder(self._db, self._embedder)
|
|
574
|
+
self._entropy_gate = EntropyGate(
|
|
575
|
+
self._embedder, self._config.encoding.entropy_threshold,
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
# Wire Sheaf consistency checker
|
|
579
|
+
if self._config.math.sheaf_at_encoding:
|
|
580
|
+
from superlocalmemory.math.sheaf import SheafConsistencyChecker
|
|
581
|
+
self._sheaf_checker = SheafConsistencyChecker(
|
|
582
|
+
self._db, self._config.math.sheaf_contradiction_threshold,
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
def _init_retrieval(self) -> None:
|
|
586
|
+
from superlocalmemory.retrieval.engine import RetrievalEngine
|
|
587
|
+
from superlocalmemory.retrieval.semantic_channel import SemanticChannel
|
|
588
|
+
from superlocalmemory.retrieval.bm25_channel import BM25Channel
|
|
589
|
+
from superlocalmemory.retrieval.entity_channel import EntityGraphChannel
|
|
590
|
+
from superlocalmemory.retrieval.temporal_channel import TemporalChannel
|
|
591
|
+
from superlocalmemory.retrieval.reranker import CrossEncoderReranker
|
|
592
|
+
from superlocalmemory.retrieval.profile_channel import ProfileChannel
|
|
593
|
+
from superlocalmemory.retrieval.bridge_discovery import BridgeDiscovery
|
|
594
|
+
|
|
595
|
+
channels: dict = {
|
|
596
|
+
"semantic": SemanticChannel(
|
|
597
|
+
self._db,
|
|
598
|
+
fisher_temperature=self._config.math.fisher_temperature,
|
|
599
|
+
embedder=self._embedder,
|
|
600
|
+
fisher_mode=self._config.math.fisher_mode,
|
|
601
|
+
),
|
|
602
|
+
"bm25": BM25Channel(self._db),
|
|
603
|
+
"entity_graph": EntityGraphChannel(self._db, self._entity_resolver),
|
|
604
|
+
"temporal": TemporalChannel(self._db),
|
|
605
|
+
}
|
|
606
|
+
reranker = None
|
|
607
|
+
if self._config.retrieval.use_cross_encoder:
|
|
608
|
+
reranker = CrossEncoderReranker(self._config.retrieval.cross_encoder_model)
|
|
609
|
+
|
|
610
|
+
profile_ch = ProfileChannel(self._db)
|
|
611
|
+
bridge = BridgeDiscovery(self._db)
|
|
612
|
+
|
|
613
|
+
self._retrieval_engine = RetrievalEngine(
|
|
614
|
+
db=self._db, config=self._config.retrieval, channels=channels,
|
|
615
|
+
embedder=self._embedder, reranker=reranker,
|
|
616
|
+
base_weights=self._config.channel_weights,
|
|
617
|
+
profile_channel=profile_ch,
|
|
618
|
+
bridge_discovery=bridge,
|
|
619
|
+
trust_scorer=self._trust_scorer,
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
def _wire_hooks(self) -> None:
|
|
623
|
+
"""Wire trust, compliance, and event bus hooks into engine lifecycle."""
|
|
624
|
+
# -- Pre-store hooks (synchronous, can reject) --
|
|
625
|
+
if self._trust_scorer:
|
|
626
|
+
from superlocalmemory.trust.gate import TrustGate
|
|
627
|
+
gate = TrustGate(self._trust_scorer)
|
|
628
|
+
self._hooks.register_pre("store", lambda ctx: gate.check_write(
|
|
629
|
+
ctx.get("agent_id", "unknown"), ctx.get("profile_id", self._profile_id)))
|
|
630
|
+
self._hooks.register_pre("delete", lambda ctx: gate.check_delete(
|
|
631
|
+
ctx.get("agent_id", "unknown"), ctx.get("profile_id", self._profile_id)))
|
|
632
|
+
|
|
633
|
+
# -- Post-store hooks (async, never block) --
|
|
634
|
+
if self._trust_scorer:
|
|
635
|
+
self._hooks.register_post("store", lambda ctx: self._trust_scorer.record_signal(
|
|
636
|
+
ctx.get("agent_id", "unknown"), ctx.get("profile_id", self._profile_id), "store_success"))
|
|
637
|
+
self._hooks.register_post("recall", lambda ctx: self._trust_scorer.record_signal(
|
|
638
|
+
ctx.get("agent_id", "unknown"), ctx.get("profile_id", self._profile_id), "recall_hit"))
|
|
639
|
+
|
|
640
|
+
# -- Burst detection via SignalRecorder --
|
|
641
|
+
try:
|
|
642
|
+
from superlocalmemory.trust.signals import SignalRecorder
|
|
643
|
+
self._signal_recorder = SignalRecorder(self._db)
|
|
644
|
+
self._hooks.register_post("store", lambda ctx: self._signal_recorder.record(
|
|
645
|
+
ctx.get("agent_id", "unknown"), ctx.get("profile_id", self._profile_id), "store_success"))
|
|
646
|
+
except Exception:
|
|
647
|
+
self._signal_recorder = None
|
|
648
|
+
|
|
649
|
+
# -- Tamper-proof audit chain (all operations logged with hash chain) --
|
|
650
|
+
try:
|
|
651
|
+
from superlocalmemory.compliance.audit import AuditChain
|
|
652
|
+
audit_path = self._config.db_path.parent / "audit_chain.db"
|
|
653
|
+
self._audit_chain = AuditChain(audit_path)
|
|
654
|
+
for op in ("store", "recall", "delete"):
|
|
655
|
+
self._hooks.register_post(op, lambda ctx, _op=op: self._audit_chain.log(
|
|
656
|
+
operation=_op,
|
|
657
|
+
agent_id=ctx.get("agent_id", "unknown"),
|
|
658
|
+
profile_id=ctx.get("profile_id", self._profile_id),
|
|
659
|
+
content_hash=ctx.get("content_hash", ""),
|
|
660
|
+
))
|
|
661
|
+
except Exception:
|
|
662
|
+
self._audit_chain = None
|
|
663
|
+
|
|
664
|
+
def _enrich_fact(self, fact: AtomicFact, record: MemoryRecord) -> AtomicFact:
|
|
665
|
+
"""Enrich fact with embeddings, entities, temporal, emotional data."""
|
|
666
|
+
from superlocalmemory.encoding.emotional import tag_emotion, emotional_importance_boost
|
|
667
|
+
from superlocalmemory.encoding.signal_inference import infer_signal
|
|
668
|
+
|
|
669
|
+
embedding = self._embedder.embed(fact.content) if self._embedder else None
|
|
670
|
+
fisher_mean, fisher_variance = (None, None)
|
|
671
|
+
if self._embedder and embedding:
|
|
672
|
+
fisher_mean, fisher_variance = self._embedder.compute_fisher_params(embedding)
|
|
673
|
+
|
|
674
|
+
canonical = {}
|
|
675
|
+
if self._entity_resolver and fact.entities:
|
|
676
|
+
canonical = self._entity_resolver.resolve(fact.entities, self._profile_id)
|
|
677
|
+
|
|
678
|
+
temporal = {}
|
|
679
|
+
if self._temporal_parser:
|
|
680
|
+
temporal = self._temporal_parser.extract_dates_from_text(fact.content)
|
|
681
|
+
|
|
682
|
+
emotion = tag_emotion(fact.content)
|
|
683
|
+
signal = infer_signal(fact.content)
|
|
684
|
+
|
|
685
|
+
return AtomicFact(
|
|
686
|
+
fact_id=fact.fact_id, memory_id=record.memory_id,
|
|
687
|
+
profile_id=self._profile_id, content=fact.content,
|
|
688
|
+
fact_type=fact.fact_type, entities=fact.entities,
|
|
689
|
+
canonical_entities=list(canonical.values()),
|
|
690
|
+
observation_date=fact.observation_date or record.session_date,
|
|
691
|
+
referenced_date=fact.referenced_date or temporal.get("referenced_date"),
|
|
692
|
+
interval_start=fact.interval_start or temporal.get("interval_start"),
|
|
693
|
+
interval_end=fact.interval_end or temporal.get("interval_end"),
|
|
694
|
+
confidence=fact.confidence,
|
|
695
|
+
importance=min(1.0, fact.importance + emotional_importance_boost(emotion)),
|
|
696
|
+
evidence_count=fact.evidence_count,
|
|
697
|
+
source_turn_ids=fact.source_turn_ids, session_id=record.session_id,
|
|
698
|
+
embedding=embedding, fisher_mean=fisher_mean, fisher_variance=fisher_variance,
|
|
699
|
+
emotional_valence=emotion.valence, emotional_arousal=emotion.arousal,
|
|
700
|
+
signal_type=signal, created_at=fact.created_at,
|
|
701
|
+
)
|