superlocalmemory 4.1.13 → 4.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +48 -0
- package/README.md +3 -3
- package/package.json +1 -8
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +1 -1
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/agents/slm-memory-advisor.md +1 -1
- package/plugin-src/agents/slm-optimize-advisor.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/plugin-src/skills/slm-cache/SKILL.md +1 -1
- package/plugin-src/skills/slm-compress/SKILL.md +1 -1
- package/plugin-src/skills/slm-governance/SKILL.md +1 -1
- package/plugin-src/skills/slm-graph/SKILL.md +1 -1
- package/plugin-src/skills/slm-loop/SKILL.md +1 -1
- package/plugin-src/skills/slm-mesh/SKILL.md +1 -1
- package/plugin-src/skills/slm-profile/SKILL.md +1 -1
- package/plugin-src/skills/slm-recall/SKILL.md +1 -1
- package/plugin-src/skills/slm-remember/SKILL.md +1 -1
- package/plugin-src/skills/slm-scope/SKILL.md +1 -1
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/scripts/postinstall.js +71 -2
- package/pyproject.toml +0 -250
- package/src/superlocalmemory/__init__.py +0 -82
- package/src/superlocalmemory/access/__init__.py +0 -3
- package/src/superlocalmemory/access/rbac.py +0 -575
- package/src/superlocalmemory/attribution/__init__.py +0 -9
- package/src/superlocalmemory/attribution/signer.py +0 -173
- package/src/superlocalmemory/attribution/watermark.py +0 -189
- package/src/superlocalmemory/brain/__init__.py +0 -5
- package/src/superlocalmemory/brain/truth.py +0 -418
- package/src/superlocalmemory/cli/__init__.py +0 -5
- package/src/superlocalmemory/cli/__main__.py +0 -17
- package/src/superlocalmemory/cli/_lazy_init.py +0 -115
- package/src/superlocalmemory/cli/cache_cmd.py +0 -198
- package/src/superlocalmemory/cli/commands.py +0 -4710
- package/src/superlocalmemory/cli/compress_cmd.py +0 -151
- package/src/superlocalmemory/cli/context_commands.py +0 -193
- package/src/superlocalmemory/cli/daemon.py +0 -909
- package/src/superlocalmemory/cli/db_migrate.py +0 -150
- package/src/superlocalmemory/cli/diagnostics_cmd.py +0 -101
- package/src/superlocalmemory/cli/escape_hatch.py +0 -220
- package/src/superlocalmemory/cli/evidence_cmd.py +0 -103
- package/src/superlocalmemory/cli/gdpr_cmd.py +0 -792
- package/src/superlocalmemory/cli/gdpr_io.py +0 -109
- package/src/superlocalmemory/cli/help_cmd.py +0 -197
- package/src/superlocalmemory/cli/host_upgrades.py +0 -189
- package/src/superlocalmemory/cli/ingest_cmd.py +0 -327
- package/src/superlocalmemory/cli/json_output.py +0 -81
- package/src/superlocalmemory/cli/loop_cmd.py +0 -187
- package/src/superlocalmemory/cli/main.py +0 -1145
- package/src/superlocalmemory/cli/mesh_cmd.py +0 -38
- package/src/superlocalmemory/cli/migrate_cmd.py +0 -55
- package/src/superlocalmemory/cli/ops_cmd.py +0 -281
- package/src/superlocalmemory/cli/optimize_cmd.py +0 -179
- package/src/superlocalmemory/cli/optimize_constants.py +0 -31
- package/src/superlocalmemory/cli/pending_store.py +0 -296
- package/src/superlocalmemory/cli/proxy_cmd.py +0 -108
- package/src/superlocalmemory/cli/scale_engine_cmd.py +0 -56
- package/src/superlocalmemory/cli/service_installer.py +0 -373
- package/src/superlocalmemory/cli/setup_wizard.py +0 -1162
- package/src/superlocalmemory/cli/summary_cmd.py +0 -215
- package/src/superlocalmemory/cli/version_banner.py +0 -202
- package/src/superlocalmemory/cli/wizard_v3426_options.py +0 -129
- package/src/superlocalmemory/code_graph/__init__.py +0 -46
- package/src/superlocalmemory/code_graph/blast_radius.py +0 -177
- package/src/superlocalmemory/code_graph/bridge/__init__.py +0 -36
- package/src/superlocalmemory/code_graph/bridge/entity_resolver.py +0 -490
- package/src/superlocalmemory/code_graph/bridge/event_listeners.py +0 -206
- package/src/superlocalmemory/code_graph/bridge/fact_enricher.py +0 -159
- package/src/superlocalmemory/code_graph/bridge/hebbian_linker.py +0 -170
- package/src/superlocalmemory/code_graph/bridge/maintenance.py +0 -220
- package/src/superlocalmemory/code_graph/bridge/temporal_checker.py +0 -152
- package/src/superlocalmemory/code_graph/changes.py +0 -363
- package/src/superlocalmemory/code_graph/communities.py +0 -299
- package/src/superlocalmemory/code_graph/config.py +0 -154
- package/src/superlocalmemory/code_graph/database.py +0 -526
- package/src/superlocalmemory/code_graph/extractors/__init__.py +0 -95
- package/src/superlocalmemory/code_graph/extractors/python.py +0 -413
- package/src/superlocalmemory/code_graph/extractors/typescript.py +0 -556
- package/src/superlocalmemory/code_graph/flows.py +0 -350
- package/src/superlocalmemory/code_graph/git_hooks.py +0 -226
- package/src/superlocalmemory/code_graph/graph_engine.py +0 -295
- package/src/superlocalmemory/code_graph/graph_store.py +0 -335
- package/src/superlocalmemory/code_graph/incremental.py +0 -200
- package/src/superlocalmemory/code_graph/models.py +0 -130
- package/src/superlocalmemory/code_graph/parser.py +0 -687
- package/src/superlocalmemory/code_graph/resolver.py +0 -321
- package/src/superlocalmemory/code_graph/search.py +0 -460
- package/src/superlocalmemory/code_graph/service.py +0 -95
- package/src/superlocalmemory/code_graph/watcher.py +0 -207
- package/src/superlocalmemory/compliance/__init__.py +0 -0
- package/src/superlocalmemory/compliance/abac.py +0 -204
- package/src/superlocalmemory/compliance/audit.py +0 -385
- package/src/superlocalmemory/compliance/eu_ai_act.py +0 -101
- package/src/superlocalmemory/compliance/gdpr.py +0 -1479
- package/src/superlocalmemory/compliance/lifecycle.py +0 -158
- package/src/superlocalmemory/compliance/retention.py +0 -415
- package/src/superlocalmemory/compliance/scheduler.py +0 -217
- package/src/superlocalmemory/contracts/__init__.py +0 -1
- package/src/superlocalmemory/contracts/schemas/agent-experience-v1.schema.json +0 -92
- package/src/superlocalmemory/contracts/schemas/agent-integration-contract-v2.schema.json +0 -46
- package/src/superlocalmemory/contracts/schemas/cognitive-turn-receipt-v1.schema.json +0 -59
- package/src/superlocalmemory/contracts/v402.py +0 -62
- package/src/superlocalmemory/core/__init__.py +0 -0
- package/src/superlocalmemory/core/actor_context.py +0 -166
- package/src/superlocalmemory/core/admission.py +0 -769
- package/src/superlocalmemory/core/backend_orchestrator.py +0 -637
- package/src/superlocalmemory/core/block_hygiene.py +0 -147
- package/src/superlocalmemory/core/community_summary.py +0 -267
- package/src/superlocalmemory/core/component_healer.py +0 -144
- package/src/superlocalmemory/core/component_registry.py +0 -514
- package/src/superlocalmemory/core/config.py +0 -2204
- package/src/superlocalmemory/core/consolidation_engine.py +0 -983
- package/src/superlocalmemory/core/context_cache.py +0 -574
- package/src/superlocalmemory/core/derivation_lineage.py +0 -246
- package/src/superlocalmemory/core/embedding_worker.py +0 -208
- package/src/superlocalmemory/core/embeddings.py +0 -1052
- package/src/superlocalmemory/core/engine.py +0 -1395
- package/src/superlocalmemory/core/engine_capabilities.py +0 -24
- package/src/superlocalmemory/core/engine_ingestion.py +0 -983
- package/src/superlocalmemory/core/engine_lock.py +0 -75
- package/src/superlocalmemory/core/engine_wiring.py +0 -776
- package/src/superlocalmemory/core/entity_community.py +0 -178
- package/src/superlocalmemory/core/error_envelope.py +0 -60
- package/src/superlocalmemory/core/evidence_bundle.py +0 -528
- package/src/superlocalmemory/core/fact_consolidator.py +0 -812
- package/src/superlocalmemory/core/file_lock.py +0 -92
- package/src/superlocalmemory/core/graph_analyzer.py +0 -456
- package/src/superlocalmemory/core/graph_metrics.py +0 -597
- package/src/superlocalmemory/core/graph_pruner.py +0 -939
- package/src/superlocalmemory/core/health_monitor.py +0 -338
- package/src/superlocalmemory/core/hooks.py +0 -65
- package/src/superlocalmemory/core/ingest_gate.py +0 -133
- package/src/superlocalmemory/core/ingest_policy.py +0 -38
- package/src/superlocalmemory/core/ingestion_command.py +0 -1042
- package/src/superlocalmemory/core/injection.py +0 -434
- package/src/superlocalmemory/core/install_detector.py +0 -131
- package/src/superlocalmemory/core/key_expander.py +0 -138
- package/src/superlocalmemory/core/lifecycle_state.py +0 -153
- package/src/superlocalmemory/core/maintenance.py +0 -777
- package/src/superlocalmemory/core/maintenance_scheduler.py +0 -507
- package/src/superlocalmemory/core/materialization_control.py +0 -20
- package/src/superlocalmemory/core/mcp_embedder_proxy.py +0 -89
- package/src/superlocalmemory/core/memory_health.py +0 -266
- package/src/superlocalmemory/core/mode_capability.py +0 -111
- package/src/superlocalmemory/core/modes.py +0 -168
- package/src/superlocalmemory/core/mutations.py +0 -688
- package/src/superlocalmemory/core/ollama_embedder.py +0 -266
- package/src/superlocalmemory/core/ollama_validator.py +0 -315
- package/src/superlocalmemory/core/operation_policy.py +0 -92
- package/src/superlocalmemory/core/operation_policy_registry.py +0 -542
- package/src/superlocalmemory/core/operation_request.py +0 -127
- package/src/superlocalmemory/core/ops_remediation.py +0 -542
- package/src/superlocalmemory/core/pii.py +0 -105
- package/src/superlocalmemory/core/platform_utils.py +0 -138
- package/src/superlocalmemory/core/profiles.py +0 -234
- package/src/superlocalmemory/core/progressive_abstraction.py +0 -208
- package/src/superlocalmemory/core/projection_drain.py +0 -380
- package/src/superlocalmemory/core/queue_consumer.py +0 -168
- package/src/superlocalmemory/core/ram_lock.py +0 -160
- package/src/superlocalmemory/core/rate_limit.py +0 -151
- package/src/superlocalmemory/core/recall_gate.py +0 -95
- package/src/superlocalmemory/core/recall_pipeline.py +0 -1337
- package/src/superlocalmemory/core/recall_queue.py +0 -377
- package/src/superlocalmemory/core/recall_worker.py +0 -414
- package/src/superlocalmemory/core/registry.py +0 -121
- package/src/superlocalmemory/core/remember_admission.py +0 -161
- package/src/superlocalmemory/core/remember_runtime.py +0 -1190
- package/src/superlocalmemory/core/remote_mode.py +0 -214
- package/src/superlocalmemory/core/reranker_worker.py +0 -338
- package/src/superlocalmemory/core/safe_fs.py +0 -108
- package/src/superlocalmemory/core/scale_autopromote.py +0 -196
- package/src/superlocalmemory/core/scale_engine.py +0 -915
- package/src/superlocalmemory/core/score_contract.py +0 -82
- package/src/superlocalmemory/core/security_primitives.py +0 -672
- package/src/superlocalmemory/core/session_identity.py +0 -98
- package/src/superlocalmemory/core/shadow_router.py +0 -319
- package/src/superlocalmemory/core/slm_disabled.py +0 -85
- package/src/superlocalmemory/core/status_contract.py +0 -108
- package/src/superlocalmemory/core/store_pipeline.py +0 -1404
- package/src/superlocalmemory/core/summarizer.py +0 -200
- package/src/superlocalmemory/core/tier_manager.py +0 -461
- package/src/superlocalmemory/core/topic_signature.py +0 -156
- package/src/superlocalmemory/core/transactions/__init__.py +0 -78
- package/src/superlocalmemory/core/transactions/concrete_owners.py +0 -604
- package/src/superlocalmemory/core/transactions/erasure.py +0 -825
- package/src/superlocalmemory/core/transactions/manifest.py +0 -255
- package/src/superlocalmemory/core/transactions/manifest_key.py +0 -155
- package/src/superlocalmemory/core/transactions/obligations.py +0 -272
- package/src/superlocalmemory/core/transactions/owners.py +0 -114
- package/src/superlocalmemory/core/transactions/reconciler.py +0 -285
- package/src/superlocalmemory/core/transactions/service.py +0 -330
- package/src/superlocalmemory/core/worker_pool.py +0 -377
- package/src/superlocalmemory/core/working_memory.py +0 -288
- package/src/superlocalmemory/dynamics/__init__.py +0 -0
- package/src/superlocalmemory/dynamics/activation_guided_quantization.py +0 -374
- package/src/superlocalmemory/dynamics/eap_scheduler.py +0 -294
- package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +0 -171
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +0 -227
- package/src/superlocalmemory/encoding/__init__.py +0 -0
- package/src/superlocalmemory/encoding/auto_linker.py +0 -308
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +0 -899
- package/src/superlocalmemory/encoding/consolidator.py +0 -472
- package/src/superlocalmemory/encoding/context_generator.py +0 -175
- package/src/superlocalmemory/encoding/emotional.py +0 -189
- package/src/superlocalmemory/encoding/entity_reflexion.py +0 -200
- package/src/superlocalmemory/encoding/entity_resolver.py +0 -687
- package/src/superlocalmemory/encoding/entropy_gate.py +0 -101
- package/src/superlocalmemory/encoding/fact_extractor.py +0 -877
- package/src/superlocalmemory/encoding/foresight.py +0 -93
- package/src/superlocalmemory/encoding/graph_builder.py +0 -346
- package/src/superlocalmemory/encoding/observation_builder.py +0 -177
- package/src/superlocalmemory/encoding/prospective_markers.py +0 -262
- package/src/superlocalmemory/encoding/scene_builder.py +0 -410
- package/src/superlocalmemory/encoding/signal_inference.py +0 -90
- package/src/superlocalmemory/encoding/temporal_parser.py +0 -432
- package/src/superlocalmemory/encoding/temporal_validator.py +0 -572
- package/src/superlocalmemory/encoding/type_router.py +0 -237
- package/src/superlocalmemory/evaluation/__init__.py +0 -13
- package/src/superlocalmemory/evaluation/calibration.py +0 -308
- package/src/superlocalmemory/evolution/__init__.py +0 -29
- package/src/superlocalmemory/evolution/blind_verifier.py +0 -122
- package/src/superlocalmemory/evolution/budget.py +0 -356
- package/src/superlocalmemory/evolution/evolution_store.py +0 -619
- package/src/superlocalmemory/evolution/llm_dispatch.py +0 -559
- package/src/superlocalmemory/evolution/model_selection.py +0 -175
- package/src/superlocalmemory/evolution/mutation_generator.py +0 -226
- package/src/superlocalmemory/evolution/skill_activator.py +0 -270
- package/src/superlocalmemory/evolution/skill_evolver.py +0 -928
- package/src/superlocalmemory/evolution/triggers.py +0 -376
- package/src/superlocalmemory/evolution/types.py +0 -114
- package/src/superlocalmemory/graph/__init__.py +0 -9
- package/src/superlocalmemory/graph/cozo_adjacency.py +0 -122
- package/src/superlocalmemory/graph/cozo_backend.py +0 -751
- package/src/superlocalmemory/hooks/__init__.py +0 -3
- package/src/superlocalmemory/hooks/_outcome_common.py +0 -523
- package/src/superlocalmemory/hooks/adapter_base.py +0 -347
- package/src/superlocalmemory/hooks/antigravity_adapter.py +0 -171
- package/src/superlocalmemory/hooks/auto_capture.py +0 -133
- package/src/superlocalmemory/hooks/auto_invoker.py +0 -521
- package/src/superlocalmemory/hooks/auto_parameterize.py +0 -147
- package/src/superlocalmemory/hooks/auto_recall.py +0 -191
- package/src/superlocalmemory/hooks/auto_recall_hook.py +0 -251
- package/src/superlocalmemory/hooks/before_web_hook.py +0 -131
- package/src/superlocalmemory/hooks/claude_code_hooks.py +0 -637
- package/src/superlocalmemory/hooks/codex_assets.py +0 -251
- package/src/superlocalmemory/hooks/codex_hooks.py +0 -186
- package/src/superlocalmemory/hooks/context_payload.py +0 -311
- package/src/superlocalmemory/hooks/copilot_adapter.py +0 -208
- package/src/superlocalmemory/hooks/cross_platform_connector.py +0 -90
- package/src/superlocalmemory/hooks/cursor_adapter.py +0 -173
- package/src/superlocalmemory/hooks/hook_daemon.py +0 -295
- package/src/superlocalmemory/hooks/hook_handlers.py +0 -822
- package/src/superlocalmemory/hooks/ide_connector.py +0 -246
- package/src/superlocalmemory/hooks/memory_protocol.py +0 -158
- package/src/superlocalmemory/hooks/portable_kit.py +0 -755
- package/src/superlocalmemory/hooks/post_tool_async_hook.py +0 -183
- package/src/superlocalmemory/hooks/post_tool_outcome_hook.py +0 -351
- package/src/superlocalmemory/hooks/prewarm_auth.py +0 -187
- package/src/superlocalmemory/hooks/rules_engine.py +0 -99
- package/src/superlocalmemory/hooks/session_registry.py +0 -330
- package/src/superlocalmemory/hooks/stop_outcome_hook.py +0 -138
- package/src/superlocalmemory/hooks/sync_loop.py +0 -114
- package/src/superlocalmemory/hooks/topic_shift_hook.py +0 -302
- package/src/superlocalmemory/hooks/user_prompt_hook.py +0 -131
- package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +0 -210
- package/src/superlocalmemory/infra/__init__.py +0 -3
- package/src/superlocalmemory/infra/auth_middleware.py +0 -145
- package/src/superlocalmemory/infra/backup.py +0 -974
- package/src/superlocalmemory/infra/backup_obligations.py +0 -423
- package/src/superlocalmemory/infra/cache_manager.py +0 -267
- package/src/superlocalmemory/infra/cloud_backup.py +0 -788
- package/src/superlocalmemory/infra/daemon_identity.py +0 -300
- package/src/superlocalmemory/infra/data_root.py +0 -238
- package/src/superlocalmemory/infra/event_bus.py +0 -637
- package/src/superlocalmemory/infra/local_diagnostics.py +0 -327
- package/src/superlocalmemory/infra/pid_manager.py +0 -193
- package/src/superlocalmemory/infra/process_identity.py +0 -180
- package/src/superlocalmemory/infra/process_reaper.py +0 -624
- package/src/superlocalmemory/infra/rate_limiter.py +0 -228
- package/src/superlocalmemory/infra/self_heal.py +0 -401
- package/src/superlocalmemory/infra/version_integrity.py +0 -229
- package/src/superlocalmemory/ingestion/__init__.py +0 -13
- package/src/superlocalmemory/ingestion/adapter_manager.py +0 -255
- package/src/superlocalmemory/ingestion/base_adapter.py +0 -171
- package/src/superlocalmemory/ingestion/calendar_adapter.py +0 -349
- package/src/superlocalmemory/ingestion/credentials.py +0 -125
- package/src/superlocalmemory/ingestion/gmail_adapter.py +0 -378
- package/src/superlocalmemory/ingestion/parsers.py +0 -100
- package/src/superlocalmemory/ingestion/transcript_adapter.py +0 -161
- package/src/superlocalmemory/integrations/__init__.py +0 -1
- package/src/superlocalmemory/integrations/bounded_loops_mcp.py +0 -431
- package/src/superlocalmemory/integrations/bounded_loops_v051.py +0 -236
- package/src/superlocalmemory/learning/__init__.py +0 -0
- package/src/superlocalmemory/learning/adaptive.py +0 -172
- package/src/superlocalmemory/learning/arm_catalog.py +0 -97
- package/src/superlocalmemory/learning/assertion_miner.py +0 -403
- package/src/superlocalmemory/learning/bandit.py +0 -654
- package/src/superlocalmemory/learning/bandit_cache.py +0 -131
- package/src/superlocalmemory/learning/behavioral.py +0 -542
- package/src/superlocalmemory/learning/bootstrap.py +0 -298
- package/src/superlocalmemory/learning/consolidation_cycle.py +0 -398
- package/src/superlocalmemory/learning/consolidation_quantization_worker.py +0 -115
- package/src/superlocalmemory/learning/consolidation_worker.py +0 -261
- package/src/superlocalmemory/learning/cross_project.py +0 -408
- package/src/superlocalmemory/learning/database.py +0 -698
- package/src/superlocalmemory/learning/dedup_hnsw.py +0 -413
- package/src/superlocalmemory/learning/engagement.py +0 -487
- package/src/superlocalmemory/learning/engagement_features.py +0 -279
- package/src/superlocalmemory/learning/ensemble.py +0 -309
- package/src/superlocalmemory/learning/entity_compiler.py +0 -356
- package/src/superlocalmemory/learning/fact_outcome_joins.py +0 -207
- package/src/superlocalmemory/learning/features.py +0 -138
- package/src/superlocalmemory/learning/feedback.py +0 -724
- package/src/superlocalmemory/learning/forgetting_scheduler.py +0 -375
- package/src/superlocalmemory/learning/hnsw_dedup.py +0 -69
- package/src/superlocalmemory/learning/labeler.py +0 -85
- package/src/superlocalmemory/learning/legacy_migration.py +0 -316
- package/src/superlocalmemory/learning/lightgbm_subprocess.py +0 -236
- package/src/superlocalmemory/learning/memory_merge.py +0 -175
- package/src/superlocalmemory/learning/model_cache.py +0 -267
- package/src/superlocalmemory/learning/model_rollback.py +0 -281
- package/src/superlocalmemory/learning/outcome_queue.py +0 -306
- package/src/superlocalmemory/learning/outcomes.py +0 -286
- package/src/superlocalmemory/learning/pattern_miner.py +0 -465
- package/src/superlocalmemory/learning/pattern_miner_constants.py +0 -90
- package/src/superlocalmemory/learning/pcos.py +0 -291
- package/src/superlocalmemory/learning/project_context.py +0 -366
- package/src/superlocalmemory/learning/propensity.py +0 -131
- package/src/superlocalmemory/learning/ranker.py +0 -300
- package/src/superlocalmemory/learning/ranker_common.py +0 -163
- package/src/superlocalmemory/learning/ranker_retrain_legacy.py +0 -210
- package/src/superlocalmemory/learning/ranker_retrain_online.py +0 -423
- package/src/superlocalmemory/learning/reward.py +0 -888
- package/src/superlocalmemory/learning/reward_archive.py +0 -223
- package/src/superlocalmemory/learning/reward_boost.py +0 -211
- package/src/superlocalmemory/learning/reward_from_outcomes.py +0 -365
- package/src/superlocalmemory/learning/reward_model.py +0 -144
- package/src/superlocalmemory/learning/reward_proxy.py +0 -578
- package/src/superlocalmemory/learning/shadow_test.py +0 -524
- package/src/superlocalmemory/learning/signal_kinds.py +0 -79
- package/src/superlocalmemory/learning/signal_worker.py +0 -268
- package/src/superlocalmemory/learning/signals.py +0 -646
- package/src/superlocalmemory/learning/skill_performance_miner.py +0 -422
- package/src/superlocalmemory/learning/source_quality.py +0 -828
- package/src/superlocalmemory/learning/trigram_index.py +0 -548
- package/src/superlocalmemory/learning/workflows.py +0 -309
- package/src/superlocalmemory/llm/__init__.py +0 -0
- package/src/superlocalmemory/llm/backbone.py +0 -364
- package/src/superlocalmemory/loops/__init__.py +0 -58
- package/src/superlocalmemory/loops/budget.py +0 -58
- package/src/superlocalmemory/loops/engine.py +0 -174
- package/src/superlocalmemory/loops/ledger.py +0 -298
- package/src/superlocalmemory/loops/models.py +0 -152
- package/src/superlocalmemory/loops/rules.py +0 -52
- package/src/superlocalmemory/math/__init__.py +0 -0
- package/src/superlocalmemory/math/ebbinghaus.py +0 -352
- package/src/superlocalmemory/math/fisher.py +0 -356
- package/src/superlocalmemory/math/fisher_quantized.py +0 -255
- package/src/superlocalmemory/math/hopfield.py +0 -282
- package/src/superlocalmemory/math/langevin.py +0 -411
- package/src/superlocalmemory/math/polar_quant.py +0 -414
- package/src/superlocalmemory/math/qjl.py +0 -115
- package/src/superlocalmemory/math/sheaf.py +0 -261
- package/src/superlocalmemory/math/turbo_quant.py +0 -318
- package/src/superlocalmemory/mcp/__init__.py +0 -0
- package/src/superlocalmemory/mcp/_daemon_proxy.py +0 -203
- package/src/superlocalmemory/mcp/_pool_adapter.py +0 -181
- package/src/superlocalmemory/mcp/_stdin_guard.py +0 -60
- package/src/superlocalmemory/mcp/agent_context.py +0 -115
- package/src/superlocalmemory/mcp/cli_fallback.py +0 -602
- package/src/superlocalmemory/mcp/http_transport.py +0 -85
- package/src/superlocalmemory/mcp/profiles.py +0 -154
- package/src/superlocalmemory/mcp/resources.py +0 -281
- package/src/superlocalmemory/mcp/server.py +0 -480
- package/src/superlocalmemory/mcp/session_binding.py +0 -98
- package/src/superlocalmemory/mcp/shared.py +0 -112
- package/src/superlocalmemory/mcp/tools.py +0 -18
- package/src/superlocalmemory/mcp/tools_active.py +0 -958
- package/src/superlocalmemory/mcp/tools_brain.py +0 -298
- package/src/superlocalmemory/mcp/tools_code_graph.py +0 -1717
- package/src/superlocalmemory/mcp/tools_context.py +0 -239
- package/src/superlocalmemory/mcp/tools_core.py +0 -1112
- package/src/superlocalmemory/mcp/tools_evolution.py +0 -343
- package/src/superlocalmemory/mcp/tools_learning.py +0 -393
- package/src/superlocalmemory/mcp/tools_loops.py +0 -345
- package/src/superlocalmemory/mcp/tools_mesh.py +0 -429
- package/src/superlocalmemory/mcp/tools_ops.py +0 -115
- package/src/superlocalmemory/mcp/tools_optimize.py +0 -322
- package/src/superlocalmemory/mcp/tools_summaries.py +0 -147
- package/src/superlocalmemory/mcp/tools_v28.py +0 -292
- package/src/superlocalmemory/mcp/tools_v3.py +0 -398
- package/src/superlocalmemory/mcp/tools_v33.py +0 -507
- package/src/superlocalmemory/mesh/__init__.py +0 -12
- package/src/superlocalmemory/mesh/broker.py +0 -812
- package/src/superlocalmemory/mesh/broker_security.py +0 -470
- package/src/superlocalmemory/mesh/discovery.py +0 -365
- package/src/superlocalmemory/mesh/lock_protocol.py +0 -313
- package/src/superlocalmemory/mesh/node_identity.py +0 -97
- package/src/superlocalmemory/mesh/outbox_remote.py +0 -429
- package/src/superlocalmemory/mesh/remote_sync.py +0 -829
- package/src/superlocalmemory/mesh/state_sync.py +0 -286
- package/src/superlocalmemory/migrations/__init__.py +0 -5
- package/src/superlocalmemory/migrations/v3_4_25_to_v3_4_26.py +0 -144
- package/src/superlocalmemory/optimize/NOTICE +0 -6
- package/src/superlocalmemory/optimize/__init__.py +0 -0
- package/src/superlocalmemory/optimize/adapters/__init__.py +0 -68
- package/src/superlocalmemory/optimize/adapters/_agent_registry.py +0 -120
- package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +0 -112
- package/src/superlocalmemory/optimize/adapters/openai_adapter.py +0 -122
- package/src/superlocalmemory/optimize/adapters/wrap.py +0 -228
- package/src/superlocalmemory/optimize/cache/__init__.py +0 -31
- package/src/superlocalmemory/optimize/cache/boundary_store.py +0 -488
- package/src/superlocalmemory/optimize/cache/centroid_store.py +0 -199
- package/src/superlocalmemory/optimize/cache/context_key.py +0 -67
- package/src/superlocalmemory/optimize/cache/exact.py +0 -88
- package/src/superlocalmemory/optimize/cache/invalidation.py +0 -36
- package/src/superlocalmemory/optimize/cache/key_builder.py +0 -111
- package/src/superlocalmemory/optimize/cache/manager.py +0 -737
- package/src/superlocalmemory/optimize/cache/semantic.py +0 -637
- package/src/superlocalmemory/optimize/cache/stampede.py +0 -50
- package/src/superlocalmemory/optimize/compress/__init__.py +0 -17
- package/src/superlocalmemory/optimize/compress/align.py +0 -159
- package/src/superlocalmemory/optimize/compress/ccr.py +0 -116
- package/src/superlocalmemory/optimize/compress/prose_llmlingua.py +0 -71
- package/src/superlocalmemory/optimize/compress/router.py +0 -667
- package/src/superlocalmemory/optimize/config/__init__.py +0 -56
- package/src/superlocalmemory/optimize/config/defaults.py +0 -43
- package/src/superlocalmemory/optimize/config/schema.py +0 -327
- package/src/superlocalmemory/optimize/config/store.py +0 -270
- package/src/superlocalmemory/optimize/metrics/__init__.py +0 -8
- package/src/superlocalmemory/optimize/metrics/counters.py +0 -155
- package/src/superlocalmemory/optimize/metrics/estimator.py +0 -87
- package/src/superlocalmemory/optimize/metrics/exporters.py +0 -77
- package/src/superlocalmemory/optimize/metrics/persistence.py +0 -115
- package/src/superlocalmemory/optimize/proxy/__init__.py +0 -28
- package/src/superlocalmemory/optimize/proxy/_helpers.py +0 -730
- package/src/superlocalmemory/optimize/proxy/anthropic_surface.py +0 -375
- package/src/superlocalmemory/optimize/proxy/capture.py +0 -550
- package/src/superlocalmemory/optimize/proxy/gemini_surface.py +0 -528
- package/src/superlocalmemory/optimize/proxy/lifecycle.py +0 -126
- package/src/superlocalmemory/optimize/proxy/openai_surface.py +0 -465
- package/src/superlocalmemory/optimize/proxy/server.py +0 -199
- package/src/superlocalmemory/optimize/proxy/vertex_surface.py +0 -246
- package/src/superlocalmemory/optimize/storage/__init__.py +0 -0
- package/src/superlocalmemory/optimize/storage/db.py +0 -1185
- package/src/superlocalmemory/optimize/storage/schema.py +0 -205
- package/src/superlocalmemory/parameterization/__init__.py +0 -47
- package/src/superlocalmemory/parameterization/cross_project.py +0 -12
- package/src/superlocalmemory/parameterization/pattern_extractor.py +0 -584
- package/src/superlocalmemory/parameterization/pii_filter.py +0 -106
- package/src/superlocalmemory/parameterization/prompt_injector.py +0 -219
- package/src/superlocalmemory/parameterization/prompt_lifecycle.py +0 -281
- package/src/superlocalmemory/parameterization/soft_prompt_generator.py +0 -542
- package/src/superlocalmemory/parameterization/workflow_miner.py +0 -17
- package/src/superlocalmemory/reliability/__init__.py +0 -45
- package/src/superlocalmemory/reliability/join_liveness.py +0 -301
- package/src/superlocalmemory/reliability/prior_distance.py +0 -243
- package/src/superlocalmemory/retrieval/__init__.py +0 -0
- package/src/superlocalmemory/retrieval/agentic.py +0 -367
- package/src/superlocalmemory/retrieval/ann_index.py +0 -235
- package/src/superlocalmemory/retrieval/bm25_channel.py +0 -451
- package/src/superlocalmemory/retrieval/bridge_discovery.py +0 -253
- package/src/superlocalmemory/retrieval/channel_registry.py +0 -154
- package/src/superlocalmemory/retrieval/channel_status.py +0 -117
- package/src/superlocalmemory/retrieval/engine.py +0 -1615
- package/src/superlocalmemory/retrieval/entity_channel.py +0 -994
- package/src/superlocalmemory/retrieval/forgetting_filter.py +0 -160
- package/src/superlocalmemory/retrieval/fusion.py +0 -81
- package/src/superlocalmemory/retrieval/graph_adjacency.py +0 -219
- package/src/superlocalmemory/retrieval/hopfield_channel.py +0 -465
- package/src/superlocalmemory/retrieval/profile_channel.py +0 -105
- package/src/superlocalmemory/retrieval/quantization_aware_search.py +0 -147
- package/src/superlocalmemory/retrieval/remote_reranker.py +0 -758
- package/src/superlocalmemory/retrieval/reranker.py +0 -674
- package/src/superlocalmemory/retrieval/scope_policy.py +0 -126
- package/src/superlocalmemory/retrieval/semantic_channel.py +0 -638
- package/src/superlocalmemory/retrieval/spreading.py +0 -288
- package/src/superlocalmemory/retrieval/spreading_activation.py +0 -616
- package/src/superlocalmemory/retrieval/strategy.py +0 -248
- package/src/superlocalmemory/retrieval/temporal_channel.py +0 -433
- package/src/superlocalmemory/retrieval/temporal_frame.py +0 -102
- package/src/superlocalmemory/retrieval/temporal_utils.py +0 -122
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +0 -499
- package/src/superlocalmemory/retrieval/time_window.py +0 -181
- package/src/superlocalmemory/retrieval/vector_store.py +0 -863
- package/src/superlocalmemory/server/__init__.py +0 -1
- package/src/superlocalmemory/server/api.py +0 -310
- package/src/superlocalmemory/server/asset_versions.py +0 -171
- package/src/superlocalmemory/server/bandit_loops.py +0 -158
- package/src/superlocalmemory/server/config_file.py +0 -90
- package/src/superlocalmemory/server/consolidation_runner.py +0 -140
- package/src/superlocalmemory/server/egress_policy.py +0 -258
- package/src/superlocalmemory/server/loopback.py +0 -85
- package/src/superlocalmemory/server/middleware/__init__.py +0 -11
- package/src/superlocalmemory/server/middleware/security_headers.py +0 -144
- package/src/superlocalmemory/server/origin.py +0 -55
- package/src/superlocalmemory/server/profile_runtime.py +0 -515
- package/src/superlocalmemory/server/rbac_enforce.py +0 -194
- package/src/superlocalmemory/server/recall_health.py +0 -343
- package/src/superlocalmemory/server/recall_serializer.py +0 -320
- package/src/superlocalmemory/server/route_mutations.py +0 -104
- package/src/superlocalmemory/server/routes/__init__.py +0 -4
- package/src/superlocalmemory/server/routes/abstraction.py +0 -314
- package/src/superlocalmemory/server/routes/adapters.py +0 -63
- package/src/superlocalmemory/server/routes/agents.py +0 -303
- package/src/superlocalmemory/server/routes/backup.py +0 -869
- package/src/superlocalmemory/server/routes/behavioral.py +0 -659
- package/src/superlocalmemory/server/routes/brain.py +0 -1892
- package/src/superlocalmemory/server/routes/chat.py +0 -393
- package/src/superlocalmemory/server/routes/compliance.py +0 -533
- package/src/superlocalmemory/server/routes/config_api.py +0 -703
- package/src/superlocalmemory/server/routes/data_io.py +0 -329
- package/src/superlocalmemory/server/routes/entity.py +0 -237
- package/src/superlocalmemory/server/routes/events.py +0 -214
- package/src/superlocalmemory/server/routes/evolution.py +0 -510
- package/src/superlocalmemory/server/routes/helpers.py +0 -499
- package/src/superlocalmemory/server/routes/ingest.py +0 -137
- package/src/superlocalmemory/server/routes/insights.py +0 -366
- package/src/superlocalmemory/server/routes/learning.py +0 -834
- package/src/superlocalmemory/server/routes/learning_telemetry.py +0 -154
- package/src/superlocalmemory/server/routes/lifecycle.py +0 -184
- package/src/superlocalmemory/server/routes/memories.py +0 -1661
- package/src/superlocalmemory/server/routes/mesh.py +0 -517
- package/src/superlocalmemory/server/routes/mesh_lock.py +0 -54
- package/src/superlocalmemory/server/routes/mesh_state.py +0 -63
- package/src/superlocalmemory/server/routes/optimize.py +0 -197
- package/src/superlocalmemory/server/routes/prewarm.py +0 -173
- package/src/superlocalmemory/server/routes/profiles.py +0 -292
- package/src/superlocalmemory/server/routes/ratelimit.py +0 -132
- package/src/superlocalmemory/server/routes/rbac.py +0 -366
- package/src/superlocalmemory/server/routes/stats.py +0 -385
- package/src/superlocalmemory/server/routes/tiers.py +0 -222
- package/src/superlocalmemory/server/routes/timeline.py +0 -258
- package/src/superlocalmemory/server/routes/token.py +0 -90
- package/src/superlocalmemory/server/routes/v3_api.py +0 -3023
- package/src/superlocalmemory/server/routes/ws.py +0 -171
- package/src/superlocalmemory/server/security_middleware.py +0 -89
- package/src/superlocalmemory/server/ui.py +0 -354
- package/src/superlocalmemory/server/unified_daemon.py +0 -6326
- package/src/superlocalmemory/server/write_identity.py +0 -195
- package/src/superlocalmemory/storage/__init__.py +0 -0
- package/src/superlocalmemory/storage/_migration_internals.py +0 -638
- package/src/superlocalmemory/storage/_schema_version.py +0 -174
- package/src/superlocalmemory/storage/access_log.py +0 -170
- package/src/superlocalmemory/storage/admission_codec.py +0 -129
- package/src/superlocalmemory/storage/admission_journal.py +0 -843
- package/src/superlocalmemory/storage/agent_experience.py +0 -546
- package/src/superlocalmemory/storage/backup.py +0 -531
- package/src/superlocalmemory/storage/correction_cases.py +0 -670
- package/src/superlocalmemory/storage/database.py +0 -3180
- package/src/superlocalmemory/storage/deferred_writes.py +0 -209
- package/src/superlocalmemory/storage/embedding_codec.py +0 -200
- package/src/superlocalmemory/storage/embedding_migrator.py +0 -672
- package/src/superlocalmemory/storage/erasure_fence.py +0 -45
- package/src/superlocalmemory/storage/execution_learning.py +0 -285
- package/src/superlocalmemory/storage/external_evidence.py +0 -359
- package/src/superlocalmemory/storage/generation_fence.py +0 -63
- package/src/superlocalmemory/storage/lineage_retention.py +0 -236
- package/src/superlocalmemory/storage/logical_edges.py +0 -86
- package/src/superlocalmemory/storage/memory_write.py +0 -115
- package/src/superlocalmemory/storage/migration_runner.py +0 -895
- package/src/superlocalmemory/storage/migration_v33.py +0 -140
- package/src/superlocalmemory/storage/migrations/M001_add_signal_features_columns.py +0 -67
- package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +0 -107
- package/src/superlocalmemory/storage/migrations/M003_migration_log.py +0 -38
- package/src/superlocalmemory/storage/migrations/M004_cross_platform_sync_log.py +0 -46
- package/src/superlocalmemory/storage/migrations/M005_bandit_tables.py +0 -75
- package/src/superlocalmemory/storage/migrations/M006_action_outcomes_reward.py +0 -75
- package/src/superlocalmemory/storage/migrations/M007_pending_outcomes.py +0 -63
- package/src/superlocalmemory/storage/migrations/M009_model_lineage.py +0 -94
- package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +0 -80
- package/src/superlocalmemory/storage/migrations/M011_archive_and_merge.py +0 -87
- package/src/superlocalmemory/storage/migrations/M012_shadow_observations.py +0 -72
- package/src/superlocalmemory/storage/migrations/M013_bi_temporal_columns.py +0 -55
- package/src/superlocalmemory/storage/migrations/M014_v345_scale_ready.py +0 -45
- package/src/superlocalmemory/storage/migrations/M015_add_pinned_column.py +0 -58
- package/src/superlocalmemory/storage/migrations/M016_add_scope_support.py +0 -120
- package/src/superlocalmemory/storage/migrations/M017_ccq_scope_column.py +0 -79
- package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +0 -120
- package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +0 -54
- package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +0 -52
- package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +0 -108
- package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +0 -86
- package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +0 -194
- package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +0 -87
- package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +0 -90
- package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +0 -136
- package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +0 -163
- package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +0 -305
- package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +0 -137
- package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +0 -93
- package/src/superlocalmemory/storage/migrations/M031_dead_letter_operations.py +0 -80
- package/src/superlocalmemory/storage/migrations/M032_write_coordinator_admission.py +0 -188
- package/src/superlocalmemory/storage/migrations/M033_projection_transactions.py +0 -148
- package/src/superlocalmemory/storage/migrations/M034_obligation_integrity.py +0 -58
- package/src/superlocalmemory/storage/migrations/M035_erasure_receipts.py +0 -113
- package/src/superlocalmemory/storage/migrations/M036_vector_row_map.py +0 -107
- package/src/superlocalmemory/storage/migrations/M037_manifest_hmac_version.py +0 -162
- package/src/superlocalmemory/storage/migrations/M038_learning_feedback_channel.py +0 -77
- package/src/superlocalmemory/storage/migrations/M039_scene_fact_members.py +0 -137
- package/src/superlocalmemory/storage/migrations/M040_agent_experience_receipts.py +0 -254
- package/src/superlocalmemory/storage/migrations/M041_external_evidence_receipts.py +0 -189
- package/src/superlocalmemory/storage/migrations/M042_correction_case_ledger.py +0 -245
- package/src/superlocalmemory/storage/migrations/M043_quarantine_display_summaries.py +0 -512
- package/src/superlocalmemory/storage/migrations/M044_play_carries_its_own_evidence.py +0 -127
- package/src/superlocalmemory/storage/migrations/M045_fact_outcome_score.py +0 -158
- package/src/superlocalmemory/storage/migrations/M046_prospective_memory_has_its_own_name.py +0 -620
- package/src/superlocalmemory/storage/migrations/M047_fisher_vectors_are_stored_like_every_other_vector.py +0 -306
- package/src/superlocalmemory/storage/migrations/M048_upcoming_holds_only_what_is_upcoming.py +0 -229
- package/src/superlocalmemory/storage/migrations/M049_a_schema_version_marker_is_one_row.py +0 -201
- package/src/superlocalmemory/storage/migrations/M050_execution_learning_v2.py +0 -70
- package/src/superlocalmemory/storage/migrations/__init__.py +0 -103
- package/src/superlocalmemory/storage/migrations.py +0 -333
- package/src/superlocalmemory/storage/models.py +0 -500
- package/src/superlocalmemory/storage/projection_outbox.py +0 -346
- package/src/superlocalmemory/storage/quantized_store.py +0 -280
- package/src/superlocalmemory/storage/read_connection.py +0 -115
- package/src/superlocalmemory/storage/retention_policy.py +0 -860
- package/src/superlocalmemory/storage/schema.py +0 -1108
- package/src/superlocalmemory/storage/schema_code_graph.py +0 -282
- package/src/superlocalmemory/storage/schema_v32.py +0 -382
- package/src/superlocalmemory/storage/schema_v3410.py +0 -159
- package/src/superlocalmemory/storage/schema_v3411.py +0 -149
- package/src/superlocalmemory/storage/schema_v343.py +0 -315
- package/src/superlocalmemory/storage/schema_v345.py +0 -109
- package/src/superlocalmemory/storage/schema_v347.py +0 -140
- package/src/superlocalmemory/storage/sqlite_vectors.py +0 -169
- package/src/superlocalmemory/storage/v2_migrator.py +0 -466
- package/src/superlocalmemory/storage/write_coordinator.py +0 -949
- package/src/superlocalmemory/storage/write_lock.py +0 -88
- package/src/superlocalmemory/summaries/__init__.py +0 -37
- package/src/superlocalmemory/summaries/base.py +0 -267
- package/src/superlocalmemory/summaries/daily_reflection.py +0 -340
- package/src/superlocalmemory/summaries/non_answer.py +0 -223
- package/src/superlocalmemory/summaries/project_work_log.py +0 -440
- package/src/superlocalmemory/summaries/session_summary.py +0 -311
- package/src/superlocalmemory/trust/__init__.py +0 -0
- package/src/superlocalmemory/trust/gate.py +0 -171
- package/src/superlocalmemory/trust/provenance.py +0 -124
- package/src/superlocalmemory/trust/scorer.py +0 -413
- package/src/superlocalmemory/trust/signals.py +0 -153
- package/src/superlocalmemory/ui/assets/slm-icon-white.svg +0 -64
- package/src/superlocalmemory/ui/assets/slm-icon.svg +0 -36
- package/src/superlocalmemory/ui/css/brain.css +0 -409
- package/src/superlocalmemory/ui/css/design-system.css +0 -696
- package/src/superlocalmemory/ui/css/legacy-dashboard.css +0 -663
- package/src/superlocalmemory/ui/css/neural-glass.css +0 -1599
- package/src/superlocalmemory/ui/css/od-bridge.css +0 -158
- package/src/superlocalmemory/ui/favicon.svg +0 -36
- package/src/superlocalmemory/ui/index.html +0 -1638
- package/src/superlocalmemory/ui/js/agents.js +0 -192
- package/src/superlocalmemory/ui/js/auto-settings.js +0 -624
- package/src/superlocalmemory/ui/js/brain.js +0 -1400
- package/src/superlocalmemory/ui/js/clusters.js +0 -326
- package/src/superlocalmemory/ui/js/compliance.js +0 -307
- package/src/superlocalmemory/ui/js/core.js +0 -566
- package/src/superlocalmemory/ui/js/dashboard.js +0 -503
- package/src/superlocalmemory/ui/js/event-delegation.js +0 -113
- package/src/superlocalmemory/ui/js/events.js +0 -178
- package/src/superlocalmemory/ui/js/fact-detail.js +0 -142
- package/src/superlocalmemory/ui/js/feedback.js +0 -339
- package/src/superlocalmemory/ui/js/graph-event-bus.js +0 -83
- package/src/superlocalmemory/ui/js/graph-filters.js +0 -220
- package/src/superlocalmemory/ui/js/graph-ui.js +0 -214
- package/src/superlocalmemory/ui/js/ide-status.js +0 -115
- package/src/superlocalmemory/ui/js/init.js +0 -54
- package/src/superlocalmemory/ui/js/knowledge-graph.js +0 -945
- package/src/superlocalmemory/ui/js/lifecycle.js +0 -387
- package/src/superlocalmemory/ui/js/math-health.js +0 -114
- package/src/superlocalmemory/ui/js/memories.js +0 -394
- package/src/superlocalmemory/ui/js/memory-chat.js +0 -371
- package/src/superlocalmemory/ui/js/memory-timeline.js +0 -265
- package/src/superlocalmemory/ui/js/modal.js +0 -733
- package/src/superlocalmemory/ui/js/ng-entities.js +0 -298
- package/src/superlocalmemory/ui/js/ng-health.js +0 -208
- package/src/superlocalmemory/ui/js/ng-ingestion.js +0 -203
- package/src/superlocalmemory/ui/js/ng-mesh.js +0 -374
- package/src/superlocalmemory/ui/js/ng-shell.js +0 -524
- package/src/superlocalmemory/ui/js/ng-skills.js +0 -663
- package/src/superlocalmemory/ui/js/od-agents.js +0 -588
- package/src/superlocalmemory/ui/js/od-auth-gate.js +0 -257
- package/src/superlocalmemory/ui/js/od-backup.js +0 -878
- package/src/superlocalmemory/ui/js/od-boundedloops.js +0 -324
- package/src/superlocalmemory/ui/js/od-brain.js +0 -1095
- package/src/superlocalmemory/ui/js/od-compliance-ext.js +0 -301
- package/src/superlocalmemory/ui/js/od-components.js +0 -147
- package/src/superlocalmemory/ui/js/od-entities.js +0 -622
- package/src/superlocalmemory/ui/js/od-graph.js +0 -776
- package/src/superlocalmemory/ui/js/od-health.js +0 -579
- package/src/superlocalmemory/ui/js/od-mcp.js +0 -508
- package/src/superlocalmemory/ui/js/od-memories.js +0 -1499
- package/src/superlocalmemory/ui/js/od-mesh.js +0 -645
- package/src/superlocalmemory/ui/js/od-operations.js +0 -1268
- package/src/superlocalmemory/ui/js/od-ops-health.js +0 -417
- package/src/superlocalmemory/ui/js/od-optimize.js +0 -828
- package/src/superlocalmemory/ui/js/od-settings.js +0 -1275
- package/src/superlocalmemory/ui/js/od-shell.js +0 -819
- package/src/superlocalmemory/ui/js/od-skills.js +0 -600
- package/src/superlocalmemory/ui/js/od-team.js +0 -265
- package/src/superlocalmemory/ui/js/optimize.js +0 -191
- package/src/superlocalmemory/ui/js/profiles.js +0 -362
- package/src/superlocalmemory/ui/js/quick-actions.js +0 -334
- package/src/superlocalmemory/ui/js/recall-lab.js +0 -373
- package/src/superlocalmemory/ui/js/search.js +0 -86
- package/src/superlocalmemory/ui/js/settings.js +0 -556
- package/src/superlocalmemory/ui/js/timeline.js +0 -62
- package/src/superlocalmemory/ui/js/trust-dashboard.js +0 -225
- package/src/superlocalmemory/ui/vendor/bootstrap-icons/bootstrap-icons.css +0 -2018
- package/src/superlocalmemory/ui/vendor/bootstrap-icons/fonts/bootstrap-icons.woff +0 -0
- package/src/superlocalmemory/ui/vendor/bootstrap-icons/fonts/bootstrap-icons.woff2 +0 -0
- package/src/superlocalmemory/ui/vendor/bootstrap.bundle.min.js +0 -7
- package/src/superlocalmemory/ui/vendor/bootstrap.min.css +0 -6
- package/src/superlocalmemory/ui/vendor/d3.v7.min.js +0 -2
- package/src/superlocalmemory/ui/vendor/graphology-library.min.js +0 -2
- package/src/superlocalmemory/ui/vendor/graphology.umd.min.js +0 -2
- package/src/superlocalmemory/ui/vendor/inter-ui/inter-variable.min.css +0 -8
- package/src/superlocalmemory/ui/vendor/inter-ui/variable/InterVariable-Italic.woff2 +0 -0
- package/src/superlocalmemory/ui/vendor/inter-ui/variable/InterVariable.woff2 +0 -0
- package/src/superlocalmemory/ui/vendor/sigma.min.js +0 -1
- package/src/superlocalmemory/vector/__init__.py +0 -9
- package/src/superlocalmemory/vector/lancedb_backend.py +0 -366
|
@@ -1,1395 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
-
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
-
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
-
|
|
5
|
-
"""SuperLocalMemory V3 — Main Memory Engine (Facade).
|
|
6
|
-
|
|
7
|
-
Thin orchestrator that delegates to extracted pipeline modules:
|
|
8
|
-
- store_pipeline (store, store_fact_direct, close_session, enrich_fact)
|
|
9
|
-
- recall_pipeline (recall, adaptive ranking)
|
|
10
|
-
- engine_wiring (embedder init, encoding init, retrieval init, hooks)
|
|
11
|
-
|
|
12
|
-
Single entry point for all memory operations.
|
|
13
|
-
Profile-scoped. Mode-aware (A/B/C).
|
|
14
|
-
|
|
15
|
-
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
from __future__ import annotations
|
|
19
|
-
|
|
20
|
-
import logging
|
|
21
|
-
import os
|
|
22
|
-
import threading
|
|
23
|
-
import time
|
|
24
|
-
from pathlib import Path
|
|
25
|
-
from typing import Any
|
|
26
|
-
|
|
27
|
-
from superlocalmemory.core.session_identity import synthetic_session_id
|
|
28
|
-
from superlocalmemory.core.config import CANONICAL_RECALL_LIMIT, SLMConfig
|
|
29
|
-
from superlocalmemory.core.engine_capabilities import Capabilities, CapabilityError
|
|
30
|
-
from superlocalmemory.core.modes import get_capabilities
|
|
31
|
-
from superlocalmemory.storage.models import (
|
|
32
|
-
AtomicFact, FactType, MemoryRecord, Mode, RecallResponse,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
logger = logging.getLogger(__name__)
|
|
36
|
-
|
|
37
|
-
from superlocalmemory.core.hooks import HookRegistry
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _verify_ingestion_schema(memory_db: Path) -> bool:
|
|
41
|
-
"""Verify M018 using an explicitly owned SQLite connection."""
|
|
42
|
-
import sqlite3
|
|
43
|
-
|
|
44
|
-
from superlocalmemory.storage.migrations import M018_ingestion_operations
|
|
45
|
-
|
|
46
|
-
connection = sqlite3.connect(str(memory_db))
|
|
47
|
-
try:
|
|
48
|
-
return bool(M018_ingestion_operations.verify(connection))
|
|
49
|
-
finally:
|
|
50
|
-
connection.close()
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
# ---------------------------------------------------------------------------
|
|
54
|
-
# Embedding a memory on the write path, before the receipt is returned
|
|
55
|
-
# ---------------------------------------------------------------------------
|
|
56
|
-
|
|
57
|
-
def _embedder_is_warm(embedder: object) -> bool:
|
|
58
|
-
"""Has this embedder already served a request, so the next one is cheap?
|
|
59
|
-
|
|
60
|
-
Availability and readiness are different questions and the write path needs
|
|
61
|
-
the second one. ``EmbeddingService._available`` is set to ``True`` in its
|
|
62
|
-
constructor, before the worker subprocess exists; the model behind it takes
|
|
63
|
-
**9.9-11.0 s** to load on this machine against **42 ms** once loaded. A write
|
|
64
|
-
that embeds inline does so behind a one-second deadline, so keying off
|
|
65
|
-
availability meant every write until something else warmed the model paid the
|
|
66
|
-
whole deadline and then stored no vector anyway -- 10 of the first 12,
|
|
67
|
-
measured. The deadline was spent proving the model was cold.
|
|
68
|
-
|
|
69
|
-
``EmbeddingService.is_warm`` answers the real question: a live worker that has
|
|
70
|
-
served at least one request. So both must hold, and availability is still
|
|
71
|
-
checked first: an embedder that reports itself unavailable is not a candidate
|
|
72
|
-
whatever else it says. Requiring only ``is_warm`` would call ``embed`` on a
|
|
73
|
-
dead embedder that happens to expose the attribute -- any mock, proxy or
|
|
74
|
-
duck-typed wrapper auto-creates one.
|
|
75
|
-
|
|
76
|
-
An embedder that offers no ``is_warm`` keeps availability as the whole answer:
|
|
77
|
-
``OllamaEmbedder`` talks to a server that is already running and has no worker
|
|
78
|
-
of its own to start, so there readiness is availability.
|
|
79
|
-
"""
|
|
80
|
-
if getattr(embedder, "_available", None) is not True:
|
|
81
|
-
return False
|
|
82
|
-
warm = getattr(embedder, "is_warm", None)
|
|
83
|
-
return True if warm is None else bool(warm)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def _is_remote_embedder(embedder: object) -> bool:
|
|
87
|
-
"""Return True if *embedder* makes remote HTTP calls (cloud / OpenAI-compatible).
|
|
88
|
-
|
|
89
|
-
Remote embedders (100–400ms round-trip) must never block the store_fast()
|
|
90
|
-
write path synchronously — they stay on the async materializer path.
|
|
91
|
-
|
|
92
|
-
Local embedders:
|
|
93
|
-
- EmbeddingService (subprocess, local ONNX/sentence-transformers): has
|
|
94
|
-
``_config`` with ``is_cloud=False`` and ``is_openai_compatible=False``.
|
|
95
|
-
- OllamaEmbedder (localhost HTTP, ~73ms): has NO ``_config`` attribute.
|
|
96
|
-
|
|
97
|
-
Remote embedders:
|
|
98
|
-
- EmbeddingService with ``_config.is_cloud=True`` (Azure, etc.)
|
|
99
|
-
- EmbeddingService with ``_config.is_openai_compatible=True``
|
|
100
|
-
"""
|
|
101
|
-
cfg = getattr(embedder, "_config", None)
|
|
102
|
-
if cfg is None:
|
|
103
|
-
return False # OllamaEmbedder — local
|
|
104
|
-
return bool(
|
|
105
|
-
getattr(cfg, "is_cloud", False)
|
|
106
|
-
or getattr(cfg, "is_openai_compatible", False)
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
class MemoryEngine:
|
|
111
|
-
"""Main orchestrator for the SuperLocalMemory V3 memory system.
|
|
112
|
-
|
|
113
|
-
Wires encoding (fact extraction, entity resolution, graph building,
|
|
114
|
-
consolidation) with retrieval (five candidate producers -> RRF fusion,
|
|
115
|
-
reranking, entity-graph post-fusion enhancement) and all supporting
|
|
116
|
-
layers (trust, learning, compliance).
|
|
117
|
-
|
|
118
|
-
Usage::
|
|
119
|
-
|
|
120
|
-
config = SLMConfig.for_mode(Mode.A)
|
|
121
|
-
engine = MemoryEngine(config)
|
|
122
|
-
engine.store("Alice went to Paris last summer", session_id="s1")
|
|
123
|
-
response = engine.recall("Where did Alice go?")
|
|
124
|
-
"""
|
|
125
|
-
|
|
126
|
-
def __init__(
|
|
127
|
-
self,
|
|
128
|
-
config: SLMConfig,
|
|
129
|
-
capabilities: Capabilities = Capabilities.FULL,
|
|
130
|
-
) -> None:
|
|
131
|
-
self._config = config
|
|
132
|
-
self._caps = get_capabilities(config.mode)
|
|
133
|
-
self._capabilities = capabilities
|
|
134
|
-
self._profile_id = config.active_profile
|
|
135
|
-
# The session a recall belongs to, for matching an outcome back to it
|
|
136
|
-
# later. Set from whatever the caller last named; falls back to a stable
|
|
137
|
-
# per-process id so a caller that never names one still leaves a record
|
|
138
|
-
# that can be matched to another call from the same process. An empty
|
|
139
|
-
# one is dropped by the queue, which is how 34 of 35 recall paths came
|
|
140
|
-
# to leave no record at all.
|
|
141
|
-
self._last_session_id: str = ""
|
|
142
|
-
# Minted through the shared helper so this id is registered as
|
|
143
|
-
# invented rather than received; continuity and the reward pipeline
|
|
144
|
-
# both ask session_identity, and a hand-built prefix is invisible to it.
|
|
145
|
-
self._ambient_session_id: str = synthetic_session_id(
|
|
146
|
-
"engine", str(os.getpid()),
|
|
147
|
-
)
|
|
148
|
-
self._initialized = False
|
|
149
|
-
|
|
150
|
-
self._db = None
|
|
151
|
-
self._embedder = None
|
|
152
|
-
self._llm = None
|
|
153
|
-
self._fact_extractor = None
|
|
154
|
-
self._entity_resolver = None
|
|
155
|
-
self._temporal_parser = None
|
|
156
|
-
self._type_router = None
|
|
157
|
-
self._graph_builder = None
|
|
158
|
-
self._consolidator = None
|
|
159
|
-
self._observation_builder = None
|
|
160
|
-
self._scene_builder = None
|
|
161
|
-
self._entropy_gate = None
|
|
162
|
-
self._retrieval_engine = None
|
|
163
|
-
self._trust_scorer = None
|
|
164
|
-
self._ann_index = None
|
|
165
|
-
self._sheaf_checker = None
|
|
166
|
-
self._provenance = None
|
|
167
|
-
self._adaptive_learner = None
|
|
168
|
-
self._compliance_checker = None
|
|
169
|
-
self._vector_store = None
|
|
170
|
-
self._access_log = None
|
|
171
|
-
self._context_generator = None
|
|
172
|
-
self._temporal_validator = None
|
|
173
|
-
self._auto_invoker = None
|
|
174
|
-
self._auto_linker = None
|
|
175
|
-
self._graph_analyzer = None
|
|
176
|
-
self._consolidation_engine = None
|
|
177
|
-
self._maintenance_scheduler = None
|
|
178
|
-
self._hooks = HookRegistry()
|
|
179
|
-
# One single-worker pool reused across store_fast() calls, created on the
|
|
180
|
-
# first warm-guard attempt. Reusing it avoids a thread per call.
|
|
181
|
-
self._store_fast_embed_pool: object | None = None
|
|
182
|
-
# Guards the lazy creation so concurrent first calls cannot each build one.
|
|
183
|
-
self._store_fast_embed_pool_lock = threading.Lock()
|
|
184
|
-
# Set once the engine is closed, and never cleared. A caller already
|
|
185
|
-
# inside the warm-guard path when close() runs will reach the creation
|
|
186
|
-
# check afterwards and find None; without this it would build a
|
|
187
|
-
# replacement pool that nothing owns and nothing will shut down, so a
|
|
188
|
-
# burst of writes arriving during shutdown leaks a thread each and holds
|
|
189
|
-
# the process open past its shutdown budget.
|
|
190
|
-
self._store_fast_embed_pool_closed = False
|
|
191
|
-
|
|
192
|
-
# -- Public properties (Phase 2+ access) --------------------------------
|
|
193
|
-
|
|
194
|
-
@property
|
|
195
|
-
def db(self):
|
|
196
|
-
"""Database manager (read-only access for Phase 2+)."""
|
|
197
|
-
return self._db
|
|
198
|
-
|
|
199
|
-
@property
|
|
200
|
-
def trust_scorer(self):
|
|
201
|
-
"""Trust scorer (read-only access for Phase 2+)."""
|
|
202
|
-
return self._trust_scorer
|
|
203
|
-
|
|
204
|
-
@property
|
|
205
|
-
def embedder(self):
|
|
206
|
-
"""Embedding service (read-only access for Phase 2+)."""
|
|
207
|
-
return self._embedder
|
|
208
|
-
|
|
209
|
-
@property
|
|
210
|
-
def capabilities(self) -> Capabilities:
|
|
211
|
-
"""Capability level chosen at construction (LIGHT or FULL)."""
|
|
212
|
-
return self._capabilities
|
|
213
|
-
|
|
214
|
-
# -- Initialization -----------------------------------------------------
|
|
215
|
-
|
|
216
|
-
def initialize(self) -> None:
|
|
217
|
-
"""Initialize all components. Call once before use.
|
|
218
|
-
|
|
219
|
-
In LIGHT mode only the DB layer is initialized (SQLite + schema
|
|
220
|
-
migrations + profile bookkeeping). In FULL mode the heavy layer
|
|
221
|
-
(embedder, LLM, encoding, retrieval, hooks, consolidation) follows.
|
|
222
|
-
"""
|
|
223
|
-
if self._initialized:
|
|
224
|
-
return
|
|
225
|
-
|
|
226
|
-
self._init_db_layer()
|
|
227
|
-
|
|
228
|
-
if self._capabilities is Capabilities.FULL:
|
|
229
|
-
self._init_heavy_layer()
|
|
230
|
-
else:
|
|
231
|
-
# V3.5.9: LIGHT mode — try to get embedder from running daemon so that
|
|
232
|
-
# memories stored via MCP have real embeddings (fixes PR #30 NULL embedder).
|
|
233
|
-
self._try_init_proxy()
|
|
234
|
-
|
|
235
|
-
self._initialized = True
|
|
236
|
-
logger.info(
|
|
237
|
-
"MemoryEngine initialized: mode=%s profile=%s capabilities=%s",
|
|
238
|
-
self._config.mode.value, self._profile_id,
|
|
239
|
-
self._capabilities.value,
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
if self._capabilities is Capabilities.FULL:
|
|
243
|
-
# Replay pending async writes only when heavy layer is available.
|
|
244
|
-
self._process_pending_memories()
|
|
245
|
-
|
|
246
|
-
def _init_db_layer(self) -> None:
|
|
247
|
-
from superlocalmemory.storage import schema
|
|
248
|
-
from superlocalmemory.storage.database import DatabaseManager
|
|
249
|
-
|
|
250
|
-
self._db = DatabaseManager(self._config.db_path)
|
|
251
|
-
self._db.initialize(schema)
|
|
252
|
-
|
|
253
|
-
# V3.4.3: Apply "Unified Brain" schema extensions (mesh, entity compilation, ingestion)
|
|
254
|
-
# Idempotent — safe to call on every init. Skips if already applied.
|
|
255
|
-
try:
|
|
256
|
-
from superlocalmemory.storage.schema_v343 import apply_v343_schema
|
|
257
|
-
apply_v343_schema(str(self._db.db_path))
|
|
258
|
-
except Exception as exc:
|
|
259
|
-
logger.warning("V3.4.3 schema migration failed: %s", exc)
|
|
260
|
-
|
|
261
|
-
# V3.4.6: Apply "Connected Brain" mesh enhancements (broadcast, project routing, offline queue)
|
|
262
|
-
try:
|
|
263
|
-
from superlocalmemory.storage.schema_v343 import apply_v346_schema
|
|
264
|
-
apply_v346_schema(str(self._db.db_path))
|
|
265
|
-
except Exception as exc:
|
|
266
|
-
logger.warning("V3.4.6 schema migration failed: %s", exc)
|
|
267
|
-
|
|
268
|
-
# V3.4.7: Apply "Learning Brain" schema before deferred migrations.
|
|
269
|
-
# M029 adds composite history indexes to these runtime tables, so the
|
|
270
|
-
# tables must exist before the migration runner records M029 complete.
|
|
271
|
-
try:
|
|
272
|
-
from superlocalmemory.storage.schema_v347 import apply_v347_schema
|
|
273
|
-
apply_v347_schema(str(self._db.db_path))
|
|
274
|
-
except Exception as exc:
|
|
275
|
-
logger.warning("V3.4.7 schema migration failed: %s", exc)
|
|
276
|
-
|
|
277
|
-
# v3.6.15: apply ALL pending migrations — including DEFERRED ones like
|
|
278
|
-
# M016 (scope/shared_with columns) — for DIRECT-engine usage: `slm
|
|
279
|
-
# remember --sync`, the Python API, and LangChain/CrewAI integrations.
|
|
280
|
-
# Previously only the daemon lifespan ran apply_deferred, so an existing
|
|
281
|
-
# pre-3.6.15 database used WITHOUT the daemon hit
|
|
282
|
-
# "table memories has no column named scope" on the first scoped write.
|
|
283
|
-
# Idempotent (skips applied migrations). M018 is now a hard runtime
|
|
284
|
-
# prerequisite: allowing initialization to continue without it would
|
|
285
|
-
# make every advertised write path fail after startup.
|
|
286
|
-
try:
|
|
287
|
-
from superlocalmemory.storage.migration_runner import (
|
|
288
|
-
apply_all, apply_deferred,
|
|
289
|
-
)
|
|
290
|
-
_base = self._config.base_dir
|
|
291
|
-
_learning_db = _base / "learning.db"
|
|
292
|
-
_memory_db = self._db.db_path
|
|
293
|
-
# Forward migrations extend the historical learning schema. A
|
|
294
|
-
# fresh explicit data root has no learning.db yet, so bootstrap
|
|
295
|
-
# those base tables before applying M001/M002/M009.
|
|
296
|
-
from superlocalmemory.learning.database import LearningDatabase
|
|
297
|
-
LearningDatabase(_learning_db)
|
|
298
|
-
apply_all(_learning_db, _memory_db)
|
|
299
|
-
apply_deferred(_learning_db, _memory_db)
|
|
300
|
-
if not _verify_ingestion_schema(_memory_db):
|
|
301
|
-
raise RuntimeError(
|
|
302
|
-
"required M018 canonical-ingestion schema is unavailable"
|
|
303
|
-
)
|
|
304
|
-
except Exception as exc:
|
|
305
|
-
raise RuntimeError(
|
|
306
|
-
"MemoryEngine initialization stopped because the required "
|
|
307
|
-
f"ingestion migration failed: {exc}"
|
|
308
|
-
) from exc
|
|
309
|
-
|
|
310
|
-
# V3.4.10: Apply "Fortress" schema (backup_destinations, entity_blacklist)
|
|
311
|
-
try:
|
|
312
|
-
from superlocalmemory.storage.schema_v3410 import apply_v3410_schema
|
|
313
|
-
apply_v3410_schema(str(self._db.db_path))
|
|
314
|
-
except Exception as exc:
|
|
315
|
-
logger.warning("V3.4.10 schema migration failed: %s", exc)
|
|
316
|
-
|
|
317
|
-
# V3.4.11: Apply "Scale-Ready" schema (pinned_facts, backend_status, fact_consolidations)
|
|
318
|
-
try:
|
|
319
|
-
from superlocalmemory.storage.schema_v3411 import apply_v3411_schema
|
|
320
|
-
apply_v3411_schema(str(self._db.db_path))
|
|
321
|
-
except Exception as exc:
|
|
322
|
-
logger.warning("V3.4.11 schema migration failed: %s", exc)
|
|
323
|
-
|
|
324
|
-
# DB-only learner — no embedder / LLM dependency. Available in
|
|
325
|
-
# LIGHT so MCP report_feedback and session_init phase counters
|
|
326
|
-
# work on the MCP process without loading the heavy layer.
|
|
327
|
-
from superlocalmemory.learning.adaptive import AdaptiveLearner
|
|
328
|
-
self._adaptive_learner = AdaptiveLearner(self._db)
|
|
329
|
-
|
|
330
|
-
def _try_init_proxy(self) -> None:
|
|
331
|
-
"""V3.5.9: Attach McpEmbedderProxy when running in LIGHT mode.
|
|
332
|
-
|
|
333
|
-
If the daemon is reachable, the proxy delegates embed calls over HTTP
|
|
334
|
-
so that MCP-stored facts have real embeddings (fixes PR #30). Silently
|
|
335
|
-
skips if the daemon is not running — keyword recall still works.
|
|
336
|
-
"""
|
|
337
|
-
try:
|
|
338
|
-
from superlocalmemory.core.mcp_embedder_proxy import McpEmbedderProxy
|
|
339
|
-
port = getattr(self._config, "daemon_port", 8765)
|
|
340
|
-
proxy = McpEmbedderProxy(port=port)
|
|
341
|
-
if proxy.is_available():
|
|
342
|
-
self._embedder = proxy
|
|
343
|
-
logger.info("MCP embedder proxy attached (daemon port %d)", port)
|
|
344
|
-
else:
|
|
345
|
-
logger.debug("Daemon not reachable — MCP will run without embedder")
|
|
346
|
-
except Exception as exc:
|
|
347
|
-
logger.debug("McpEmbedderProxy init skipped: %s", exc)
|
|
348
|
-
|
|
349
|
-
def _init_heavy_layer(self) -> None:
|
|
350
|
-
from superlocalmemory.llm.backbone import LLMBackbone
|
|
351
|
-
from superlocalmemory.core.engine_wiring import (
|
|
352
|
-
init_embedder, init_encoding, init_retrieval, wire_hooks,
|
|
353
|
-
_init_auto_invoker, _init_consolidation,
|
|
354
|
-
)
|
|
355
|
-
|
|
356
|
-
self._embedder = init_embedder(self._config)
|
|
357
|
-
|
|
358
|
-
# Rebuild the complete vector projection before VectorStore opens it.
|
|
359
|
-
# This preserves the previous vec0 table until the shadow activation
|
|
360
|
-
# succeeds, including across embedding-dimension changes.
|
|
361
|
-
self._check_embedding_migration()
|
|
362
|
-
|
|
363
|
-
if self._caps.llm_fact_extraction:
|
|
364
|
-
self._llm = LLMBackbone(self._config.llm)
|
|
365
|
-
if not self._llm.is_available():
|
|
366
|
-
logger.warning(
|
|
367
|
-
"LLM not available. Falling back to Mode A extraction.",
|
|
368
|
-
)
|
|
369
|
-
self._llm = None
|
|
370
|
-
|
|
371
|
-
# H-03 (3.7.9): surface mode-capability degradation explicitly at
|
|
372
|
-
# startup. validate_mode_config existed but was never called, so Mode B
|
|
373
|
-
# silently using rule-based extraction after Ollama vanished (update,
|
|
374
|
-
# restart, port conflict) went unwarned. Each mode only checks its own
|
|
375
|
-
# capabilities, so passing the single llm-availability signal is safe.
|
|
376
|
-
from superlocalmemory.core.modes import validate_mode_config
|
|
377
|
-
_llm_up = getattr(self, "_llm", None) is not None
|
|
378
|
-
for _warning in validate_mode_config(
|
|
379
|
-
self._config.mode, has_ollama=_llm_up, has_cloud_llm=_llm_up,
|
|
380
|
-
):
|
|
381
|
-
logger.warning("Mode config: %s", _warning)
|
|
382
|
-
|
|
383
|
-
from superlocalmemory.trust.scorer import TrustScorer
|
|
384
|
-
from superlocalmemory.trust.provenance import ProvenanceTracker
|
|
385
|
-
from superlocalmemory.compliance.eu_ai_act import EUAIActChecker
|
|
386
|
-
|
|
387
|
-
self._trust_scorer = TrustScorer(self._db)
|
|
388
|
-
|
|
389
|
-
enc = init_encoding(
|
|
390
|
-
self._config, self._db, self._embedder, self._llm,
|
|
391
|
-
)
|
|
392
|
-
self._ann_index = enc["ann_index"]
|
|
393
|
-
self._fact_extractor = enc["fact_extractor"]
|
|
394
|
-
self._entity_resolver = enc["entity_resolver"]
|
|
395
|
-
self._temporal_parser = enc["temporal_parser"]
|
|
396
|
-
self._type_router = enc["type_router"]
|
|
397
|
-
self._graph_builder = enc["graph_builder"]
|
|
398
|
-
self._consolidator = enc["consolidator"]
|
|
399
|
-
self._observation_builder = enc["observation_builder"]
|
|
400
|
-
self._scene_builder = enc["scene_builder"]
|
|
401
|
-
self._entropy_gate = enc["entropy_gate"]
|
|
402
|
-
self._sheaf_checker = enc["sheaf_checker"]
|
|
403
|
-
self._vector_store = enc.get("vector_store")
|
|
404
|
-
self._access_log = enc.get("access_log")
|
|
405
|
-
self._context_generator = enc.get("context_generator")
|
|
406
|
-
self._temporal_validator = enc.get("temporal_validator")
|
|
407
|
-
self._auto_linker = enc.get("auto_linker")
|
|
408
|
-
self._graph_analyzer = enc.get("graph_analyzer")
|
|
409
|
-
|
|
410
|
-
self._retrieval_engine = init_retrieval(
|
|
411
|
-
self._config, self._db, self._embedder,
|
|
412
|
-
self._entity_resolver, self._trust_scorer,
|
|
413
|
-
vector_store=self._vector_store,
|
|
414
|
-
)
|
|
415
|
-
|
|
416
|
-
self._provenance = ProvenanceTracker(self._db)
|
|
417
|
-
# self._adaptive_learner is initialized in _init_db_layer() because
|
|
418
|
-
# it depends only on the DB (no embedder / LLM); see note there.
|
|
419
|
-
self._compliance_checker = EUAIActChecker()
|
|
420
|
-
|
|
421
|
-
hook_result = wire_hooks(
|
|
422
|
-
self._hooks, self._config, self._db,
|
|
423
|
-
self._trust_scorer, self._profile_id,
|
|
424
|
-
)
|
|
425
|
-
self._signal_recorder = hook_result["signal_recorder"]
|
|
426
|
-
self._audit_chain = hook_result["audit_chain"]
|
|
427
|
-
|
|
428
|
-
# V3.2: AutoInvoker (Phase 2) -- multi-signal auto-recall
|
|
429
|
-
self._auto_invoker = _init_auto_invoker(
|
|
430
|
-
self._config, self._db, self._vector_store,
|
|
431
|
-
self._trust_scorer, self._embedder,
|
|
432
|
-
)
|
|
433
|
-
|
|
434
|
-
# V3.2: ConsolidationEngine (Phase 5) -- sleep-time consolidation
|
|
435
|
-
from superlocalmemory.core.summarizer import Summarizer
|
|
436
|
-
summarizer = Summarizer(self._config)
|
|
437
|
-
# P1-5 (core-promotion-01): wire a real behavioral store. Previously
|
|
438
|
-
# hardcoded None → _compile_behavioral_block always returned the
|
|
439
|
-
# "No behavioral patterns detected yet." placeholder, so behavioral
|
|
440
|
-
# patterns never reached the always-injected core block (dead feature).
|
|
441
|
-
from superlocalmemory.core.recall_pipeline import _get_behavioral_tracker
|
|
442
|
-
self._consolidation_engine = _init_consolidation(
|
|
443
|
-
self._config, self._db,
|
|
444
|
-
auto_linker=self._auto_linker,
|
|
445
|
-
graph_analyzer=self._graph_analyzer,
|
|
446
|
-
temporal_validator=self._temporal_validator,
|
|
447
|
-
summarizer=summarizer,
|
|
448
|
-
behavioral_store=_get_behavioral_tracker(self._db),
|
|
449
|
-
embedder=self._embedder, # v3.4.7: for CCQ worker
|
|
450
|
-
llm=getattr(self, "_llm", None), # v3.4.7: for CCQ worker
|
|
451
|
-
)
|
|
452
|
-
|
|
453
|
-
# Lifecycle/tier evaluation, bounded housekeeping, and backup checks
|
|
454
|
-
# must continue even when optional forgetting/math maintenance is
|
|
455
|
-
# disabled. The scheduler itself gates those optional calculations.
|
|
456
|
-
try:
|
|
457
|
-
from superlocalmemory.core.maintenance_scheduler import MaintenanceScheduler
|
|
458
|
-
self._maintenance_scheduler = MaintenanceScheduler(
|
|
459
|
-
self._db, self._config, self._profile_id,
|
|
460
|
-
embedder=self._embedder, # v3.8.2: periodic NULL-embedding self-heal
|
|
461
|
-
)
|
|
462
|
-
self._maintenance_scheduler.start()
|
|
463
|
-
except Exception as exc:
|
|
464
|
-
logger.debug("Maintenance scheduler init failed: %s", exc)
|
|
465
|
-
|
|
466
|
-
def _process_pending_memories(self) -> None:
|
|
467
|
-
"""Process pending memories from store-first async pattern.
|
|
468
|
-
|
|
469
|
-
Called on initialize(). If pending.db doesn't exist or has no items,
|
|
470
|
-
returns immediately (~0ms). If items exist, processes them through the
|
|
471
|
-
normal store() pipeline and marks them done/failed.
|
|
472
|
-
"""
|
|
473
|
-
try:
|
|
474
|
-
from superlocalmemory.cli.pending_store import (
|
|
475
|
-
get_pending, mark_done, mark_failed,
|
|
476
|
-
)
|
|
477
|
-
except ImportError:
|
|
478
|
-
return
|
|
479
|
-
|
|
480
|
-
base_dir = self._config.base_dir
|
|
481
|
-
# Only drain items enqueued under THIS engine's profile — a queued
|
|
482
|
-
# memory must never materialize under a profile it was not written for.
|
|
483
|
-
pending = get_pending(base_dir, limit=20, profile_id=self.profile_id)
|
|
484
|
-
if not pending:
|
|
485
|
-
return
|
|
486
|
-
|
|
487
|
-
logger.info("Processing %d pending memories from async store", len(pending))
|
|
488
|
-
for item in pending:
|
|
489
|
-
try:
|
|
490
|
-
# v3.6.15 multi-scope: the pending row carries a metadata JSON
|
|
491
|
-
# blob that may hold scope/shared_with (written by the async
|
|
492
|
-
# /remember path). Replay them so a queued ``--scope global``
|
|
493
|
-
# write lands as global, not silently downgraded to personal.
|
|
494
|
-
import json as _json
|
|
495
|
-
meta = item.get("metadata")
|
|
496
|
-
if isinstance(meta, str):
|
|
497
|
-
try:
|
|
498
|
-
meta = _json.loads(meta) if meta else {}
|
|
499
|
-
except (ValueError, TypeError):
|
|
500
|
-
meta = {}
|
|
501
|
-
if not isinstance(meta, dict):
|
|
502
|
-
meta = {}
|
|
503
|
-
_scope = meta.pop("scope", None) or "personal"
|
|
504
|
-
_shared = meta.pop("shared_with", None)
|
|
505
|
-
_source_type = str(
|
|
506
|
-
meta.pop("_slm_source_type", "legacy-pending")
|
|
507
|
-
)
|
|
508
|
-
_idempotency_key = str(
|
|
509
|
-
meta.pop("_slm_idempotency_key", f"pending:{item['id']}")
|
|
510
|
-
)
|
|
511
|
-
if item.get("tags"):
|
|
512
|
-
meta.setdefault("tags", item["tags"])
|
|
513
|
-
from superlocalmemory.core.engine_ingestion import (
|
|
514
|
-
canonical_store,
|
|
515
|
-
local_trusted_actor_id,
|
|
516
|
-
)
|
|
517
|
-
canonical_store(
|
|
518
|
-
self,
|
|
519
|
-
item["content"],
|
|
520
|
-
source_type=_source_type,
|
|
521
|
-
trusted_actor_id=local_trusted_actor_id("engine-startup"),
|
|
522
|
-
metadata=meta or None,
|
|
523
|
-
scope=_scope,
|
|
524
|
-
shared_with=_shared,
|
|
525
|
-
session_id=str(meta.get("session_id") or ""),
|
|
526
|
-
idempotency_key=_idempotency_key,
|
|
527
|
-
)
|
|
528
|
-
mark_done(item["id"], base_dir)
|
|
529
|
-
except Exception as exc:
|
|
530
|
-
logger.warning("Pending memory %d failed: %s", item["id"], exc)
|
|
531
|
-
mark_failed(item["id"], str(exc), base_dir)
|
|
532
|
-
|
|
533
|
-
# -- Store operations ---------------------------------------------------
|
|
534
|
-
|
|
535
|
-
def store(
|
|
536
|
-
self,
|
|
537
|
-
content: str,
|
|
538
|
-
session_id: str = "",
|
|
539
|
-
session_date: str | None = None,
|
|
540
|
-
speaker: str = "",
|
|
541
|
-
role: str = "user",
|
|
542
|
-
metadata: dict[str, Any] | None = None,
|
|
543
|
-
*,
|
|
544
|
-
scope: str = "personal",
|
|
545
|
-
shared_with: list[str] | None = None,
|
|
546
|
-
) -> list[str]:
|
|
547
|
-
"""Store content and extract structured facts. Returns fact_ids.
|
|
548
|
-
|
|
549
|
-
Multi-scope: ``scope`` sets the visibility (personal/shared/global).
|
|
550
|
-
``shared_with`` is a list of profile_ids for shared scope.
|
|
551
|
-
"""
|
|
552
|
-
self._require_full("store")
|
|
553
|
-
self._ensure_init()
|
|
554
|
-
|
|
555
|
-
from superlocalmemory.core.engine_ingestion import (
|
|
556
|
-
canonical_store,
|
|
557
|
-
local_trusted_actor_id,
|
|
558
|
-
)
|
|
559
|
-
return canonical_store(
|
|
560
|
-
self,
|
|
561
|
-
content,
|
|
562
|
-
source_type="python-api",
|
|
563
|
-
trusted_actor_id=local_trusted_actor_id("python-api"),
|
|
564
|
-
metadata=metadata,
|
|
565
|
-
scope=scope,
|
|
566
|
-
shared_with=shared_with,
|
|
567
|
-
session_id=session_id,
|
|
568
|
-
session_date=session_date,
|
|
569
|
-
speaker=speaker,
|
|
570
|
-
role=role,
|
|
571
|
-
require_complete=False,
|
|
572
|
-
)
|
|
573
|
-
|
|
574
|
-
def store_fact_direct(self, fact: AtomicFact) -> str:
|
|
575
|
-
"""Durably store a pre-built fact with full enrichment."""
|
|
576
|
-
self._require_full("store_fact_direct")
|
|
577
|
-
self._ensure_init()
|
|
578
|
-
|
|
579
|
-
from superlocalmemory.core.engine_ingestion import (
|
|
580
|
-
canonical_store_fact,
|
|
581
|
-
local_trusted_actor_id,
|
|
582
|
-
)
|
|
583
|
-
return canonical_store_fact(
|
|
584
|
-
self,
|
|
585
|
-
fact,
|
|
586
|
-
trusted_actor_id=local_trusted_actor_id("python-api-prebuilt"),
|
|
587
|
-
)
|
|
588
|
-
|
|
589
|
-
def _warm_guard_embed(
|
|
590
|
-
self, text: str, *, timeout_s: float | None = None,
|
|
591
|
-
) -> tuple[list[float] | None, Any, Any]:
|
|
592
|
-
"""Embed now if it is cheap and safe to, otherwise leave it to the materializer.
|
|
593
|
-
|
|
594
|
-
A fact with no vector is invisible to the semantic channel, so a memory
|
|
595
|
-
written moments ago is the hardest thing in the store to find. Computing
|
|
596
|
-
it inline closes that window — but only when doing so cannot hurt anyone
|
|
597
|
-
else, because this daemon serves many sessions at once.
|
|
598
|
-
|
|
599
|
-
Four constraints, each load-bearing:
|
|
600
|
-
|
|
601
|
-
* only when the embedder is provably warm and local, so a cold start or a
|
|
602
|
-
network round-trip can never be paid on a write;
|
|
603
|
-
* on one shared single-worker pool, so N concurrent writers cannot spawn
|
|
604
|
-
N threads — the sixth caller simply misses its deadline and defers;
|
|
605
|
-
* inside ``background_work()``, so a foreground recall preempts it rather
|
|
606
|
-
than queueing behind it;
|
|
607
|
-
* behind a hard deadline, after which the caller proceeds with no
|
|
608
|
-
embedding and the background materializer finishes the job.
|
|
609
|
-
|
|
610
|
-
Returns ``(embedding, fisher_mean, fisher_variance)``, any of which may be
|
|
611
|
-
``None``. Never raises: every failure means "not now", not "write failed".
|
|
612
|
-
"""
|
|
613
|
-
emb = None
|
|
614
|
-
fmean = fvar = None
|
|
615
|
-
_embedder_ref = self._embedder
|
|
616
|
-
if (
|
|
617
|
-
_embedder_ref is None
|
|
618
|
-
or not _embedder_is_warm(_embedder_ref)
|
|
619
|
-
or _is_remote_embedder(_embedder_ref)
|
|
620
|
-
):
|
|
621
|
-
return None, None, None
|
|
622
|
-
|
|
623
|
-
import concurrent.futures as _cf
|
|
624
|
-
|
|
625
|
-
# Lazy-init once per engine — avoids per-call thread churn. Double-checked
|
|
626
|
-
# locking guards against TOCTOU on concurrent first calls.
|
|
627
|
-
if self._store_fast_embed_pool is None:
|
|
628
|
-
with self._store_fast_embed_pool_lock:
|
|
629
|
-
if self._store_fast_embed_pool_closed:
|
|
630
|
-
# Shutdown happened while this call was between the check
|
|
631
|
-
# above and this lock. Decline rather than resurrect the
|
|
632
|
-
# pool: the fact is already committed durably and keeps its
|
|
633
|
-
# place in the background queue.
|
|
634
|
-
return None, None, None
|
|
635
|
-
if self._store_fast_embed_pool is None:
|
|
636
|
-
self._store_fast_embed_pool = _cf.ThreadPoolExecutor(
|
|
637
|
-
max_workers=1,
|
|
638
|
-
thread_name_prefix="slm-sg-embed",
|
|
639
|
-
)
|
|
640
|
-
# A ceiling, not a target. Storing a memory is allowed up to 1.5 s in
|
|
641
|
-
# total, and giving up on the embedding early is not free: it is the
|
|
642
|
-
# difference between a memory that can be found by asking a question and
|
|
643
|
-
# one that can only be found by quoting its own words. Within the budget
|
|
644
|
-
# the more complete outcome wins, so this waits a full second rather than
|
|
645
|
-
# half of one. Both values below are milliseconds via the environment.
|
|
646
|
-
if timeout_s is None:
|
|
647
|
-
try:
|
|
648
|
-
timeout_s = int(os.environ.get("SLM_STORE_FAST_EMBED_TIMEOUT_MS", 1000)) / 1000.0
|
|
649
|
-
except (ValueError, TypeError):
|
|
650
|
-
timeout_s = 1.0
|
|
651
|
-
try:
|
|
652
|
-
def _best_effort_embed():
|
|
653
|
-
from superlocalmemory.core.recall_gate import background_work
|
|
654
|
-
with background_work():
|
|
655
|
-
return _embedder_ref.embed(text)
|
|
656
|
-
|
|
657
|
-
_future = self._store_fast_embed_pool.submit(_best_effort_embed)
|
|
658
|
-
try:
|
|
659
|
-
emb = _future.result(timeout=timeout_s)
|
|
660
|
-
if emb:
|
|
661
|
-
fmean, fvar = _embedder_ref.compute_fisher_params(emb)
|
|
662
|
-
except _cf.TimeoutError:
|
|
663
|
-
# Cancel rather than abandon. This pool has a single worker, so a
|
|
664
|
-
# task left running holds it for every caller behind us: each in
|
|
665
|
-
# turn times out and queues another task that runs after its
|
|
666
|
-
# caller has already answered. Cancel only stops it if it has not
|
|
667
|
-
# started, which is exactly the queued backlog we care about.
|
|
668
|
-
_future.cancel()
|
|
669
|
-
logger.debug(
|
|
670
|
-
"warm-guard embed timed out (>%.0fms) — deferring to materializer",
|
|
671
|
-
timeout_s * 1000,
|
|
672
|
-
)
|
|
673
|
-
emb = None
|
|
674
|
-
except Exception as _exc:
|
|
675
|
-
logger.debug("warm-guard embed failed (%s) — deferring to materializer", _exc)
|
|
676
|
-
emb = None
|
|
677
|
-
except Exception as _exc:
|
|
678
|
-
logger.debug("warm-guard pool submit failed (%s)", _exc)
|
|
679
|
-
emb = None
|
|
680
|
-
return emb, fmean, fvar
|
|
681
|
-
|
|
682
|
-
def _projection_has(self, fact_id: str) -> bool:
|
|
683
|
-
"""Can the meaning-based channel reach this fact?
|
|
684
|
-
|
|
685
|
-
Answered by the vector store, which owns the question, and never from
|
|
686
|
-
``atomic_facts.embedding``. The column and the projection can disagree,
|
|
687
|
-
and when they do it is the projection that decides whether a search
|
|
688
|
-
finds the fact. Reachability also needs more than a stored vector: the
|
|
689
|
-
search joins two projection tables, so a vector present in one of them
|
|
690
|
-
and absent from the other is not reachable. That predicate belongs to
|
|
691
|
-
the store; duplicating it here is how the column and the projection
|
|
692
|
-
drifted apart in the first place.
|
|
693
|
-
|
|
694
|
-
Fails closed. A store that cannot answer is treated as "not reachable",
|
|
695
|
-
which costs at most one redundant idempotent write and can never report
|
|
696
|
-
a fact as findable when it is not.
|
|
697
|
-
"""
|
|
698
|
-
store = getattr(self, "_vector_store", None)
|
|
699
|
-
answer = getattr(store, "is_searchable_by_meaning", None) if store else None
|
|
700
|
-
if not callable(answer):
|
|
701
|
-
return False
|
|
702
|
-
try:
|
|
703
|
-
return bool(answer(fact_id, self._profile_id))
|
|
704
|
-
except Exception:
|
|
705
|
-
return False
|
|
706
|
-
|
|
707
|
-
def _attach_vector(
|
|
708
|
-
self, fact_id: str, emb: list[float],
|
|
709
|
-
fmean: float | None, fvar: float | None,
|
|
710
|
-
) -> bool:
|
|
711
|
-
"""Attach a vector to a stored fact. True only if a search can now find it.
|
|
712
|
-
|
|
713
|
-
Two representations are involved and they answer different questions. The
|
|
714
|
-
vector projection is what a search on meaning queries, so it decides
|
|
715
|
-
whether the fact is *findable*. The ``atomic_facts.embedding`` column is
|
|
716
|
-
where the vector *lives* — it is what an export, a backup or a direct
|
|
717
|
-
fetch reads, and what a re-projection would be rebuilt from.
|
|
718
|
-
|
|
719
|
-
So:
|
|
720
|
-
|
|
721
|
-
* **The projection is attempted first**, because that is the ordering that
|
|
722
|
-
keeps the two consistent when both can succeed.
|
|
723
|
-
* **The column is written either way.** The vector has already been
|
|
724
|
-
computed and it is real data; withholding it because the projection was
|
|
725
|
-
refused would discard it and leave the repair pass paying for the model
|
|
726
|
-
again on every pass. Every reason a projection is refused — no search
|
|
727
|
-
extension on this platform, a dimension change, a store at a different
|
|
728
|
-
dimension — is a property of the installation and not of this fact, so
|
|
729
|
-
withholding one fact's column repairs nothing.
|
|
730
|
-
* **The return value is the honest answer to "can this be found by
|
|
731
|
-
meaning", and it is False whenever the projection did not accept the
|
|
732
|
-
vector.** That is what keeps a receipt from claiming a memory is
|
|
733
|
-
searchable when no search can reach it, which was the actual harm in
|
|
734
|
-
writing these two out of order — not the column itself.
|
|
735
|
-
* **If the projection succeeded and the column write then failed, the
|
|
736
|
-
projection is rolled back.** Otherwise a search finds a fact whose
|
|
737
|
-
vector nothing else can see, and nothing would ever reconcile them.
|
|
738
|
-
|
|
739
|
-
Never raises. The durable write has already happened before this runs; a
|
|
740
|
-
fact that is not projected keeps its place in the background queue.
|
|
741
|
-
|
|
742
|
-
Once started, the pair runs to completion and is not interrupted by a
|
|
743
|
-
caller deadline. Abandoning it half-way is what produces the disagreeing
|
|
744
|
-
states above, so a deadline can decide whether to *begin* the pair, never
|
|
745
|
-
whether to finish it. Callers check their budget immediately before
|
|
746
|
-
calling. Bounding the pair itself is a property of the write lock.
|
|
747
|
-
"""
|
|
748
|
-
projected = False
|
|
749
|
-
store = getattr(self, "_vector_store", None)
|
|
750
|
-
if store is not None and getattr(store, "available", False):
|
|
751
|
-
try:
|
|
752
|
-
projected = bool(store.upsert(fact_id, self._profile_id, emb))
|
|
753
|
-
if not projected:
|
|
754
|
-
logger.warning(
|
|
755
|
-
"vector projection refused for %s — stored but not "
|
|
756
|
-
"findable by meaning", fact_id[:12],
|
|
757
|
-
)
|
|
758
|
-
except Exception as exc:
|
|
759
|
-
logger.warning(
|
|
760
|
-
"vector projection failed for %s (%s: %s) — stored but not "
|
|
761
|
-
"findable by meaning",
|
|
762
|
-
fact_id[:12], type(exc).__name__, exc,
|
|
763
|
-
)
|
|
764
|
-
if projected:
|
|
765
|
-
index = getattr(self, "_ann_index", None)
|
|
766
|
-
if index is not None:
|
|
767
|
-
try:
|
|
768
|
-
index.add(fact_id, emb)
|
|
769
|
-
except Exception:
|
|
770
|
-
logger.warning(
|
|
771
|
-
"in-memory index rejected %s", fact_id[:12],
|
|
772
|
-
exc_info=True,
|
|
773
|
-
)
|
|
774
|
-
|
|
775
|
-
if not self._write_canonical_vector(fact_id, emb, fmean, fvar):
|
|
776
|
-
if projected:
|
|
777
|
-
logger.warning(
|
|
778
|
-
"canonical vector write failed for %s — undoing the "
|
|
779
|
-
"projection so the two representations cannot disagree",
|
|
780
|
-
fact_id[:12],
|
|
781
|
-
)
|
|
782
|
-
self._detach_vector(fact_id)
|
|
783
|
-
return False
|
|
784
|
-
return projected
|
|
785
|
-
|
|
786
|
-
def _write_canonical_vector(
|
|
787
|
-
self, fact_id: str, emb: list[float],
|
|
788
|
-
fmean: float | None, fvar: float | None,
|
|
789
|
-
) -> bool:
|
|
790
|
-
"""Write the vector to ``atomic_facts``. False if it did not land."""
|
|
791
|
-
try:
|
|
792
|
-
self._db.update_fact(
|
|
793
|
-
fact_id,
|
|
794
|
-
{"embedding": emb, "fisher_mean": fmean, "fisher_variance": fvar},
|
|
795
|
-
profile_id=self._profile_id,
|
|
796
|
-
)
|
|
797
|
-
return True
|
|
798
|
-
except Exception as exc:
|
|
799
|
-
logger.warning(
|
|
800
|
-
"canonical vector write failed for %s (%s: %s)",
|
|
801
|
-
fact_id[:12], type(exc).__name__, exc,
|
|
802
|
-
)
|
|
803
|
-
return False
|
|
804
|
-
|
|
805
|
-
def _detach_vector(self, fact_id: str) -> None:
|
|
806
|
-
"""Undo a projection whose canonical write did not land.
|
|
807
|
-
|
|
808
|
-
Returns the row to the one state that heals itself: no vector anywhere,
|
|
809
|
-
which is what the repair pass looks for.
|
|
810
|
-
"""
|
|
811
|
-
store = getattr(self, "_vector_store", None)
|
|
812
|
-
delete = getattr(store, "delete", None) if store else None
|
|
813
|
-
if callable(delete):
|
|
814
|
-
try:
|
|
815
|
-
delete(fact_id)
|
|
816
|
-
except Exception:
|
|
817
|
-
logger.warning(
|
|
818
|
-
"could not undo the projection for %s; it and the canonical "
|
|
819
|
-
"column now disagree", fact_id[:12], exc_info=True,
|
|
820
|
-
)
|
|
821
|
-
index = getattr(self, "_ann_index", None)
|
|
822
|
-
remove = getattr(index, "remove", None) if index else None
|
|
823
|
-
if callable(remove):
|
|
824
|
-
try:
|
|
825
|
-
remove(fact_id)
|
|
826
|
-
except Exception:
|
|
827
|
-
logger.warning(
|
|
828
|
-
"could not undo the index entry for %s", fact_id[:12],
|
|
829
|
-
exc_info=True,
|
|
830
|
-
)
|
|
831
|
-
|
|
832
|
-
def enrich_new_facts_now(
|
|
833
|
-
self, fact_ids: list[str], *, timeout_s: float | None = None,
|
|
834
|
-
) -> int:
|
|
835
|
-
"""Make just-written facts searchable by meaning, within a deadline.
|
|
836
|
-
|
|
837
|
-
Returns how many of ``fact_ids`` ARE searchable by meaning when this
|
|
838
|
-
returns — not how many were embedded here. A fact that already had a
|
|
839
|
-
vector counts, because the caller uses this number to tell the user
|
|
840
|
-
whether their memory can be found, and "not embedded by me" is not the
|
|
841
|
-
same statement as "not findable".
|
|
842
|
-
|
|
843
|
-
Every write path in the product — the command line, the tool interface and
|
|
844
|
-
the dashboard — commits through one durable receipt and then relies on a
|
|
845
|
-
background pass for enrichment. Until that pass runs, the memory can only
|
|
846
|
-
be found by quoting its own wording, which is not how anyone asks. This
|
|
847
|
-
closes that window for whichever facts it can reach in time, and reports
|
|
848
|
-
honestly how many it reached.
|
|
849
|
-
|
|
850
|
-
The durable write has already happened before this is called. Nothing here
|
|
851
|
-
can fail it: on any error or timeout the fact simply keeps its place in the
|
|
852
|
-
background queue.
|
|
853
|
-
"""
|
|
854
|
-
if not fact_ids:
|
|
855
|
-
return 0
|
|
856
|
-
# Components are built on demand. Without this the embedder is still
|
|
857
|
-
# None and every fact is silently skipped as "nothing to do" — which
|
|
858
|
-
# looks exactly like "there was nothing to enrich".
|
|
859
|
-
self._require_full("enrich_new_facts_now")
|
|
860
|
-
self._ensure_init()
|
|
861
|
-
# Matches the default the write path uses for its own embedding attempt;
|
|
862
|
-
# the two were allowed to drift apart, so a direct caller got half the
|
|
863
|
-
# budget the daemon gives.
|
|
864
|
-
deadline = time.monotonic() + (timeout_s if timeout_s is not None else 1.0)
|
|
865
|
-
enriched = 0
|
|
866
|
-
for fact_id in fact_ids:
|
|
867
|
-
try:
|
|
868
|
-
fact = self._db.get_fact(fact_id, self._profile_id)
|
|
869
|
-
if fact is None:
|
|
870
|
-
continue
|
|
871
|
-
already = self._projection_has(fact_id)
|
|
872
|
-
if already and getattr(fact, "embedding", None):
|
|
873
|
-
# Findable by meaning, and the two representations agree.
|
|
874
|
-
# Counting it keeps the caller's receipt truthful: saying
|
|
875
|
-
# "wording only" about a fact that is already fully
|
|
876
|
-
# searchable is its own kind of wrong.
|
|
877
|
-
#
|
|
878
|
-
# BOTH are required. A projection with no canonical vector is
|
|
879
|
-
# a fact a search can return while an export, a backup or a
|
|
880
|
-
# direct fetch sees nothing — and treating the projection
|
|
881
|
-
# alone as proof of health means no pass ever repairs it. A
|
|
882
|
-
# canonical vector with no projection is the opposite, and
|
|
883
|
-
# equally unrepaired. Requiring agreement is what makes both
|
|
884
|
-
# states heal instead of persisting silently.
|
|
885
|
-
enriched += 1
|
|
886
|
-
continue
|
|
887
|
-
# Answering "is it already findable" costs one indexed lookup and
|
|
888
|
-
# no model call, so it is done BEFORE the budget check: a caller
|
|
889
|
-
# that is out of time still deserves an honest answer about a
|
|
890
|
-
# fact that needs no work. Only the work below needs a budget.
|
|
891
|
-
remaining = deadline - time.monotonic()
|
|
892
|
-
if remaining <= 0:
|
|
893
|
-
break
|
|
894
|
-
# Re-checked immediately before the writes below as well. The
|
|
895
|
-
# caller awaits this on a thread and stops waiting at its own
|
|
896
|
-
# deadline, but the thread keeps running — so a late write could
|
|
897
|
-
# land while the next request already holds the write lock. Past
|
|
898
|
-
# the deadline this stops writing rather than racing a
|
|
899
|
-
# transaction nobody is waiting for.
|
|
900
|
-
emb = getattr(fact, "embedding", None)
|
|
901
|
-
fmean = getattr(fact, "fisher_mean", None)
|
|
902
|
-
fvar = getattr(fact, "fisher_variance", None)
|
|
903
|
-
if not emb:
|
|
904
|
-
emb, fmean, fvar = self._warm_guard_embed(
|
|
905
|
-
fact.content, timeout_s=remaining,
|
|
906
|
-
)
|
|
907
|
-
if not emb:
|
|
908
|
-
continue
|
|
909
|
-
if time.monotonic() >= deadline:
|
|
910
|
-
# The embed itself consumed the budget; the caller has
|
|
911
|
-
# moved on. Leave this fact to the background pass.
|
|
912
|
-
break
|
|
913
|
-
# The row already holds a vector that never reached the
|
|
914
|
-
# projection: reuse it rather than paying for the model again.
|
|
915
|
-
if time.monotonic() >= deadline:
|
|
916
|
-
# Checked here as well as after the embed, because reusing a
|
|
917
|
-
# vector already on the row skips that check entirely and the
|
|
918
|
-
# fetch above can itself be slow under write contention.
|
|
919
|
-
break
|
|
920
|
-
if self._attach_vector(fact_id, emb, fmean, fvar):
|
|
921
|
-
enriched += 1
|
|
922
|
-
except Exception as exc:
|
|
923
|
-
# Warning, not debug. A run that enriches nothing returns 0 in a
|
|
924
|
-
# millisecond and reads exactly like "there was nothing to do".
|
|
925
|
-
logger.warning(
|
|
926
|
-
"inline enrichment failed for %s (%s: %s) — left for the "
|
|
927
|
-
"background pass", fact_id[:12], type(exc).__name__, exc,
|
|
928
|
-
)
|
|
929
|
-
return enriched
|
|
930
|
-
|
|
931
|
-
def store_fast(
|
|
932
|
-
self, content: str, metadata: dict[str, Any] | None = None,
|
|
933
|
-
*, scope: str = "personal", shared_with: list[str] | None = None,
|
|
934
|
-
session_date: str | None = None, speaker: str = "", role: str = "user",
|
|
935
|
-
index_external: bool = True,
|
|
936
|
-
) -> list[str]:
|
|
937
|
-
"""v3.5.5 WRITE-THROUGH: synchronous verbatim insert for IMMEDIATE recall.
|
|
938
|
-
|
|
939
|
-
Full ``store()`` blocks 30-180s on LLM fact-extraction + Ollama embedding
|
|
940
|
-
+ graph building. That created a recall window: a memory stored via the
|
|
941
|
-
async path sat in pending.db, unrecallable, until the background
|
|
942
|
-
materializer caught up. An agent storing a decision then immediately
|
|
943
|
-
recalling it (same session, or a parallel/next session) would miss it.
|
|
944
|
-
|
|
945
|
-
store_fast inserts a verbatim AtomicFact (+ memory row) synchronously.
|
|
946
|
-
The FTS5 ``atomic_facts_fts`` trigger auto-populates on INSERT, so the
|
|
947
|
-
memory is **keyword/BM25-recallable the instant this returns**, with no
|
|
948
|
-
LLM call. Entities and graph are enriched async by the materializer.
|
|
949
|
-
|
|
950
|
-
The embedding is no longer purely async: ``_warm_guard_embed`` computes it
|
|
951
|
-
inline **when the model is already loaded**, because a fact with no vector
|
|
952
|
-
is invisible to the semantic channel and a memory written moments ago is
|
|
953
|
-
then the hardest thing in the store to find by asking about it. Measured
|
|
954
|
-
on a copy of a real store: **75 ms** with the model loaded, vector
|
|
955
|
-
attached; **34 ms** when it is not, deferring to the materializer. This
|
|
956
|
-
docstring used to say "~ms, no embedding" and that had stopped being true.
|
|
957
|
-
|
|
958
|
-
Returns real fact_ids immediately. Quality gate rejects template junk.
|
|
959
|
-
"""
|
|
960
|
-
self._require_full("store_fast")
|
|
961
|
-
self._ensure_init()
|
|
962
|
-
import re as _re
|
|
963
|
-
import uuid as _uuid
|
|
964
|
-
from datetime import datetime, timezone
|
|
965
|
-
from superlocalmemory.core.engine_ingestion import content_passes_admission
|
|
966
|
-
if not content_passes_admission(content):
|
|
967
|
-
return []
|
|
968
|
-
# v3.6.6 ingest gate: reject 1MB monsters + prompt-template pollution;
|
|
969
|
-
# clamp the searchable FACT copy (head+tail) while the memories row keeps
|
|
970
|
-
# the FULL original. Embedding/BM25 use the clamped copy so a 167KB paste
|
|
971
|
-
# never produces a garbage vector. Env kill-switch: SLM_INGEST_NO_GATE=1.
|
|
972
|
-
fact_text = content
|
|
973
|
-
try:
|
|
974
|
-
from superlocalmemory.core.ingest_gate import apply_ingest_gate
|
|
975
|
-
_sc = getattr(self._config, "store", None)
|
|
976
|
-
gate = apply_ingest_gate(
|
|
977
|
-
content,
|
|
978
|
-
max_verbatim_chars=getattr(_sc, "max_verbatim_chars", 24000),
|
|
979
|
-
max_ingest_bytes=getattr(_sc, "max_ingest_bytes", 1_048_576),
|
|
980
|
-
)
|
|
981
|
-
if gate.rejected:
|
|
982
|
-
logger.debug("store_fast ingest gate rejected: %s", gate.rejection_reason)
|
|
983
|
-
return []
|
|
984
|
-
fact_text = gate.fact_content
|
|
985
|
-
except ImportError:
|
|
986
|
-
pass # gate module missing → store verbatim (never block a write)
|
|
987
|
-
now = datetime.now(timezone.utc).isoformat()
|
|
988
|
-
record = MemoryRecord(
|
|
989
|
-
profile_id=self._profile_id, content=content,
|
|
990
|
-
session_date=session_date or now[:10],
|
|
991
|
-
session_id=(metadata or {}).get("session_id", ""),
|
|
992
|
-
speaker=speaker,
|
|
993
|
-
role=role,
|
|
994
|
-
metadata=metadata or {},
|
|
995
|
-
scope=scope, shared_with=shared_with,
|
|
996
|
-
)
|
|
997
|
-
self._db.store_memory(record)
|
|
998
|
-
# Lightweight regex entities (matches store_pipeline verbatim path) so
|
|
999
|
-
# the entity_graph channel has something to work with before enrichment.
|
|
1000
|
-
ents = sorted(
|
|
1001
|
-
{m.group(1) for m in _re.finditer(
|
|
1002
|
-
r"\b([A-Z][a-z]+(?:\s[A-Z][a-z]+){0,3})\b", fact_text)}
|
|
1003
|
-
| {m.group(1) for m in _re.finditer(r"\b([A-Z]{2,})\b", fact_text)}
|
|
1004
|
-
)
|
|
1005
|
-
# Workstream D (3.8.4) — warm-guard synchronous embed.
|
|
1006
|
-
#
|
|
1007
|
-
# Original contract (3.8.2): queryable admission NEVER acquires the
|
|
1008
|
-
# embedding worker lock. On a clean Mode-A install the background model
|
|
1009
|
-
# load owns that lock for up to 180s, turning the receipt-first path into
|
|
1010
|
-
# a hidden synchronous wait. The canonical materializer promotes this
|
|
1011
|
-
# fact with its full pipeline (embedding, Fisher, entities, graph edges).
|
|
1012
|
-
#
|
|
1013
|
-
# 3.8.4 extension: when the embedder is PROVABLY warm (_available is True)
|
|
1014
|
-
# AND is a local embedder (not a remote cloud/OpenAI endpoint), compute the
|
|
1015
|
-
# embedding synchronously with a hard 1s cap. On timeout or any
|
|
1016
|
-
# exception, fall through to emb=None — the materializer fills it async.
|
|
1017
|
-
# This preserves the 3.8.2 invariant for cold start while eliminating the
|
|
1018
|
-
# semantic-channel blind spot on warm daemons (the top UX complaint).
|
|
1019
|
-
emb, fmean, fvar = self._warm_guard_embed(fact_text)
|
|
1020
|
-
fact = AtomicFact(
|
|
1021
|
-
fact_id=_uuid.uuid4().hex[:16], memory_id=record.memory_id,
|
|
1022
|
-
profile_id=self._profile_id, content=fact_text,
|
|
1023
|
-
fact_type=FactType.EPISODIC, entities=ents,
|
|
1024
|
-
observation_date=session_date or now[:10],
|
|
1025
|
-
confidence=0.7, importance=0.5,
|
|
1026
|
-
# The vector is attached after this row exists, not with it. The
|
|
1027
|
-
# projection that the meaning-based channel searches has a foreign
|
|
1028
|
-
# key onto this row, so it cannot be written first; and writing this
|
|
1029
|
-
# column first would hide the fact from the repair pass if the
|
|
1030
|
-
# projection then failed. _attach_vector owns that ordering.
|
|
1031
|
-
embedding=None, fisher_mean=None, fisher_variance=None,
|
|
1032
|
-
created_at=now,
|
|
1033
|
-
scope=scope, shared_with=shared_with,
|
|
1034
|
-
)
|
|
1035
|
-
self._db.store_fact(fact) # FTS5 trigger → immediately BM25-recallable
|
|
1036
|
-
# Attach the vector so the meaning-based channel finds it now, through
|
|
1037
|
-
# the same ordering every other write path uses.
|
|
1038
|
-
if emb and index_external:
|
|
1039
|
-
self._attach_vector(fact.fact_id, emb, fmean, fvar)
|
|
1040
|
-
# Persist BM25 tokens too (covers the in-memory rank_bm25 fallback path).
|
|
1041
|
-
if index_external:
|
|
1042
|
-
try:
|
|
1043
|
-
bm25 = getattr(self._retrieval_engine, "_bm25", None)
|
|
1044
|
-
if bm25:
|
|
1045
|
-
bm25.add(fact.fact_id, fact_text, self._profile_id)
|
|
1046
|
-
except Exception:
|
|
1047
|
-
pass
|
|
1048
|
-
return [fact.fact_id]
|
|
1049
|
-
|
|
1050
|
-
# -- Recall operations --------------------------------------------------
|
|
1051
|
-
|
|
1052
|
-
def recall(
|
|
1053
|
-
self, query: str, profile_id: str | None = None,
|
|
1054
|
-
mode: Mode | None = None, limit: int = CANONICAL_RECALL_LIMIT,
|
|
1055
|
-
agent_id: str = "unknown",
|
|
1056
|
-
session_id: str | None = None,
|
|
1057
|
-
fast: bool | None = None,
|
|
1058
|
-
*,
|
|
1059
|
-
include_global: bool | None = None,
|
|
1060
|
-
include_shared: bool | None = None,
|
|
1061
|
-
window: str | tuple[str, str] | None = None,
|
|
1062
|
-
as_of: str | None = None,
|
|
1063
|
-
known_as_of: str | None = None,
|
|
1064
|
-
valid_at: str | None = None,
|
|
1065
|
-
include_unknown: bool = False,
|
|
1066
|
-
) -> RecallResponse:
|
|
1067
|
-
"""Recall relevant facts for a query.
|
|
1068
|
-
|
|
1069
|
-
``session_id`` gives the recall continuity: memories this session was
|
|
1070
|
-
recently shown are held in a small in-process working set and bias the
|
|
1071
|
-
ranking of subsequent queries in the same session. Retrieval still runs
|
|
1072
|
-
in full every time — the working set reorders the answer, it never
|
|
1073
|
-
replaces the search. Costs no query and no file access.
|
|
1074
|
-
|
|
1075
|
-
Omitting it is supported and means every recall starts cold, which is
|
|
1076
|
-
what every caller got before. The parameter was previously accepted and
|
|
1077
|
-
then ignored: it was documented as enqueueing a ``pending_outcomes`` row
|
|
1078
|
-
for downstream hooks, and no such row was ever written.
|
|
1079
|
-
|
|
1080
|
-
``fast`` controls only the internal agentic verification round; all six
|
|
1081
|
-
local retrieval channels + reranker run regardless. ``fast=None`` (the
|
|
1082
|
-
default) resolves to the client-driven-agentic policy: the agent hot
|
|
1083
|
-
path skips the internal round (equivalent to ``fast=True``) and lets the
|
|
1084
|
-
calling LLM drive query refinement using the returned confidence signals.
|
|
1085
|
-
Pass ``fast=False`` to force the internal agentic round when no smart
|
|
1086
|
-
client sits in front of SLM. See ``recall_pipeline.resolve_hot_path_fast``.
|
|
1087
|
-
|
|
1088
|
-
Multi-scope: ``include_global`` / ``include_shared`` control which
|
|
1089
|
-
scopes participate in retrieval. ``None`` (the default) means "use the
|
|
1090
|
-
configured ScopeConfig default", which ships OFF — shared memory is
|
|
1091
|
-
opt-in (v3.6.15). Personal facts are ALWAYS returned regardless, so a
|
|
1092
|
-
config of False reproduces 3.6.14 pure-isolation behaviour exactly.
|
|
1093
|
-
This is the single policy chokepoint: every recall path (CLI, MCP,
|
|
1094
|
-
daemon HTTP, in-process adapter) flows through here, so a caller that
|
|
1095
|
-
forgets to thread the flag still gets the safe configured default.
|
|
1096
|
-
"""
|
|
1097
|
-
self._require_full("recall")
|
|
1098
|
-
self._ensure_init()
|
|
1099
|
-
|
|
1100
|
-
# Resolve None → configured ScopeConfig default (shared-off by default).
|
|
1101
|
-
_scope_cfg = getattr(self._config, "scope", None)
|
|
1102
|
-
if include_global is None:
|
|
1103
|
-
include_global = bool(getattr(_scope_cfg, "recall_include_global", False))
|
|
1104
|
-
if include_shared is None:
|
|
1105
|
-
include_shared = bool(getattr(_scope_cfg, "recall_include_shared", False))
|
|
1106
|
-
|
|
1107
|
-
from superlocalmemory.retrieval.temporal_utils import normalize_strict_boundary
|
|
1108
|
-
known_as_of = normalize_strict_boundary(known_as_of, "known_as_of")
|
|
1109
|
-
valid_at = normalize_strict_boundary(valid_at, "valid_at")
|
|
1110
|
-
|
|
1111
|
-
pid = profile_id or self._profile_id
|
|
1112
|
-
|
|
1113
|
-
from superlocalmemory.core.recall_pipeline import run_recall
|
|
1114
|
-
try:
|
|
1115
|
-
response = run_recall(
|
|
1116
|
-
query, pid, mode=mode, limit=limit, agent_id=agent_id,
|
|
1117
|
-
session_id=session_id,
|
|
1118
|
-
config=self._config,
|
|
1119
|
-
retrieval_engine=self._retrieval_engine,
|
|
1120
|
-
trust_scorer=self._trust_scorer,
|
|
1121
|
-
embedder=self._embedder,
|
|
1122
|
-
db=self._db, llm=self._llm,
|
|
1123
|
-
hooks=self._hooks,
|
|
1124
|
-
access_log=self._access_log,
|
|
1125
|
-
auto_linker=self._auto_linker,
|
|
1126
|
-
fast=fast,
|
|
1127
|
-
include_global=include_global,
|
|
1128
|
-
include_shared=include_shared,
|
|
1129
|
-
window=window,
|
|
1130
|
-
as_of=as_of,
|
|
1131
|
-
known_as_of=known_as_of,
|
|
1132
|
-
valid_at=valid_at,
|
|
1133
|
-
include_unknown=include_unknown,
|
|
1134
|
-
)
|
|
1135
|
-
except Exception:
|
|
1136
|
-
# Diagnostics are intentionally not recorded here. A recall is a
|
|
1137
|
-
# read command; diagnostics, outcomes, and implicit feedback must
|
|
1138
|
-
# be submitted through explicit write commands.
|
|
1139
|
-
raise
|
|
1140
|
-
|
|
1141
|
-
# Leave a ticket saying what was shown, so an outcome can be attached to
|
|
1142
|
-
# it later. Without one there is no key to join an outcome back to the
|
|
1143
|
-
# recall that produced it, and the whole learning path stops at the first
|
|
1144
|
-
# hop: on a real store every one of 162 recorded outcomes carried an
|
|
1145
|
-
# empty recall id, every per-memory usefulness score sat at its neutral
|
|
1146
|
-
# 0.5, and the ranking model had not retrained in eleven weeks.
|
|
1147
|
-
#
|
|
1148
|
-
# THIS MUST NOT COST THE READER ANYTHING. It is one put_nowait on a
|
|
1149
|
-
# bounded in-memory queue -- roughly a microsecond -- and it drops the
|
|
1150
|
-
# event rather than blocking or raising if the queue is full. A worker
|
|
1151
|
-
# persists it elsewhere. Recall's answer and its timing are unchanged;
|
|
1152
|
-
# measured p50 and p95 before and after are within noise of each other.
|
|
1153
|
-
#
|
|
1154
|
-
# It records only. Nothing here reorders a result: whether learning is
|
|
1155
|
-
# allowed to influence ranking stays a separate, explicit setting that
|
|
1156
|
-
# remains off unless an operator turns it on.
|
|
1157
|
-
try:
|
|
1158
|
-
import uuid
|
|
1159
|
-
|
|
1160
|
-
from superlocalmemory.learning.outcome_queue import (
|
|
1161
|
-
RecallEvent,
|
|
1162
|
-
enqueue_recall,
|
|
1163
|
-
)
|
|
1164
|
-
|
|
1165
|
-
# A fresh id per answer, put on the response as well as the
|
|
1166
|
-
# ticket. `calibration_id` was the obvious candidate and is wrong:
|
|
1167
|
-
# it is None on most paths, and an empty join key is exactly the
|
|
1168
|
-
# state that made all 162 recorded outcomes unmatchable. Returning
|
|
1169
|
-
# it is what makes it useful -- until an answer carried its own
|
|
1170
|
-
# name, no caller could quote one back, so an outcome could only
|
|
1171
|
-
# ever be matched by guessing from overlapping memories.
|
|
1172
|
-
answer_id = uuid.uuid4().hex
|
|
1173
|
-
try:
|
|
1174
|
-
response.query_id = answer_id
|
|
1175
|
-
except Exception: # noqa: BLE001 -- an older response shape
|
|
1176
|
-
pass
|
|
1177
|
-
enqueue_recall(RecallEvent(
|
|
1178
|
-
session_id=self._session_for_signals(session_id),
|
|
1179
|
-
profile_id=str(pid),
|
|
1180
|
-
query=query,
|
|
1181
|
-
fact_ids=[
|
|
1182
|
-
r.fact.fact_id for r in (response.results or [])
|
|
1183
|
-
if getattr(r, "fact", None) is not None
|
|
1184
|
-
],
|
|
1185
|
-
query_id=answer_id,
|
|
1186
|
-
))
|
|
1187
|
-
except Exception as exc: # noqa: BLE001 -- a read must not fail on this
|
|
1188
|
-
logger.debug("recall outcome ticket skipped: %s", exc)
|
|
1189
|
-
|
|
1190
|
-
return response
|
|
1191
|
-
|
|
1192
|
-
def _session_for_signals(self, session_id: str | None) -> str:
|
|
1193
|
-
"""The name to file this call's outcome under.
|
|
1194
|
-
|
|
1195
|
-
In order: what the caller said, then the last thing any caller on this
|
|
1196
|
-
engine said, then a stable id for this process. Never empty, because an
|
|
1197
|
-
empty one is discarded and the call then leaves no trace an outcome
|
|
1198
|
-
could ever be matched to -- which is what happened on every path that
|
|
1199
|
-
did not thread a session through, meaning nearly all of them.
|
|
1200
|
-
|
|
1201
|
-
Three attribute reads and no I/O. Recall must not get slower to record
|
|
1202
|
-
that it happened.
|
|
1203
|
-
"""
|
|
1204
|
-
named = str(session_id or "").strip()
|
|
1205
|
-
if named:
|
|
1206
|
-
self._last_session_id = named
|
|
1207
|
-
return named
|
|
1208
|
-
return self._last_session_id or self._ambient_session_id
|
|
1209
|
-
|
|
1210
|
-
# -- Session operations -------------------------------------------------
|
|
1211
|
-
|
|
1212
|
-
def create_speaker_entities(
|
|
1213
|
-
self, speaker_a: str, speaker_b: str,
|
|
1214
|
-
) -> None:
|
|
1215
|
-
"""Pre-create canonical entities for conversation speakers."""
|
|
1216
|
-
self._require_full("create_speaker_entities")
|
|
1217
|
-
self._ensure_init()
|
|
1218
|
-
if self._entity_resolver:
|
|
1219
|
-
self._entity_resolver.create_speaker_entities(
|
|
1220
|
-
speaker_a, speaker_b, self._profile_id,
|
|
1221
|
-
)
|
|
1222
|
-
|
|
1223
|
-
def close_session(self, session_id: str) -> int:
|
|
1224
|
-
"""Create session-level temporal summary and release its working set.
|
|
1225
|
-
|
|
1226
|
-
The working set is what the session was recently looking at. It exists
|
|
1227
|
-
to connect one turn to the next, so it has no meaning once there are no
|
|
1228
|
-
more turns; holding it would let a later session that reuses this id
|
|
1229
|
-
inherit ranking bias from a conversation that already ended.
|
|
1230
|
-
"""
|
|
1231
|
-
self._ensure_init()
|
|
1232
|
-
|
|
1233
|
-
try:
|
|
1234
|
-
from superlocalmemory.core.working_memory import discard
|
|
1235
|
-
|
|
1236
|
-
discard(self._profile_id, session_id)
|
|
1237
|
-
except Exception as exc: # pragma: no cover — never block a close
|
|
1238
|
-
logger.debug("working-set discard skipped: %s", exc)
|
|
1239
|
-
|
|
1240
|
-
from superlocalmemory.core.store_pipeline import run_close_session
|
|
1241
|
-
return run_close_session(
|
|
1242
|
-
session_id, self._profile_id, db=self._db,
|
|
1243
|
-
)
|
|
1244
|
-
|
|
1245
|
-
# -- Lifecycle ----------------------------------------------------------
|
|
1246
|
-
|
|
1247
|
-
def close(self) -> None:
|
|
1248
|
-
"""Release engine-owned resources without waiting for model workers.
|
|
1249
|
-
|
|
1250
|
-
Daemon shutdown must be a bounded operation. In particular, a
|
|
1251
|
-
``store_fast`` embed submitted just before shutdown may be blocked in a
|
|
1252
|
-
model runtime forever; waiting for its executor here used to make the
|
|
1253
|
-
service manager SIGKILL the daemon and leave its children behind.
|
|
1254
|
-
References are cleared before invoking each cleanup hook, so a second
|
|
1255
|
-
close is safe even when one optional cleanup hook fails.
|
|
1256
|
-
"""
|
|
1257
|
-
scheduler = getattr(self, "_maintenance_scheduler", None)
|
|
1258
|
-
self._maintenance_scheduler = None
|
|
1259
|
-
if scheduler is not None:
|
|
1260
|
-
try:
|
|
1261
|
-
scheduler.stop()
|
|
1262
|
-
except Exception:
|
|
1263
|
-
logger.warning("engine cleanup: maintenance scheduler stop failed", exc_info=True)
|
|
1264
|
-
|
|
1265
|
-
embed_pool = getattr(self, "_store_fast_embed_pool", None)
|
|
1266
|
-
embed_pool_lock = getattr(self, "_store_fast_embed_pool_lock", None)
|
|
1267
|
-
if embed_pool_lock is not None:
|
|
1268
|
-
with embed_pool_lock:
|
|
1269
|
-
# Marked closed under the same lock that guards creation, so a
|
|
1270
|
-
# call already past the unlocked check declines instead of
|
|
1271
|
-
# building an unowned replacement.
|
|
1272
|
-
self._store_fast_embed_pool_closed = True
|
|
1273
|
-
embed_pool, self._store_fast_embed_pool = self._store_fast_embed_pool, None
|
|
1274
|
-
else:
|
|
1275
|
-
self._store_fast_embed_pool_closed = True
|
|
1276
|
-
self._store_fast_embed_pool = None
|
|
1277
|
-
if embed_pool is not None:
|
|
1278
|
-
try:
|
|
1279
|
-
embed_pool.shutdown(wait=False, cancel_futures=True)
|
|
1280
|
-
except Exception:
|
|
1281
|
-
logger.warning(
|
|
1282
|
-
"engine cleanup: store-fast embed pool shutdown failed",
|
|
1283
|
-
exc_info=True,
|
|
1284
|
-
)
|
|
1285
|
-
|
|
1286
|
-
retrieval = getattr(self, "_retrieval_engine", None)
|
|
1287
|
-
self._retrieval_engine = None
|
|
1288
|
-
if retrieval is not None:
|
|
1289
|
-
reranker = getattr(retrieval, "_reranker", None)
|
|
1290
|
-
try:
|
|
1291
|
-
if reranker is not None:
|
|
1292
|
-
shutdown = getattr(reranker, "shutdown", None)
|
|
1293
|
-
if callable(shutdown):
|
|
1294
|
-
shutdown(timeout=1.0)
|
|
1295
|
-
else:
|
|
1296
|
-
unload = getattr(reranker, "unload", None)
|
|
1297
|
-
if callable(unload):
|
|
1298
|
-
unload()
|
|
1299
|
-
except Exception:
|
|
1300
|
-
logger.warning("engine cleanup: reranker shutdown failed", exc_info=True)
|
|
1301
|
-
try:
|
|
1302
|
-
retrieval.close(wait=False)
|
|
1303
|
-
except TypeError:
|
|
1304
|
-
# Compatibility for plugins with the legacy no-argument
|
|
1305
|
-
# close hook. Built-in RetrievalEngine accepts ``wait``.
|
|
1306
|
-
try:
|
|
1307
|
-
retrieval.close()
|
|
1308
|
-
except Exception:
|
|
1309
|
-
logger.warning(
|
|
1310
|
-
"engine cleanup: legacy retrieval shutdown failed",
|
|
1311
|
-
exc_info=True,
|
|
1312
|
-
)
|
|
1313
|
-
except Exception:
|
|
1314
|
-
logger.warning("engine cleanup: retrieval executor shutdown failed", exc_info=True)
|
|
1315
|
-
|
|
1316
|
-
embedder = getattr(self, "_embedder", None)
|
|
1317
|
-
self._embedder = None
|
|
1318
|
-
if embedder is not None:
|
|
1319
|
-
try:
|
|
1320
|
-
shutdown = getattr(embedder, "shutdown", None)
|
|
1321
|
-
if callable(shutdown):
|
|
1322
|
-
shutdown(timeout=1.0)
|
|
1323
|
-
else:
|
|
1324
|
-
unload = getattr(embedder, "unload", None)
|
|
1325
|
-
if not callable(unload):
|
|
1326
|
-
unload = None
|
|
1327
|
-
if not callable(shutdown) and callable(unload):
|
|
1328
|
-
try:
|
|
1329
|
-
unload(timeout=1.0)
|
|
1330
|
-
except TypeError:
|
|
1331
|
-
# Ollama and third-party embedders may still expose
|
|
1332
|
-
# the legacy no-argument unload hook.
|
|
1333
|
-
unload()
|
|
1334
|
-
except Exception:
|
|
1335
|
-
logger.warning("engine cleanup: embedder unload failed", exc_info=True)
|
|
1336
|
-
|
|
1337
|
-
db = getattr(self, "_db", None)
|
|
1338
|
-
self._db = None
|
|
1339
|
-
if db is not None:
|
|
1340
|
-
try:
|
|
1341
|
-
from superlocalmemory.core.recall_pipeline import release_recall_resources
|
|
1342
|
-
|
|
1343
|
-
release_recall_resources(db)
|
|
1344
|
-
except Exception:
|
|
1345
|
-
logger.warning("engine cleanup: recall resources release failed", exc_info=True)
|
|
1346
|
-
try:
|
|
1347
|
-
db.close()
|
|
1348
|
-
except Exception:
|
|
1349
|
-
logger.warning("engine cleanup: database close failed", exc_info=True)
|
|
1350
|
-
self._initialized = False
|
|
1351
|
-
|
|
1352
|
-
@property
|
|
1353
|
-
def profile_id(self) -> str:
|
|
1354
|
-
return self._profile_id
|
|
1355
|
-
|
|
1356
|
-
@profile_id.setter
|
|
1357
|
-
def profile_id(self, value: str) -> None:
|
|
1358
|
-
self._profile_id = value
|
|
1359
|
-
|
|
1360
|
-
@property
|
|
1361
|
-
def fact_count(self) -> int:
|
|
1362
|
-
self._ensure_init()
|
|
1363
|
-
return self._db.get_fact_count(self._profile_id)
|
|
1364
|
-
|
|
1365
|
-
# -- Internal -----------------------------------------------------------
|
|
1366
|
-
|
|
1367
|
-
def _check_embedding_migration(self) -> None:
|
|
1368
|
-
"""Detect embedding model change and re-index if needed."""
|
|
1369
|
-
try:
|
|
1370
|
-
from superlocalmemory.storage.embedding_migrator import (
|
|
1371
|
-
check_embedding_migration,
|
|
1372
|
-
run_embedding_migration,
|
|
1373
|
-
)
|
|
1374
|
-
if check_embedding_migration(self._config):
|
|
1375
|
-
count = run_embedding_migration(
|
|
1376
|
-
self._config, self._db, self._embedder,
|
|
1377
|
-
)
|
|
1378
|
-
if count > 0:
|
|
1379
|
-
logger.info(
|
|
1380
|
-
"Embedding migration: %d facts re-embedded", count,
|
|
1381
|
-
)
|
|
1382
|
-
except Exception as exc:
|
|
1383
|
-
logger.warning("Embedding migration check failed: %s", exc)
|
|
1384
|
-
|
|
1385
|
-
def _ensure_init(self) -> None:
|
|
1386
|
-
if not self._initialized:
|
|
1387
|
-
self.initialize()
|
|
1388
|
-
|
|
1389
|
-
def _require_full(self, operation: str) -> None:
|
|
1390
|
-
if self._capabilities is not Capabilities.FULL:
|
|
1391
|
-
raise CapabilityError(
|
|
1392
|
-
f"{operation} requires a FULL MemoryEngine but this instance "
|
|
1393
|
-
f"is LIGHT; route through WorkerPool (pool.{operation}) or "
|
|
1394
|
-
f"construct MemoryEngine(config, capabilities=Capabilities.FULL)."
|
|
1395
|
-
)
|