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,2204 +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 — Configuration.
|
|
6
|
-
|
|
7
|
-
Unified configuration with Mode A/B/C capability matrix.
|
|
8
|
-
Clean — zero dead options, every config has a consumer.
|
|
9
|
-
|
|
10
|
-
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
from __future__ import annotations
|
|
14
|
-
|
|
15
|
-
import logging
|
|
16
|
-
from dataclasses import asdict, dataclass, field
|
|
17
|
-
from pathlib import Path
|
|
18
|
-
|
|
19
|
-
from superlocalmemory.infra.data_root import DynamicStatePath, canonical_data_root
|
|
20
|
-
from superlocalmemory.storage.models import Mode
|
|
21
|
-
|
|
22
|
-
logger = logging.getLogger(__name__)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
# ---------------------------------------------------------------------------
|
|
26
|
-
# Canonical limits — single source of truth across all surfaces
|
|
27
|
-
# ---------------------------------------------------------------------------
|
|
28
|
-
|
|
29
|
-
#: Default number of results returned by recall across MCP, CLI, daemon, and
|
|
30
|
-
#: engine. All surfaces bind their ``limit`` defaults to this constant so a
|
|
31
|
-
#: single change is sufficient to keep the full stack in sync. Search is a
|
|
32
|
-
#: retrieval operation too and binds here: asking the same question through
|
|
33
|
-
#: ``recall`` and through ``search`` must not return differently sized answers.
|
|
34
|
-
CANONICAL_RECALL_LIMIT: int = 20
|
|
35
|
-
|
|
36
|
-
#: Default number of rows returned when listing the newest memories rather than
|
|
37
|
-
#: answering a question — MCP ``list_recent`` and ``slm list``. Separate from
|
|
38
|
-
#: the recall limit because the two can legitimately diverge; equal today.
|
|
39
|
-
CANONICAL_LIST_LIMIT: int = 20
|
|
40
|
-
|
|
41
|
-
#: Page size for the HTTP memory browser (``GET /api/memories``), which is a
|
|
42
|
-
#: paged table rather than an answer. Deliberately larger than the list limit:
|
|
43
|
-
#: a person scrolling a table wants a fuller page than an agent asking what
|
|
44
|
-
#: happened recently. The ceiling on that endpoint is enforced separately.
|
|
45
|
-
BROWSE_PAGE_SIZE: int = 50
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# ---------------------------------------------------------------------------
|
|
49
|
-
# Default Paths
|
|
50
|
-
# ---------------------------------------------------------------------------
|
|
51
|
-
|
|
52
|
-
DEFAULT_BASE_DIR = DynamicStatePath()
|
|
53
|
-
DEFAULT_DB_NAME = "memory.db"
|
|
54
|
-
DEFAULT_PROFILES_FILE = "profiles.json"
|
|
55
|
-
CURRENT_MODE_FILE = "current_mode"
|
|
56
|
-
# Populated lazily in _get_mode_config_path() to avoid circular imports
|
|
57
|
-
_MODE_CONFIG_NAMES: dict | None = None
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def _runtime_base_dir() -> Path:
|
|
61
|
-
"""Resolve the process namespace when a config root is not explicit."""
|
|
62
|
-
return canonical_data_root()
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
# ---------------------------------------------------------------------------
|
|
66
|
-
# Embedding Config
|
|
67
|
-
# ---------------------------------------------------------------------------
|
|
68
|
-
|
|
69
|
-
@dataclass(frozen=True)
|
|
70
|
-
class EmbeddingConfig:
|
|
71
|
-
"""Embedding model configuration per mode."""
|
|
72
|
-
|
|
73
|
-
model_name: str = "nomic-ai/nomic-embed-text-v1.5"
|
|
74
|
-
dimension: int = 768
|
|
75
|
-
# Provider: "" = auto-detect, "sentence-transformers", "ollama", "cloud",
|
|
76
|
-
# "openai" (V3.4.24: any OpenAI-compatible /v1/embeddings endpoint)
|
|
77
|
-
provider: str = ""
|
|
78
|
-
# Ollama settings (used when provider="ollama" or auto-detected)
|
|
79
|
-
ollama_model: str = "nomic-embed-text"
|
|
80
|
-
ollama_base_url: str = "http://localhost:11434"
|
|
81
|
-
# Azure / cloud settings (Mode C only)
|
|
82
|
-
api_endpoint: str = ""
|
|
83
|
-
api_key: str = ""
|
|
84
|
-
api_version: str = "2024-02-01"
|
|
85
|
-
deployment_name: str = ""
|
|
86
|
-
|
|
87
|
-
@property
|
|
88
|
-
def is_cloud(self) -> bool:
|
|
89
|
-
if self.provider == "openai":
|
|
90
|
-
return False
|
|
91
|
-
return bool(self.api_endpoint) or self.provider == "cloud"
|
|
92
|
-
|
|
93
|
-
@property
|
|
94
|
-
def is_ollama(self) -> bool:
|
|
95
|
-
return self.provider == "ollama"
|
|
96
|
-
|
|
97
|
-
@property
|
|
98
|
-
def is_openai_compatible(self) -> bool:
|
|
99
|
-
"""V3.4.24: True when using a custom OpenAI-compatible endpoint."""
|
|
100
|
-
return self.provider == "openai" and bool(self.api_endpoint)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
# ---------------------------------------------------------------------------
|
|
104
|
-
# LLM Config
|
|
105
|
-
# ---------------------------------------------------------------------------
|
|
106
|
-
|
|
107
|
-
@dataclass(frozen=True)
|
|
108
|
-
class LLMConfig:
|
|
109
|
-
"""LLM provider configuration per mode.
|
|
110
|
-
|
|
111
|
-
Frozen (restored in 4.0.6). ``frozen=True`` also generates ``__hash__``;
|
|
112
|
-
dropping it makes every LLMConfig unhashable, which breaks any set/dict-key
|
|
113
|
-
or cache use at runtime without necessarily failing a test. Updates build a
|
|
114
|
-
NEW instance via ``dataclasses.replace`` and assign it to the mutable
|
|
115
|
-
SLMConfig — see ``v3_api.apply_settings_update`` — so immutability here
|
|
116
|
-
costs nothing.
|
|
117
|
-
|
|
118
|
-
All production code creates new instances via LLMConfig(...) or
|
|
119
|
-
dataclasses.replace().
|
|
120
|
-
"""
|
|
121
|
-
|
|
122
|
-
provider: str = "" # "" = no LLM, "ollama", "azure", "openai", "anthropic"
|
|
123
|
-
model: str = "" # Model name/deployment
|
|
124
|
-
api_key: str = ""
|
|
125
|
-
api_base: str = ""
|
|
126
|
-
temperature: float = 0.0 # Deterministic by default
|
|
127
|
-
max_tokens: int = 4096
|
|
128
|
-
timeout_seconds: float = 60.0
|
|
129
|
-
|
|
130
|
-
@property
|
|
131
|
-
def is_available(self) -> bool:
|
|
132
|
-
return bool(self.provider)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
# ---------------------------------------------------------------------------
|
|
136
|
-
# Channel Weights
|
|
137
|
-
# ---------------------------------------------------------------------------
|
|
138
|
-
|
|
139
|
-
@dataclass
|
|
140
|
-
class ChannelWeights:
|
|
141
|
-
"""Retrieval channel weights — 5 channels, query-adaptive."""
|
|
142
|
-
|
|
143
|
-
# Semantic should dominate for conversational retrieval (paraphrase matters most).
|
|
144
|
-
semantic: float = 1.5
|
|
145
|
-
bm25: float = 1.0
|
|
146
|
-
entity_graph: float = 1.0
|
|
147
|
-
temporal: float = 1.0
|
|
148
|
-
spreading_activation: float = 1.0 # Phase 3: 5th channel (BC-08: default value)
|
|
149
|
-
hopfield: float = 0.8 # Phase G: 6th channel (Hopfield associative memory)
|
|
150
|
-
|
|
151
|
-
def as_dict(self) -> dict[str, float]:
|
|
152
|
-
return {
|
|
153
|
-
"semantic": self.semantic,
|
|
154
|
-
"bm25": self.bm25,
|
|
155
|
-
"entity_graph": self.entity_graph,
|
|
156
|
-
"temporal": self.temporal,
|
|
157
|
-
"spreading_activation": self.spreading_activation,
|
|
158
|
-
"hopfield": self.hopfield,
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
# ---------------------------------------------------------------------------
|
|
163
|
-
# Scope Weights
|
|
164
|
-
# ---------------------------------------------------------------------------
|
|
165
|
-
|
|
166
|
-
@dataclass
|
|
167
|
-
class ScopeWeights:
|
|
168
|
-
"""RRF fusion weights for multi-scope retrieval.
|
|
169
|
-
|
|
170
|
-
Personal scope has highest weight (most relevant to current profile).
|
|
171
|
-
Shared scope has medium weight (team/group memories).
|
|
172
|
-
Global scope has lowest weight (public/common knowledge).
|
|
173
|
-
"""
|
|
174
|
-
|
|
175
|
-
personal: float = 1.0
|
|
176
|
-
shared: float = 0.7
|
|
177
|
-
global_: float = 0.5 # trailing underscore avoids Python keyword
|
|
178
|
-
|
|
179
|
-
def __post_init__(self) -> None:
|
|
180
|
-
for name in ("personal", "shared", "global_"):
|
|
181
|
-
val = getattr(self, name)
|
|
182
|
-
if val < 0:
|
|
183
|
-
raise ValueError(f"ScopeWeights values must be non-negative, got {name}={val}")
|
|
184
|
-
|
|
185
|
-
def as_dict(self) -> dict[str, float]:
|
|
186
|
-
return {"personal": self.personal, "shared": self.shared, "global": self.global_}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
# ---------------------------------------------------------------------------
|
|
190
|
-
# Scope Config (multi-scope memory behaviour defaults)
|
|
191
|
-
# ---------------------------------------------------------------------------
|
|
192
|
-
|
|
193
|
-
_VALID_SCOPES = ("personal", "shared", "global")
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
@dataclass
|
|
197
|
-
class ScopeConfig:
|
|
198
|
-
"""User-facing defaults for multi-scope (shared) memory.
|
|
199
|
-
|
|
200
|
-
SHARED MEMORY IS OPT-IN — NOT a default feature (v3.6.15 product decision).
|
|
201
|
-
The defaults below make a fresh / unconfigured install behave EXACTLY like
|
|
202
|
-
3.6.14: every write is ``personal`` and recall returns only this profile's
|
|
203
|
-
own facts. Another profile's ``global``/``shared`` facts never leak into
|
|
204
|
-
recall until the user explicitly turns sharing on.
|
|
205
|
-
|
|
206
|
-
- ``default_scope='personal'`` → writes stay private by default;
|
|
207
|
-
- ``recall_include_global=False`` → don't surface other profiles' global;
|
|
208
|
-
- ``recall_include_shared=False`` → don't surface facts shared *to* me.
|
|
209
|
-
|
|
210
|
-
Turning it on is a deliberate act, done either per-call (``--scope`` /
|
|
211
|
-
``--include-global`` / MCP args) or persistently by editing config.json /
|
|
212
|
-
mode_a|b|c.json (the installer can also write the choice). The CLI/MCP
|
|
213
|
-
boundary passes ``None`` ("not specified") so the engine resolves these
|
|
214
|
-
config values as the effective default.
|
|
215
|
-
"""
|
|
216
|
-
|
|
217
|
-
default_scope: str = "personal" # scope assigned to new memories
|
|
218
|
-
recall_include_global: bool = False # surface scope='global' facts in recall
|
|
219
|
-
recall_include_shared: bool = False # surface scope='shared' facts in recall
|
|
220
|
-
|
|
221
|
-
def __post_init__(self) -> None:
|
|
222
|
-
if self.default_scope not in _VALID_SCOPES:
|
|
223
|
-
raise ValueError(
|
|
224
|
-
f"default_scope must be one of {_VALID_SCOPES}, got {self.default_scope!r}"
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
def as_dict(self) -> dict:
|
|
228
|
-
return {
|
|
229
|
-
"default_scope": self.default_scope,
|
|
230
|
-
"recall_include_global": self.recall_include_global,
|
|
231
|
-
"recall_include_shared": self.recall_include_shared,
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
# ---------------------------------------------------------------------------
|
|
236
|
-
# Encoding Config
|
|
237
|
-
# ---------------------------------------------------------------------------
|
|
238
|
-
|
|
239
|
-
@dataclass
|
|
240
|
-
class EncodingConfig:
|
|
241
|
-
"""Configuration for the encoding (memory creation) pipeline."""
|
|
242
|
-
|
|
243
|
-
# Fact extraction
|
|
244
|
-
chunk_size: int = 10 # Conversation turns per extraction chunk
|
|
245
|
-
max_facts_per_chunk: int = 10 # V3.3.11: increased from 5 to preserve more details
|
|
246
|
-
min_fact_confidence: float = 0.3
|
|
247
|
-
|
|
248
|
-
# Entity resolution
|
|
249
|
-
entity_similarity_threshold: float = 0.85
|
|
250
|
-
max_entity_candidates: int = 10
|
|
251
|
-
|
|
252
|
-
# Graph construction
|
|
253
|
-
semantic_edge_top_k: int = 5 # Top-K semantic edges per new fact
|
|
254
|
-
temporal_edge_window_hours: int = 168 # 1 week
|
|
255
|
-
|
|
256
|
-
# Consolidation
|
|
257
|
-
consolidation_similarity_threshold: float = 0.85
|
|
258
|
-
max_consolidation_candidates: int = 5
|
|
259
|
-
|
|
260
|
-
# Entropy gate
|
|
261
|
-
entropy_threshold: float = 0.95
|
|
262
|
-
|
|
263
|
-
# Entity reflexion (Wave Q1) — Mode B/C self-review of extracted entities.
|
|
264
|
-
# Fail-open; one extra bounded LLM call per chunk. Mode A is unaffected.
|
|
265
|
-
enable_entity_reflexion: bool = True
|
|
266
|
-
reflexion_max_facts: int = 8
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
# ---------------------------------------------------------------------------
|
|
270
|
-
# Retrieval Config
|
|
271
|
-
# ---------------------------------------------------------------------------
|
|
272
|
-
|
|
273
|
-
@dataclass
|
|
274
|
-
class RetrievalConfig:
|
|
275
|
-
"""Configuration for the retrieval (recall) pipeline."""
|
|
276
|
-
|
|
277
|
-
# Fusion
|
|
278
|
-
rrf_k: int = 15 # RRF smoothing constant (k=15 for candidate pools of 50-200)
|
|
279
|
-
top_k: int = 20 # Final results to return
|
|
280
|
-
|
|
281
|
-
# Per-channel
|
|
282
|
-
semantic_top_k: int = 50 # ANN pre-filter candidates
|
|
283
|
-
bm25_top_k: int = 50
|
|
284
|
-
entity_graph_max_hops: int = 3
|
|
285
|
-
temporal_proximity_days: int = 30
|
|
286
|
-
|
|
287
|
-
# Reranking (V3.3.2: ONNX backend enabled for all modes)
|
|
288
|
-
# V3.4.2: Tested gte-reranker-modernbert-base (8K context) — REGRESSED
|
|
289
|
-
# LoCoMo from 68.4% to 64.1%. Reverted to MiniLM-L-12-v2. The 512-token
|
|
290
|
-
# limit is acceptable because SLM's multi-producer retrieval pre-filters
|
|
291
|
-
# relevant facts before reranking. See bench-v342-locomo.md.
|
|
292
|
-
use_cross_encoder: bool = True
|
|
293
|
-
cross_encoder_model: str = "cross-encoder/ms-marco-MiniLM-L-12-v2"
|
|
294
|
-
# "" = PyTorch (~500MB stable), "onnx" = ONNX (leaks on ARM64 CoreML),
|
|
295
|
-
# "openai"/"remote" = v3.8.12 (issue #105) OpenAI-compatible /v1/rerank
|
|
296
|
-
# endpoint (llama-server, TEI, Infinity, vLLM, Cohere-shaped services).
|
|
297
|
-
cross_encoder_backend: str = ""
|
|
298
|
-
|
|
299
|
-
# v3.8.12 (issue #105): remote reranker endpoint. The bundled default
|
|
300
|
-
# cross-encoder (ms-marco-MiniLM-L-12-v2) is ENGLISH-ONLY, so non-English
|
|
301
|
-
# deployments scored their own language with a model that cannot read it.
|
|
302
|
-
# Pointing this at a multilingual reranker (bge-reranker-v2-m3, a Qwen
|
|
303
|
-
# reranker, …) is the same escape hatch remote embeddings got in v3.4.24.
|
|
304
|
-
#
|
|
305
|
-
# This key was accepted-and-ignored before 3.8.12 (issue #103): it was not
|
|
306
|
-
# a dataclass field, so ``SLMConfig.load`` filtered it out without a word.
|
|
307
|
-
# It is now read, validated, and — when it disagrees with the backend —
|
|
308
|
-
# reported as a loud configuration error instead of nothing at all.
|
|
309
|
-
cross_encoder_endpoint: str = ""
|
|
310
|
-
# Optional bearer token. SLM_CROSS_ENCODER_API_KEY takes precedence.
|
|
311
|
-
# When persisted for backward compatibility, config.json is atomically
|
|
312
|
-
# forced to owner-only mode 0600 by save().
|
|
313
|
-
cross_encoder_api_key: str = ""
|
|
314
|
-
# Per-request read budget for the remote reranker. Recall is interactive,
|
|
315
|
-
# so this stays tight: a slow reranker degrades to fusion scores rather
|
|
316
|
-
# than holding the recall open.
|
|
317
|
-
cross_encoder_timeout_seconds: float = 15.0
|
|
318
|
-
|
|
319
|
-
# v3.9.x (issue #112): plain-HTTP trust for private-LAN reranker endpoints.
|
|
320
|
-
# When True (the default), numeric RFC1918/ULA/link-local addresses may use
|
|
321
|
-
# plain HTTP — the same security model as the local reranker, where memory
|
|
322
|
-
# text only crosses the loopback. Set to False in hardened deployments
|
|
323
|
-
# (zero-trust networks, shared colocation) to require HTTPS for all
|
|
324
|
-
# non-loopback hosts, including private-LAN addresses.
|
|
325
|
-
#
|
|
326
|
-
# THREAT MODEL: setting this True does NOT prevent a MITM attack by an
|
|
327
|
-
# adversary on the same physical LAN (e.g. ARP spoofing). This flag means
|
|
328
|
-
# "my LAN is under my control and I accept that residual risk." It is not
|
|
329
|
-
# a claim that private-LAN traffic is cryptographically secure.
|
|
330
|
-
trust_plain_http_lan: bool = True
|
|
331
|
-
|
|
332
|
-
@property
|
|
333
|
-
def is_remote_cross_encoder(self) -> bool:
|
|
334
|
-
"""True when reranking is served by a remote HTTP endpoint."""
|
|
335
|
-
from superlocalmemory.retrieval.remote_reranker import (
|
|
336
|
-
is_remote_cross_encoder_backend,
|
|
337
|
-
)
|
|
338
|
-
|
|
339
|
-
return (
|
|
340
|
-
is_remote_cross_encoder_backend(self.cross_encoder_backend)
|
|
341
|
-
and bool(self.cross_encoder_endpoint)
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
# Agentic (Mode C only)
|
|
345
|
-
agentic_max_rounds: int = 3
|
|
346
|
-
agentic_confidence_threshold: float = 0.3
|
|
347
|
-
|
|
348
|
-
# v3.8.2: Client-driven agentic (the recall spine's global flag).
|
|
349
|
-
# The agent hot path (CLI / MCP / plugins) is consumed by a frontier
|
|
350
|
-
# LLM (Claude Code, Copilot, Codex, …) that reformulates multi-hop /
|
|
351
|
-
# low-confidence queries far better than the local Ollama model. So the
|
|
352
|
-
# hot path DELEGATES the agentic reformulation loop to that calling LLM:
|
|
353
|
-
# it returns fast local retrieval (all six channels + reranker) plus the
|
|
354
|
-
# confidence signals (no_confident_match / answer_confidence / abstained),
|
|
355
|
-
# and never spends an internal LLM round unless a caller asks for it.
|
|
356
|
-
# True (default) → hot path skips the internal agentic round
|
|
357
|
-
# (equivalent to fast=True) and the client drives
|
|
358
|
-
# refinement. Consistent ~1.5-2s recall, no LLM tail.
|
|
359
|
-
# False → hot path always runs the internal agentic round
|
|
360
|
-
# when a query looks multi-hop / low-confidence
|
|
361
|
-
# (equivalent to fast=False) — for deployments with
|
|
362
|
-
# no smart client in front of SLM.
|
|
363
|
-
# This ONLY sets the default when a caller does not pass ``fast``
|
|
364
|
-
# explicitly. The dashboard human path (search-all → cluster summary)
|
|
365
|
-
# passes fast=False directly and always keeps internal synthesis.
|
|
366
|
-
# Env kill-switch: SLM_HOT_PATH_INTERNAL_AGENTIC=1 forces internal-on.
|
|
367
|
-
client_driven_agentic: bool = True
|
|
368
|
-
|
|
369
|
-
# Spreading activation
|
|
370
|
-
spreading_activation_decay: float = 0.7
|
|
371
|
-
spreading_activation_threshold: float = 0.1
|
|
372
|
-
|
|
373
|
-
# Hopfield (Phase G: 6th channel)
|
|
374
|
-
hopfield_top_k: int = 50
|
|
375
|
-
|
|
376
|
-
# Trust weighting — apply Bayesian trust scores to retrieval ranking.
|
|
377
|
-
# When enabled, each fact's score is multiplied by a trust weight in [0.5, 1.5].
|
|
378
|
-
# Low-trust facts are demoted; high-trust facts are promoted.
|
|
379
|
-
# Default trust = 1.0 (no effect when no trust data exists).
|
|
380
|
-
use_trust_weighting: bool = True
|
|
381
|
-
|
|
382
|
-
# Ablation channel control for experiments.
|
|
383
|
-
# List of channel names to SKIP during retrieval (e.g., ["bm25", "entity_graph"]).
|
|
384
|
-
# Used by s19_runner for ablation experiments. Empty = all channels active.
|
|
385
|
-
disabled_channels: list[str] = field(default_factory=list)
|
|
386
|
-
|
|
387
|
-
# v3.6.6: Evidence floor — gate on per-channel scores, not fused/RRF score.
|
|
388
|
-
# Nonsense queries earn 0.0 on every primary channel; real matches earn
|
|
389
|
-
# semantic >= 0.85 or bm25 > 0. The discriminator is earned channel evidence.
|
|
390
|
-
# Env kill-switch: SLM_RECALL_NO_FLOOR=1 disables without release.
|
|
391
|
-
evidence_floor_enabled: bool = True
|
|
392
|
-
min_semantic_evidence: float = 0.60 # Minimum cosine similarity to keep a result
|
|
393
|
-
|
|
394
|
-
# v3.6.6: Recall output budget — protect consuming agents from 585KB responses.
|
|
395
|
-
recall_per_fact_max_chars: int = 2400 # ~600 tokens; head 70% + tail 30%
|
|
396
|
-
recall_total_max_chars: int = 12000 # ~3K tokens; stubs beyond this
|
|
397
|
-
|
|
398
|
-
# Wave Q2b: attach a precomputed community summary as thematic context when
|
|
399
|
-
# the top results cluster in one community. Read-only lookup, gated, and
|
|
400
|
-
# fail-open — never a per-query LLM call. Kill-switch for tuning.
|
|
401
|
-
enable_community_context: bool = True
|
|
402
|
-
|
|
403
|
-
# Recency query strategy.
|
|
404
|
-
# When False, queries matching present-activity phrases fall through to
|
|
405
|
-
# factual routing — one-line rollback without touching strategy.py.
|
|
406
|
-
enable_recency_strategy: bool = True
|
|
407
|
-
# Strength of the recency prior applied during final-score fusion.
|
|
408
|
-
# 0.0 = no recency bias (restores previous ranking exactly).
|
|
409
|
-
# Consumed by a separate ranking stage; this field makes the knob
|
|
410
|
-
# configurable without further edits to this class.
|
|
411
|
-
recency_prior_strength: float = 0.5
|
|
412
|
-
# A fact written moments ago has no embedding yet, so it scores zero on the
|
|
413
|
-
# semantic channel — the same score a genuinely unrelated fact gets. Inside
|
|
414
|
-
# this window that zero is read as "not computed yet", not as "unrelated".
|
|
415
|
-
write_recency_floor_enabled: bool = True
|
|
416
|
-
write_recency_floor_minutes: float = 60.0
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
# ---------------------------------------------------------------------------
|
|
420
|
-
# Math Config
|
|
421
|
-
# ---------------------------------------------------------------------------
|
|
422
|
-
|
|
423
|
-
@dataclass
|
|
424
|
-
class MathConfig:
|
|
425
|
-
"""Configuration for mathematical layers."""
|
|
426
|
-
|
|
427
|
-
# Fisher-Rao
|
|
428
|
-
fisher_temperature: float = 15.0
|
|
429
|
-
fisher_bayesian_update: bool = True
|
|
430
|
-
# "simplified" = local Mahalanobis-like (fast, existing behaviour)
|
|
431
|
-
# "full" = Atkinson-Mitchell geodesic from FisherRaoMetric class
|
|
432
|
-
fisher_mode: str = "simplified"
|
|
433
|
-
|
|
434
|
-
# Langevin
|
|
435
|
-
langevin_dt: float = 0.005
|
|
436
|
-
langevin_temperature: float = 0.3
|
|
437
|
-
langevin_persist_positions: bool = True
|
|
438
|
-
langevin_weight_range: tuple[float, float] = (0.0, 1.0)
|
|
439
|
-
|
|
440
|
-
# Hopfield
|
|
441
|
-
|
|
442
|
-
# Ebbinghaus-Langevin coupling in maintenance (Phase 5 — P1-ELC)
|
|
443
|
-
# When True, the maintenance cycle computes the combined Ebbinghaus-Langevin
|
|
444
|
-
# coupled state for each fact and writes back the lifecycle zone.
|
|
445
|
-
# Defaults to False to preserve existing maintenance behaviour.
|
|
446
|
-
ebbinghaus_langevin_coupling_enabled: bool = False
|
|
447
|
-
|
|
448
|
-
# Sheaf (at encoding time, NOT retrieval)
|
|
449
|
-
sheaf_at_encoding: bool = True
|
|
450
|
-
sheaf_contradiction_threshold: float = 0.45
|
|
451
|
-
# Max edges to check per fact during sheaf consistency.
|
|
452
|
-
# At 18K+ edges, coboundary computation becomes O(N*dim^2) and hangs.
|
|
453
|
-
# Facts with more edges than this skip sheaf check (still get contradiction
|
|
454
|
-
# detection via consolidator UPDATE/SUPERSEDE path).
|
|
455
|
-
sheaf_max_edges_per_check: int = 200
|
|
456
|
-
|
|
457
|
-
# Rate-Distortion (production only, disabled for benchmarks)
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
# ---------------------------------------------------------------------------
|
|
461
|
-
# Store Config (v3.6.6)
|
|
462
|
-
# ---------------------------------------------------------------------------
|
|
463
|
-
|
|
464
|
-
@dataclass(frozen=True)
|
|
465
|
-
class StoreConfig:
|
|
466
|
-
"""Configuration for the remember/store write path (v3.6.6).
|
|
467
|
-
|
|
468
|
-
Ingest gate: protect the DB from oversized facts and prompt-template
|
|
469
|
-
pollution. Defaults ON; env kill-switch SLM_INGEST_NO_GATE=1.
|
|
470
|
-
"""
|
|
471
|
-
|
|
472
|
-
# Max chars for the FACT content stored in atomic_facts.content.
|
|
473
|
-
# Content above this is clamped to head 70% + tail 30% + truncation marker.
|
|
474
|
-
# The FULL original is preserved in the memories table row. Set high (24K
|
|
475
|
-
# ≈ 6K tokens) so only pathological pastes are touched; normal dense
|
|
476
|
-
# session-handoff memories (6-15K chars) are stored 100% intact.
|
|
477
|
-
max_verbatim_chars: int = 24000
|
|
478
|
-
|
|
479
|
-
# Hard upper bound in bytes. Content above this is rejected outright.
|
|
480
|
-
# Nobody's "memory" is a megabyte (MCP: success=False, HTTP: 413).
|
|
481
|
-
max_ingest_bytes: int = 1_048_576 # 1 MB
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
# ---------------------------------------------------------------------------
|
|
485
|
-
# Context Injection (v3.4.65)
|
|
486
|
-
# ---------------------------------------------------------------------------
|
|
487
|
-
|
|
488
|
-
@dataclass(frozen=True)
|
|
489
|
-
class InjectionConfig:
|
|
490
|
-
"""Context-injection budgets & framing (v3.4.65).
|
|
491
|
-
|
|
492
|
-
Single source of truth for what reaches the agent at session_init,
|
|
493
|
-
prestage_context, and the recall hooks. Replaces scattered char caps.
|
|
494
|
-
|
|
495
|
-
Budgets are in *estimated tokens* (chars/4 heuristic; see
|
|
496
|
-
core/injection.estimate_tokens). Mode-aware: A=fast/cheap, C=rich.
|
|
497
|
-
"""
|
|
498
|
-
enabled: bool = True
|
|
499
|
-
|
|
500
|
-
# Mode-aware TOTAL budget (core block + recall), in estimated tokens.
|
|
501
|
-
total_budget_tokens_a: int = 2000
|
|
502
|
-
total_budget_tokens_b: int = 4000
|
|
503
|
-
total_budget_tokens_c: int = 8000
|
|
504
|
-
|
|
505
|
-
# Per-memory ceiling (estimated tokens). Whole-memory inclusion until
|
|
506
|
-
# the total budget is hit; this only clamps a single oversized memory.
|
|
507
|
-
per_memory_max_tokens: int = 600
|
|
508
|
-
|
|
509
|
-
# Core Memory Block (Letta pattern).
|
|
510
|
-
core_block_enabled: bool = True
|
|
511
|
-
core_block_max_facts: int = 5
|
|
512
|
-
core_block_max_tokens: int = 1000
|
|
513
|
-
core_block_importance_min: float = 0.8
|
|
514
|
-
core_block_min_access_count: int = 2
|
|
515
|
-
|
|
516
|
-
# Lost-in-the-middle: place strongest at top & bottom edges.
|
|
517
|
-
edge_ordering: bool = True
|
|
518
|
-
|
|
519
|
-
# Trust framing. False (shipped) = cautious "reference only" wrapper.
|
|
520
|
-
# True (Varun personal) = clean "memory context" framing.
|
|
521
|
-
# redact_secrets ALWAYS runs regardless.
|
|
522
|
-
trust_first_party: bool = False
|
|
523
|
-
|
|
524
|
-
# prestage_context response byte cap (was hardcoded 16 KB).
|
|
525
|
-
prestage_max_response_bytes: int = 64 * 1024
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
# ---------------------------------------------------------------------------
|
|
529
|
-
# Master Config
|
|
530
|
-
# ---------------------------------------------------------------------------
|
|
531
|
-
|
|
532
|
-
@dataclass(frozen=True)
|
|
533
|
-
class ConsolidationConfig:
|
|
534
|
-
"""Configuration for sleep-time consolidation (Phase 5).
|
|
535
|
-
|
|
536
|
-
Ships enabled by default. Users can disable via slm config.
|
|
537
|
-
"""
|
|
538
|
-
|
|
539
|
-
enabled: bool = True
|
|
540
|
-
step_count_trigger: int = 50 # Lightweight consolidation every N stores (L7)
|
|
541
|
-
session_trigger: bool = True # Run on session end
|
|
542
|
-
idle_timeout_seconds: int = 300 # 5 min inactivity
|
|
543
|
-
scheduled_sessions: int = 5 # Full consolidation every N sessions
|
|
544
|
-
core_memory_char_limit: int = 2000 # Total chars across all blocks
|
|
545
|
-
block_char_limit: int = 500 # Per-block character limit
|
|
546
|
-
compression_similarity: float = 0.85 # Dedup threshold for compression
|
|
547
|
-
promotion_min_access: int = 3 # Min access count for promotion
|
|
548
|
-
promotion_min_trust: float = 0.5 # Min trust for promotion
|
|
549
|
-
decay_days_threshold: int = 30 # Edge decay after N days
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
@dataclass(frozen=True)
|
|
553
|
-
class ForgettingConfig:
|
|
554
|
-
"""Ebbinghaus forgetting configuration."""
|
|
555
|
-
|
|
556
|
-
enabled: bool = True
|
|
557
|
-
# Strength coefficients
|
|
558
|
-
alpha: float = 2.0 # Access frequency weight (log scale)
|
|
559
|
-
beta: float = 1.5 # Importance weight (PageRank)
|
|
560
|
-
gamma: float = 1.0 # Confirmation count weight
|
|
561
|
-
delta: float = 0.5 # Emotional salience weight
|
|
562
|
-
# Strength bounds
|
|
563
|
-
min_strength: float = 0.1 # Floor (prevents instant forgetting)
|
|
564
|
-
max_strength: float = 100.0 # Ceiling (numerical stability)
|
|
565
|
-
# Zone thresholds
|
|
566
|
-
archive_threshold: float = 0.2 # Below this -> ARCHIVE
|
|
567
|
-
forget_threshold: float = 0.05 # Below this -> FORGOTTEN
|
|
568
|
-
# Spaced repetition
|
|
569
|
-
learning_rate: float = 1.0 # eta in spaced repetition update
|
|
570
|
-
# Coupling
|
|
571
|
-
forgetting_drift_scale: float = 0.5 # How strongly forgetting affects Langevin drift
|
|
572
|
-
# Trust-weighted forgetting (Paper 3, Section 5.5)
|
|
573
|
-
trust_kappa: float = 2.0 # Sensitivity: lambda_eff = lambda * (1 + trust_kappa * (1 - tau))
|
|
574
|
-
# Scheduler
|
|
575
|
-
scheduler_interval_minutes: int = 30 # How often to recompute retentions
|
|
576
|
-
# Immunity
|
|
577
|
-
core_memory_immune: bool = True # Core Memory blocks never forget
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
@dataclass(frozen=True)
|
|
581
|
-
class HopfieldConfig:
|
|
582
|
-
"""Modern Continuous Hopfield Network configuration (Ramsauer et al., 2020).
|
|
583
|
-
|
|
584
|
-
Energy: E(xi) = -log(sum_i exp(B * xi' * x_i)) + B/2 * ||xi||^2
|
|
585
|
-
Update: xi_new = X' @ softmax(B * X @ xi)
|
|
586
|
-
Beta: B = 1/sqrt(d) where d = dimension
|
|
587
|
-
Storage capacity: O(e^{d/2}) -- exponential in dimension.
|
|
588
|
-
"""
|
|
589
|
-
|
|
590
|
-
enabled: bool = True
|
|
591
|
-
dimension: int = 768
|
|
592
|
-
max_iterations: int = 1
|
|
593
|
-
convergence_epsilon: float = 1e-6
|
|
594
|
-
prefilter_threshold: int = 10_000
|
|
595
|
-
# How many candidates the index hands to this stage. This stage decides final
|
|
596
|
-
# membership, so a fact ranked past this number by the index never reaches it
|
|
597
|
-
# and can never be returned — the size of this pool is a direct limit on which
|
|
598
|
-
# memories are reachable at all.
|
|
599
|
-
#
|
|
600
|
-
#
|
|
601
|
-
# Chosen by measurement against the 2 s recall ceiling, on a 517 MB store:
|
|
602
|
-
# 150 -> p95 998 ms, 0 channel timeouts
|
|
603
|
-
# 300 -> p95 1054 ms, 0
|
|
604
|
-
# 500 -> p95 1140 ms, 0 <- here: 3.3x the reachability of 150 for +142 ms
|
|
605
|
-
# 750 -> p95 1278 ms, 0
|
|
606
|
-
# 1000 -> p95 1947-2737 ms, 2-25 timeouts
|
|
607
|
-
#
|
|
608
|
-
# 1000 is excluded twice over: it breaches the ceiling, and a channel that
|
|
609
|
-
# misses its deadline contributes nothing to fusion — so whether it lands
|
|
610
|
-
# varies with machine load and the same question stops returning the same
|
|
611
|
-
# answer. 500 keeps ~860 ms of headroom, which is several times the observed
|
|
612
|
-
# run-to-run variance, and stays clear of the region where the cost curve
|
|
613
|
-
# turned non-linear for reasons that were never explained.
|
|
614
|
-
prefilter_candidates: int = 500
|
|
615
|
-
skip_threshold: int = 100_000
|
|
616
|
-
cache_ttl_seconds: float = 60.0
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
@dataclass(frozen=True)
|
|
620
|
-
class ReaperConfig:
|
|
621
|
-
"""Process health & stale reaper configuration (Phase H0).
|
|
622
|
-
|
|
623
|
-
Prevents zombie SLM processes from exhausting RAM.
|
|
624
|
-
"""
|
|
625
|
-
|
|
626
|
-
enabled: bool = True
|
|
627
|
-
heartbeat_interval_seconds: int = 60
|
|
628
|
-
orphan_age_threshold_hours: float = 4.0
|
|
629
|
-
pid_file_path: str = ""
|
|
630
|
-
graceful_timeout_seconds: float = 5.0
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
@dataclass(frozen=True)
|
|
634
|
-
class PolarQuantConfig:
|
|
635
|
-
"""PolarQuant embedding quantization configuration.
|
|
636
|
-
|
|
637
|
-
Random orthogonal rotation + recursive polar + scalar quantization.
|
|
638
|
-
Reference: TurboQuant (ICLR 2026), PolarQuant (arXiv 2502.02617).
|
|
639
|
-
"""
|
|
640
|
-
|
|
641
|
-
dimension: int = 768
|
|
642
|
-
rotation_matrix_path: str = "" # empty = ~/.superlocalmemory/polar_rotation.npy
|
|
643
|
-
seed: int = 42 # reproducible rotation matrix
|
|
644
|
-
codebook_method: str = "turbo" # "turbo" (default) or "polar_legacy"
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
@dataclass(frozen=True)
|
|
648
|
-
class QJLConfig:
|
|
649
|
-
"""QJL 1-bit residual correction configuration.
|
|
650
|
-
|
|
651
|
-
Random projection + sign-bit quantization for asymmetric IP estimation.
|
|
652
|
-
Reference: QJL (AAAI 2025, arXiv 2406.03482).
|
|
653
|
-
"""
|
|
654
|
-
|
|
655
|
-
projection_dim: int = 128
|
|
656
|
-
seed: int = 43 # separate from PolarQuant
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
@dataclass(frozen=True)
|
|
660
|
-
class QuantizationConfig:
|
|
661
|
-
"""Memory-aware embedding quantization (EAP + LP2E).
|
|
662
|
-
|
|
663
|
-
Couples Ebbinghaus retention to embedding precision.
|
|
664
|
-
"""
|
|
665
|
-
|
|
666
|
-
enabled: bool = True
|
|
667
|
-
polar: PolarQuantConfig = field(default_factory=PolarQuantConfig)
|
|
668
|
-
qjl: QJLConfig = field(default_factory=QJLConfig)
|
|
669
|
-
default_bit_width: int = 32
|
|
670
|
-
eap_enabled: bool = True
|
|
671
|
-
keep_float32_backup: bool = True
|
|
672
|
-
auto_compact_interval_hours: int = 6
|
|
673
|
-
polar_search_penalty: float = 0.97 # V3.3.8: 0.95→0.97, TurboQuant has lower MSE
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
@dataclass(frozen=True)
|
|
677
|
-
class CCQConfig:
|
|
678
|
-
"""Cognitive Consolidation Quantization configuration (Phase E).
|
|
679
|
-
|
|
680
|
-
Ships enabled by default. CCQ runs as Step 7 of the consolidation cycle.
|
|
681
|
-
Biological analogy: sleep-time hippocampal-neocortical transfer.
|
|
682
|
-
"""
|
|
683
|
-
|
|
684
|
-
enabled: bool = True
|
|
685
|
-
|
|
686
|
-
# Candidate identification
|
|
687
|
-
retention_threshold: float = 0.5
|
|
688
|
-
max_candidates_per_run: int = 200
|
|
689
|
-
|
|
690
|
-
# Clustering
|
|
691
|
-
min_entity_overlap: int = 2
|
|
692
|
-
temporal_window_days: int = 7
|
|
693
|
-
min_cluster_size: int = 3
|
|
694
|
-
max_cluster_size: int = 20
|
|
695
|
-
|
|
696
|
-
# Gist extraction
|
|
697
|
-
use_llm_gist: bool = True
|
|
698
|
-
max_gist_chars: int = 500
|
|
699
|
-
min_entity_coverage: float = 0.5
|
|
700
|
-
|
|
701
|
-
# Embedding compression
|
|
702
|
-
target_bit_width: int = 2
|
|
703
|
-
compress_embeddings: bool = True
|
|
704
|
-
|
|
705
|
-
# Scheduling
|
|
706
|
-
store_count_trigger: int = 100
|
|
707
|
-
run_on_session_end: bool = True
|
|
708
|
-
|
|
709
|
-
# Safety
|
|
710
|
-
core_memory_immune: bool = True
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
@dataclass(frozen=True)
|
|
714
|
-
class SAGQConfig:
|
|
715
|
-
"""Spreading Activation-Guided Quantization configuration.
|
|
716
|
-
|
|
717
|
-
Centrality formula:
|
|
718
|
-
centrality(i) = w_pagerank * pr_norm + w_degree * deg_norm + w_sa_freq * sa_freq_norm
|
|
719
|
-
|
|
720
|
-
SAGQ precision:
|
|
721
|
-
sagq_bw = b_min + (b_max - b_min) * centrality, snapped to valid_bit_widths
|
|
722
|
-
|
|
723
|
-
Combined precision (with Phase A EAP):
|
|
724
|
-
final_bw = max(eap_bw, sagq_bw)
|
|
725
|
-
"""
|
|
726
|
-
|
|
727
|
-
enabled: bool = True
|
|
728
|
-
|
|
729
|
-
# Centrality weights (MUST sum to 1.0 -- validated in __post_init__)
|
|
730
|
-
w_pagerank: float = 0.5 # PageRank structural importance
|
|
731
|
-
w_degree: float = 0.3 # Degree centrality (connection count)
|
|
732
|
-
w_sa_freq: float = 0.2 # Spreading activation frequency (7-day window)
|
|
733
|
-
|
|
734
|
-
# Bit-width range
|
|
735
|
-
b_min: int = 2 # Minimum bit-width (most aggressive quantization)
|
|
736
|
-
b_max: int = 32 # Maximum bit-width (full float32 precision)
|
|
737
|
-
|
|
738
|
-
# Valid bit-widths (snapping targets) -- must be sorted ascending
|
|
739
|
-
valid_bit_widths: tuple[int, ...] = (2, 4, 8, 32)
|
|
740
|
-
|
|
741
|
-
# SA frequency window (days to look back in activation_cache)
|
|
742
|
-
sa_frequency_window_days: int = 7
|
|
743
|
-
|
|
744
|
-
# Scheduler
|
|
745
|
-
scheduler_interval_hours: float = 6.0 # How often to run combined scheduler
|
|
746
|
-
|
|
747
|
-
def __post_init__(self) -> None:
|
|
748
|
-
weight_sum = self.w_pagerank + self.w_degree + self.w_sa_freq
|
|
749
|
-
if abs(weight_sum - 1.0) > 1e-6:
|
|
750
|
-
raise ValueError(
|
|
751
|
-
f"SAGQConfig centrality weights must sum to 1.0, got {weight_sum:.6f}"
|
|
752
|
-
)
|
|
753
|
-
if not self.valid_bit_widths:
|
|
754
|
-
raise ValueError("SAGQConfig.valid_bit_widths must not be empty")
|
|
755
|
-
if self.b_min < 1:
|
|
756
|
-
raise ValueError(f"SAGQConfig.b_min must be >= 1, got {self.b_min}")
|
|
757
|
-
if self.b_max < self.b_min:
|
|
758
|
-
raise ValueError(
|
|
759
|
-
f"SAGQConfig.b_max ({self.b_max}) must be >= b_min ({self.b_min})"
|
|
760
|
-
)
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
@dataclass(frozen=True)
|
|
764
|
-
class ParameterizationConfig:
|
|
765
|
-
"""Soft prompt parameterization configuration (Phase F: The Learning Brain).
|
|
766
|
-
|
|
767
|
-
Controls pattern extraction, prompt generation, injection, and lifecycle.
|
|
768
|
-
Ships enabled by default. Pure text soft prompts — no LoRA, no weights.
|
|
769
|
-
"""
|
|
770
|
-
|
|
771
|
-
enabled: bool = True
|
|
772
|
-
|
|
773
|
-
# Pattern extraction
|
|
774
|
-
min_confidence: float = 0.7 # Minimum pattern confidence [0.3, 1.0]
|
|
775
|
-
min_evidence: int = 5 # Minimum evidence count for behavioral/workflow
|
|
776
|
-
cross_project_boost: float = 1.2 # 20% confidence boost for cross-project patterns
|
|
777
|
-
|
|
778
|
-
# Prompt generation
|
|
779
|
-
max_prompt_tokens: int = 500 # Token budget for soft prompts
|
|
780
|
-
max_memory_tokens: int = 1500 # Token budget for regular memories
|
|
781
|
-
categories_enabled: tuple[str, ...] = (
|
|
782
|
-
"identity", "tech_preference", "communication_style",
|
|
783
|
-
"workflow_pattern", "project_context", "topic_interest",
|
|
784
|
-
"decision_history", "avoidance",
|
|
785
|
-
)
|
|
786
|
-
|
|
787
|
-
# Lifecycle
|
|
788
|
-
refresh_interval_hours: float = 24.0 # Min hours between parameterization runs
|
|
789
|
-
effectiveness_tracking: bool = True # Track prompt effectiveness via feedback
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
@dataclass(frozen=True)
|
|
793
|
-
class TemporalValidatorConfig:
|
|
794
|
-
"""Configuration for temporal intelligence (Phase 4).
|
|
795
|
-
|
|
796
|
-
Ships enabled by default. Users can disable via slm config.
|
|
797
|
-
"""
|
|
798
|
-
|
|
799
|
-
enabled: bool = True
|
|
800
|
-
mode: str = "a" # "a" (sheaf), "b"/"c" (LLM)
|
|
801
|
-
|
|
802
|
-
# Sheaf contradiction threshold
|
|
803
|
-
contradiction_threshold: float = 0.45 # Mode A threshold (768d)
|
|
804
|
-
|
|
805
|
-
# P5-INT-01: superseded facts are DEMOTED in recall, not hidden. A fact
|
|
806
|
-
# marked system_expired_at keeps its channel evidence but its per-channel
|
|
807
|
-
# score is multiplied by this factor, so current facts rank above it while
|
|
808
|
-
# nothing valid silently vanishes (Mem0-2026 non-destructive design;
|
|
809
|
-
# retrieval-time recency resolves conflicts). 0.0 restores the old hide
|
|
810
|
-
# behaviour; 1.0 disables demotion.
|
|
811
|
-
superseded_demotion_factor: float = 0.25
|
|
812
|
-
|
|
813
|
-
# LLM pre-filter threshold (lower to catch more candidates)
|
|
814
|
-
llm_prefilter_threshold: float = 0.30
|
|
815
|
-
|
|
816
|
-
# Max LLM checks per new fact (cost control)
|
|
817
|
-
max_llm_checks: int = 5
|
|
818
|
-
|
|
819
|
-
# Trust penalty for expired facts
|
|
820
|
-
expiration_trust_penalty: float = -0.2
|
|
821
|
-
|
|
822
|
-
# Include expired facts in historical queries
|
|
823
|
-
include_expired_in_history: bool = True
|
|
824
|
-
|
|
825
|
-
# Event-time (valid_until) demotion factor, applied like
|
|
826
|
-
# superseded_demotion_factor but for facts whose stated validity window has
|
|
827
|
-
# elapsed. Default matches the prior hardcoded constant (0.5).
|
|
828
|
-
event_time_demotion_factor: float = 0.5
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
@dataclass(frozen=True)
|
|
832
|
-
class EvolutionConfig:
|
|
833
|
-
"""Configuration for Skill Evolution Engine (v3.4.10).
|
|
834
|
-
|
|
835
|
-
OFF by default — opt in via `slm setup` (interactive) or
|
|
836
|
-
`slm config set evolution.enabled true` (CLI). Enabling makes
|
|
837
|
-
background LLM calls; the enable flow surfaces a cost advisory.
|
|
838
|
-
|
|
839
|
-
Backend auto-detection priority:
|
|
840
|
-
1. `claude` CLI available → spawn `claude --model haiku` (ECC pattern, free)
|
|
841
|
-
2. Ollama running → use Ollama (free, local)
|
|
842
|
-
3. API key set → use Anthropic/OpenAI API (paid)
|
|
843
|
-
4. Nothing → dashboard-only (show candidates, manual evolution)
|
|
844
|
-
|
|
845
|
-
Model selection (v3.7.9): each pipeline step is configurable and
|
|
846
|
-
defaults to the lowest-cost capable model for the active backend
|
|
847
|
-
(Claude→haiku, Ollama→local). Leave a field empty ("") for "auto".
|
|
848
|
-
The blind verifier is kept on a *different* model from the generator
|
|
849
|
-
so it can't grade its own homework; see
|
|
850
|
-
``evolution.model_selection.resolve_evolution_models``.
|
|
851
|
-
"""
|
|
852
|
-
|
|
853
|
-
enabled: bool = False # OFF by default, opt-in
|
|
854
|
-
backend: str = "auto" # auto, claude, ollama, anthropic, openai
|
|
855
|
-
max_evolutions_per_cycle: int = 3 # Budget cap per consolidation
|
|
856
|
-
# Empty string == "auto" (resolve cheapest capable model at runtime).
|
|
857
|
-
# Accepts short aliases ("haiku"/"sonnet") or allow-listed model ids.
|
|
858
|
-
mutation_model: str = "" # generator (quality-sensitive)
|
|
859
|
-
verify_model: str = "" # blind verifier (must differ from generator)
|
|
860
|
-
confirm_model: str = "" # cheap yes/no gate
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
@dataclass(frozen=True)
|
|
864
|
-
class AutoInvokeConfig:
|
|
865
|
-
"""Configuration for the Auto-Invoke Engine (Phase 2).
|
|
866
|
-
|
|
867
|
-
Ships enabled by default. Users can disable via slm config.
|
|
868
|
-
|
|
869
|
-
References:
|
|
870
|
-
- SYNAPSE: FOK gating (fok_threshold = 0.12)
|
|
871
|
-
- ACT-R: base-level activation (act_r_decay = 0.5)
|
|
872
|
-
- Zep/Hindsight: multi-signal ranking consensus
|
|
873
|
-
"""
|
|
874
|
-
|
|
875
|
-
enabled: bool = True
|
|
876
|
-
profile_id: str = "default"
|
|
877
|
-
|
|
878
|
-
# Scoring weights (4-signal default) -- must sum to 1.0
|
|
879
|
-
weights: dict = field(default_factory=lambda: {
|
|
880
|
-
"similarity": 0.40,
|
|
881
|
-
"recency": 0.25,
|
|
882
|
-
"frequency": 0.20,
|
|
883
|
-
"trust": 0.15,
|
|
884
|
-
})
|
|
885
|
-
|
|
886
|
-
# ACT-R mode (3-signal alternative) -- must sum to 1.0
|
|
887
|
-
use_act_r: bool = False
|
|
888
|
-
act_r_weights: dict = field(default_factory=lambda: {
|
|
889
|
-
"similarity": 0.40,
|
|
890
|
-
"base_level": 0.35,
|
|
891
|
-
"trust": 0.25,
|
|
892
|
-
})
|
|
893
|
-
act_r_decay: float = 0.5 # Power-law decay exponent
|
|
894
|
-
|
|
895
|
-
# FOK gating (Feeling-of-Knowing)
|
|
896
|
-
fok_threshold: float = 0.12 # SYNAPSE minimum score gate
|
|
897
|
-
|
|
898
|
-
# Retrieval limits
|
|
899
|
-
max_memories_injected: int = 10
|
|
900
|
-
candidate_multiplier: int = 3 # candidates = limit * multiplier
|
|
901
|
-
|
|
902
|
-
# Mode A degradation weights -- must sum to 1.0
|
|
903
|
-
mode_a_weights: dict = field(default_factory=lambda: {
|
|
904
|
-
"similarity": 0.00,
|
|
905
|
-
"recency": 0.40,
|
|
906
|
-
"frequency": 0.35,
|
|
907
|
-
"trust": 0.25,
|
|
908
|
-
})
|
|
909
|
-
|
|
910
|
-
# Behavioral
|
|
911
|
-
include_archived: bool = False
|
|
912
|
-
relevance_threshold: float = 0.3 # Legacy compat with AutoRecall
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
# ---------------------------------------------------------------------------
|
|
916
|
-
# Deployment Config (v3.8.0)
|
|
917
|
-
# ---------------------------------------------------------------------------
|
|
918
|
-
|
|
919
|
-
_VALID_DEPLOYMENT_MODES = ("personal", "enterprise")
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
@dataclass(frozen=True)
|
|
923
|
-
class DeploymentConfig:
|
|
924
|
-
"""Deployment mode configuration.
|
|
925
|
-
|
|
926
|
-
Personal (default, safe defaults):
|
|
927
|
-
Single-user install. No login required, no PII redaction, no
|
|
928
|
-
retention scheduler. Behaviour is identical to pre-3.8.0 installs.
|
|
929
|
-
|
|
930
|
-
Enterprise:
|
|
931
|
-
Multi-user / team / company install. Login required, PII redacted,
|
|
932
|
-
retention scheduler active, audit enabled.
|
|
933
|
-
|
|
934
|
-
SAFE DEFAULTS = Personal — an existing config.toml with no [deployment]
|
|
935
|
-
section continues to behave exactly as before. Adding a [deployment]
|
|
936
|
-
section is opt-in; removing it reverts to Personal automatically.
|
|
937
|
-
"""
|
|
938
|
-
|
|
939
|
-
mode: str = "personal" # "personal" | "enterprise"
|
|
940
|
-
require_login: bool = False
|
|
941
|
-
pii_redaction: bool = False
|
|
942
|
-
retention_enabled: bool = False
|
|
943
|
-
audit: bool = True
|
|
944
|
-
|
|
945
|
-
def __post_init__(self) -> None:
|
|
946
|
-
if self.mode not in _VALID_DEPLOYMENT_MODES:
|
|
947
|
-
raise ValueError(
|
|
948
|
-
f"DeploymentConfig.mode must be one of {_VALID_DEPLOYMENT_MODES!r}, "
|
|
949
|
-
f"got {self.mode!r}"
|
|
950
|
-
)
|
|
951
|
-
|
|
952
|
-
@property
|
|
953
|
-
def is_personal(self) -> bool:
|
|
954
|
-
"""True when operating in personal (single-user) mode."""
|
|
955
|
-
return self.mode == "personal"
|
|
956
|
-
|
|
957
|
-
@property
|
|
958
|
-
def is_enterprise(self) -> bool:
|
|
959
|
-
"""True when operating in enterprise (team/company) mode."""
|
|
960
|
-
return self.mode == "enterprise"
|
|
961
|
-
|
|
962
|
-
def as_dict(self) -> dict:
|
|
963
|
-
return {
|
|
964
|
-
"mode": self.mode,
|
|
965
|
-
"require_login": self.require_login,
|
|
966
|
-
"pii_redaction": self.pii_redaction,
|
|
967
|
-
"retention_enabled": self.retention_enabled,
|
|
968
|
-
"audit": self.audit,
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
#: Canonical Personal preset — all permissive defaults.
|
|
973
|
-
DEPLOYMENT_PERSONAL = DeploymentConfig(
|
|
974
|
-
mode="personal",
|
|
975
|
-
require_login=False,
|
|
976
|
-
pii_redaction=False,
|
|
977
|
-
retention_enabled=False,
|
|
978
|
-
audit=True,
|
|
979
|
-
)
|
|
980
|
-
|
|
981
|
-
#: Canonical Enterprise preset — all enforcement defaults.
|
|
982
|
-
DEPLOYMENT_ENTERPRISE = DeploymentConfig(
|
|
983
|
-
mode="enterprise",
|
|
984
|
-
require_login=True,
|
|
985
|
-
pii_redaction=True,
|
|
986
|
-
retention_enabled=True,
|
|
987
|
-
audit=True,
|
|
988
|
-
)
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
def load_deployment_config(
|
|
992
|
-
config_toml_path: Path | None = None,
|
|
993
|
-
) -> DeploymentConfig:
|
|
994
|
-
"""Parse [deployment] from config.toml with fail-closed invalid-state handling.
|
|
995
|
-
|
|
996
|
-
config.toml is the installer-written performance config (separate from the
|
|
997
|
-
daemon's config.json managed by SLMConfig). This function reads ONLY the
|
|
998
|
-
[deployment] section. All other sections are ignored.
|
|
999
|
-
|
|
1000
|
-
Args:
|
|
1001
|
-
config_toml_path: Explicit path to config.toml. When None, resolved to
|
|
1002
|
-
``~/.superlocalmemory/config.toml`` via the canonical data root.
|
|
1003
|
-
|
|
1004
|
-
Returns:
|
|
1005
|
-
DeploymentConfig — Personal only when the file or deployment section is
|
|
1006
|
-
absent. A present but invalid deployment configuration resolves to the
|
|
1007
|
-
enterprise preset so startup controls cannot silently downgrade.
|
|
1008
|
-
"""
|
|
1009
|
-
if config_toml_path is None:
|
|
1010
|
-
try:
|
|
1011
|
-
config_toml_path = _runtime_base_dir() / "config.toml"
|
|
1012
|
-
except Exception:
|
|
1013
|
-
return DEPLOYMENT_PERSONAL
|
|
1014
|
-
|
|
1015
|
-
if not config_toml_path.exists():
|
|
1016
|
-
return DEPLOYMENT_PERSONAL
|
|
1017
|
-
|
|
1018
|
-
try:
|
|
1019
|
-
import tomllib as _tomllib
|
|
1020
|
-
raw = config_toml_path.read_text(encoding="utf-8")
|
|
1021
|
-
data = _tomllib.loads(raw)
|
|
1022
|
-
except Exception as exc:
|
|
1023
|
-
logger.warning(
|
|
1024
|
-
"load_deployment_config: failed to parse %s: %s", config_toml_path, exc
|
|
1025
|
-
)
|
|
1026
|
-
return DEPLOYMENT_ENTERPRISE
|
|
1027
|
-
|
|
1028
|
-
if "deployment" not in data:
|
|
1029
|
-
# No [deployment] section — personal defaults, no behaviour change.
|
|
1030
|
-
return DEPLOYMENT_PERSONAL
|
|
1031
|
-
dep = data["deployment"]
|
|
1032
|
-
if not isinstance(dep, dict):
|
|
1033
|
-
logger.warning(
|
|
1034
|
-
"load_deployment_config: [deployment] in %s is not a table — "
|
|
1035
|
-
"failing closed to enterprise",
|
|
1036
|
-
config_toml_path,
|
|
1037
|
-
)
|
|
1038
|
-
return DEPLOYMENT_ENTERPRISE
|
|
1039
|
-
|
|
1040
|
-
raw_mode = str(dep.get("mode", "")).lower()
|
|
1041
|
-
if raw_mode not in _VALID_DEPLOYMENT_MODES:
|
|
1042
|
-
logger.warning(
|
|
1043
|
-
"load_deployment_config: unknown mode %r in %s — failing closed to enterprise",
|
|
1044
|
-
raw_mode, config_toml_path,
|
|
1045
|
-
)
|
|
1046
|
-
return DEPLOYMENT_ENTERPRISE
|
|
1047
|
-
|
|
1048
|
-
# Use the preset for the mode as the base so omitted keys inherit
|
|
1049
|
-
# sensible values (enterprise → require_login=True etc.).
|
|
1050
|
-
base = DEPLOYMENT_ENTERPRISE if raw_mode == "enterprise" else DEPLOYMENT_PERSONAL
|
|
1051
|
-
|
|
1052
|
-
def _strict_bool(name: str, default: bool) -> bool:
|
|
1053
|
-
value = dep.get(name, default)
|
|
1054
|
-
if not isinstance(value, bool):
|
|
1055
|
-
raise TypeError(f"{name} must be a TOML boolean")
|
|
1056
|
-
return value
|
|
1057
|
-
|
|
1058
|
-
try:
|
|
1059
|
-
return DeploymentConfig(
|
|
1060
|
-
mode=raw_mode,
|
|
1061
|
-
require_login=_strict_bool("require_login", base.require_login),
|
|
1062
|
-
pii_redaction=_strict_bool("pii_redaction", base.pii_redaction),
|
|
1063
|
-
retention_enabled=_strict_bool(
|
|
1064
|
-
"retention_enabled", base.retention_enabled
|
|
1065
|
-
),
|
|
1066
|
-
audit=_strict_bool("audit", base.audit),
|
|
1067
|
-
)
|
|
1068
|
-
except (ValueError, TypeError) as exc:
|
|
1069
|
-
logger.warning(
|
|
1070
|
-
"load_deployment_config: invalid values in [deployment] in %s: %s — "
|
|
1071
|
-
"failing closed to enterprise",
|
|
1072
|
-
config_toml_path, exc,
|
|
1073
|
-
)
|
|
1074
|
-
return DEPLOYMENT_ENTERPRISE
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
# ---------------------------------------------------------------------------
|
|
1078
|
-
# Health Config (v3.6.9 BUG-A)
|
|
1079
|
-
# ---------------------------------------------------------------------------
|
|
1080
|
-
|
|
1081
|
-
@dataclass
|
|
1082
|
-
class HealthConfig:
|
|
1083
|
-
"""Health-monitor tuning knobs.
|
|
1084
|
-
|
|
1085
|
-
All values have safe defaults so an empty ``"health": {}`` JSON section
|
|
1086
|
-
silently inherits them. The ``global_rss_budget_mb`` default is computed
|
|
1087
|
-
at runtime (40% of physical RAM, floor 2500 MB) so low-RAM boxes keep the
|
|
1088
|
-
old behaviour while large machines are never accidentally throttled.
|
|
1089
|
-
"""
|
|
1090
|
-
global_rss_budget_mb: int = 0 # 0 = compute at runtime (40% RAM, floor 2500)
|
|
1091
|
-
heartbeat_timeout_sec: int = 60
|
|
1092
|
-
health_check_interval_sec: int = 15
|
|
1093
|
-
enable_structured_logging: bool = True
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
# ---------------------------------------------------------------------------
|
|
1097
|
-
# Graph Pruning Config (Workstream G — #84)
|
|
1098
|
-
# ---------------------------------------------------------------------------
|
|
1099
|
-
|
|
1100
|
-
@dataclass
|
|
1101
|
-
class GraphPruningConfig:
|
|
1102
|
-
"""Graph thinning parameters. Exposed via dashboard + CLI.
|
|
1103
|
-
|
|
1104
|
-
Defaults reproduce the previous hard-coded behaviour so existing
|
|
1105
|
-
deployments see zero behavioural change after upgrade.
|
|
1106
|
-
"""
|
|
1107
|
-
|
|
1108
|
-
#: Maximum in- or out-degree per node. Maps to the legacy module-level
|
|
1109
|
-
#: ``_MAX_DEGREE_PER_NODE = 100`` in graph_pruner.py.
|
|
1110
|
-
max_degree_per_node: int = 100
|
|
1111
|
-
#: Discard edges with weight strictly below this floor.
|
|
1112
|
-
#: 0.0 = no floor (current behaviour, always-safe default).
|
|
1113
|
-
min_edge_weight: float = 0.0
|
|
1114
|
-
#: Master kill-switch for all graph pruning. ``False`` skips the entire
|
|
1115
|
-
#: prune step (useful for debugging graph bloat).
|
|
1116
|
-
enabled: bool = True
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
# ---------------------------------------------------------------------------
|
|
1120
|
-
# Master Config
|
|
1121
|
-
# ---------------------------------------------------------------------------
|
|
1122
|
-
|
|
1123
|
-
@dataclass
|
|
1124
|
-
class SLMConfig:
|
|
1125
|
-
"""Master configuration for SuperLocalMemory V3.
|
|
1126
|
-
|
|
1127
|
-
Create via SLMConfig.for_mode(Mode.A) for mode-specific defaults.
|
|
1128
|
-
"""
|
|
1129
|
-
|
|
1130
|
-
mode: Mode = Mode.A
|
|
1131
|
-
base_dir: Path = field(default_factory=_runtime_base_dir)
|
|
1132
|
-
db_path: Path | None = None # Computed from base_dir if None
|
|
1133
|
-
active_profile: str = "default"
|
|
1134
|
-
|
|
1135
|
-
embedding: EmbeddingConfig = field(default_factory=EmbeddingConfig)
|
|
1136
|
-
llm: LLMConfig = field(default_factory=LLMConfig)
|
|
1137
|
-
channel_weights: ChannelWeights = field(default_factory=ChannelWeights)
|
|
1138
|
-
scope_weights: ScopeWeights = field(default_factory=ScopeWeights)
|
|
1139
|
-
scope: ScopeConfig = field(default_factory=ScopeConfig)
|
|
1140
|
-
encoding: EncodingConfig = field(default_factory=EncodingConfig)
|
|
1141
|
-
retrieval: RetrievalConfig = field(default_factory=RetrievalConfig)
|
|
1142
|
-
math: MathConfig = field(default_factory=MathConfig)
|
|
1143
|
-
temporal_validator: TemporalValidatorConfig = field(
|
|
1144
|
-
default_factory=TemporalValidatorConfig,
|
|
1145
|
-
)
|
|
1146
|
-
auto_invoke: AutoInvokeConfig = field(default_factory=AutoInvokeConfig)
|
|
1147
|
-
consolidation: ConsolidationConfig = field(
|
|
1148
|
-
default_factory=ConsolidationConfig,
|
|
1149
|
-
)
|
|
1150
|
-
forgetting: ForgettingConfig = field(default_factory=ForgettingConfig)
|
|
1151
|
-
hopfield: HopfieldConfig = field(default_factory=HopfieldConfig)
|
|
1152
|
-
reaper: ReaperConfig = field(default_factory=ReaperConfig)
|
|
1153
|
-
quantization: QuantizationConfig = field(default_factory=QuantizationConfig)
|
|
1154
|
-
sagq: SAGQConfig = field(default_factory=SAGQConfig)
|
|
1155
|
-
ccq: CCQConfig = field(default_factory=CCQConfig)
|
|
1156
|
-
parameterization: ParameterizationConfig = field(
|
|
1157
|
-
default_factory=ParameterizationConfig,
|
|
1158
|
-
)
|
|
1159
|
-
injection: InjectionConfig = field(default_factory=InjectionConfig)
|
|
1160
|
-
store: StoreConfig = field(default_factory=StoreConfig)
|
|
1161
|
-
# v3.5.0: scaling backends — "sqlite" / "cozo" / "auto" / "lancedb" / "sqlite-vec" / "auto".
|
|
1162
|
-
graph_backend: str = "auto" # "auto" = cozo if pycozo installed, else sqlite
|
|
1163
|
-
vector_backend: str = "auto" # "auto" = lancedb if installed, else sqlite-vec
|
|
1164
|
-
# Scale Engine is installed separately from activation. Existing roots
|
|
1165
|
-
# stay on SQLite until a staged parity check promotes these projections.
|
|
1166
|
-
scale_engine_state: str = "local_core" # local_core | prepared | verified | promoted
|
|
1167
|
-
# v3.8.5: auto-promote Cozo+LanceDB when a DB grows past the scale at which
|
|
1168
|
-
# they actually help. Auto-promotion is background, uses the same staged
|
|
1169
|
-
# parity gate as the manual path, and falls back to SQLite on any failure.
|
|
1170
|
-
#
|
|
1171
|
-
# 100,000 (was 1,000,000, set by Varun 2026-08-22). The old value gated every
|
|
1172
|
-
# store anyone actually has -- the author's is 140,000 edges -- so the
|
|
1173
|
-
# projection was built for nobody and, being unexercised, was wrong: it
|
|
1174
|
-
# carried 1,257 memories the store may not return and its graph search
|
|
1175
|
-
# disagreed with SQLite on every query. A threshold no real store crosses is
|
|
1176
|
-
# not a safety margin, it is a feature nobody tests.
|
|
1177
|
-
#
|
|
1178
|
-
# What promotion now changes is narrow and measured. The graph WALK does not
|
|
1179
|
-
# move: SQLite loads 130k edges in 141 ms against CozoDB's 252 ms, so the
|
|
1180
|
-
# adjacency read stays where it is fastest (retrieval/graph_adjacency.py
|
|
1181
|
-
# holds the seam for the day that flips). What promotion switches on is the
|
|
1182
|
-
# LanceDB vector index, which is within noise of sqlite-vec at this size
|
|
1183
|
-
# (18.3 ms against 18.7 ms over 5,324 vectors) and is the one built to keep
|
|
1184
|
-
# going as the vector count grows. So crossing this line is close to
|
|
1185
|
-
# latency-neutral today and buys the projection real exercise.
|
|
1186
|
-
scale_auto_promote_enabled: bool = True
|
|
1187
|
-
scale_auto_promote_min_edges: int = 100_000
|
|
1188
|
-
evolution: EvolutionConfig = field(default_factory=EvolutionConfig)
|
|
1189
|
-
health: HealthConfig = field(default_factory=HealthConfig)
|
|
1190
|
-
# v3.8.4-G: Graph thinning parameters (#84)
|
|
1191
|
-
graph_pruning: GraphPruningConfig = field(default_factory=GraphPruningConfig)
|
|
1192
|
-
|
|
1193
|
-
# v3.4.3: Daemon configuration
|
|
1194
|
-
daemon_idle_timeout: int = 0 # 0 = 24/7 (no auto-kill). >0 = seconds before auto-kill.
|
|
1195
|
-
daemon_port: int = 8765 # Primary daemon port
|
|
1196
|
-
daemon_legacy_port: int = 8767 # Backward-compat redirect port
|
|
1197
|
-
daemon_enable_legacy_port: bool = True # Set False to disable 8767 redirect
|
|
1198
|
-
|
|
1199
|
-
# v4: auto-close orphaned application sessions during maintenance.
|
|
1200
|
-
# A session is stale when it has had no new atomic_facts for this many
|
|
1201
|
-
# hours. close_session is only invoked by the MCP tool otherwise, so
|
|
1202
|
-
# without this pass temporal summaries never form for abandoned sessions.
|
|
1203
|
-
session_idle_close_hours: float = 24.0
|
|
1204
|
-
session_idle_close_max_per_pass: int = 50
|
|
1205
|
-
|
|
1206
|
-
# v3.4.3: Entity compilation
|
|
1207
|
-
entity_compilation_enabled: bool = True
|
|
1208
|
-
entity_compilation_retrieval_boost: float = 1.0 # 1.0 = disabled. >1.0 = boost score.
|
|
1209
|
-
|
|
1210
|
-
# v3.4.3: Mesh
|
|
1211
|
-
mesh_enabled: bool = True
|
|
1212
|
-
|
|
1213
|
-
# Deployment policy overlay. Personal installs remain unchanged; the
|
|
1214
|
-
# daemon upgrades this to True before engine initialization when the
|
|
1215
|
-
# enterprise deployment preset requests PII redaction.
|
|
1216
|
-
pii_redaction: bool = False
|
|
1217
|
-
|
|
1218
|
-
# GDPR / compliance: data-retention window in days.
|
|
1219
|
-
# Default 90 days (owner decision: "configurable through the UI, and
|
|
1220
|
-
# 90 days by default"). Units: days. Value of 0 means no automatic
|
|
1221
|
-
# expiry. Consumed by the backup/erasure obligation tracker and any
|
|
1222
|
-
# future retention scheduler. Configurable via config.json (key
|
|
1223
|
-
# "retention_window_days") or the UI settings panel.
|
|
1224
|
-
# NOTE: this field is intentionally separate from
|
|
1225
|
-
# DeploymentConfig.retention_enabled — the enabled flag gates the
|
|
1226
|
-
# scheduler, while this window controls the duration.
|
|
1227
|
-
retention_window_days: int = 90
|
|
1228
|
-
|
|
1229
|
-
def __post_init__(self) -> None:
|
|
1230
|
-
if self.db_path is None:
|
|
1231
|
-
self.db_path = self.base_dir / DEFAULT_DB_NAME
|
|
1232
|
-
|
|
1233
|
-
@classmethod
|
|
1234
|
-
def load(cls, config_path: Path | None = None) -> SLMConfig:
|
|
1235
|
-
"""Load config from JSON file. Returns default Mode A if file doesn't exist."""
|
|
1236
|
-
# V3.7: process namespace selection is authoritative. A persisted
|
|
1237
|
-
# base_dir may bootstrap a legacy custom root only when no environment
|
|
1238
|
-
# alias selected the process namespace.
|
|
1239
|
-
try:
|
|
1240
|
-
from superlocalmemory.infra.data_root import (
|
|
1241
|
-
canonical_data_root,
|
|
1242
|
-
environment_data_root,
|
|
1243
|
-
)
|
|
1244
|
-
_environment_base = environment_data_root()
|
|
1245
|
-
_runtime_base = canonical_data_root()
|
|
1246
|
-
except Exception:
|
|
1247
|
-
_environment_base = None
|
|
1248
|
-
_runtime_base = DEFAULT_BASE_DIR
|
|
1249
|
-
path = config_path or (_runtime_base / "config.json")
|
|
1250
|
-
if not path.exists():
|
|
1251
|
-
return cls.for_mode(Mode.A, base_dir=_runtime_base)
|
|
1252
|
-
|
|
1253
|
-
# DASH-V4 (3.7.9): the ``current_mode`` file is the single source of
|
|
1254
|
-
# truth for the ACTIVE mode. If config.json drifted from it (a writer
|
|
1255
|
-
# bypassed switch_mode and reset the active config), load the
|
|
1256
|
-
# authoritative per-mode file so daemon/CLI/dashboard all run the user's
|
|
1257
|
-
# chosen mode — with its settings — instead of a stale one. Scoped to
|
|
1258
|
-
# config.json so explicit per-mode loads (switch_mode) are untouched.
|
|
1259
|
-
# Fail-open: any error falls through to the normal load below.
|
|
1260
|
-
if path.name == "config.json":
|
|
1261
|
-
try:
|
|
1262
|
-
import json as _json
|
|
1263
|
-
_disk_mode = str(
|
|
1264
|
-
_json.loads(path.read_text()).get("mode", "")
|
|
1265
|
-
).lower()
|
|
1266
|
-
_active_mode = cls.read_current_mode(path.parent)
|
|
1267
|
-
if _disk_mode and _active_mode and _disk_mode != _active_mode:
|
|
1268
|
-
_mode_path = cls._mode_config_path(
|
|
1269
|
-
path.parent, Mode(_active_mode)
|
|
1270
|
-
)
|
|
1271
|
-
if _mode_path.exists():
|
|
1272
|
-
logger.warning(
|
|
1273
|
-
"config.json mode=%s disagrees with current_mode=%s;"
|
|
1274
|
-
" loading authoritative %s",
|
|
1275
|
-
_disk_mode, _active_mode, _mode_path.name,
|
|
1276
|
-
)
|
|
1277
|
-
return cls.load(_mode_path)
|
|
1278
|
-
except Exception:
|
|
1279
|
-
pass # fail-open — fall through to the normal load below
|
|
1280
|
-
|
|
1281
|
-
import json
|
|
1282
|
-
try:
|
|
1283
|
-
data = json.loads(path.read_text())
|
|
1284
|
-
except (json.JSONDecodeError, OSError) as exc:
|
|
1285
|
-
# An already-corrupt/truncated config.json must NOT brick every `slm`
|
|
1286
|
-
# call — degrade to the Mode-A default and warn rather than raise.
|
|
1287
|
-
logger.warning(
|
|
1288
|
-
"config.json unreadable/corrupt (%s) — using Mode A default", exc
|
|
1289
|
-
)
|
|
1290
|
-
return cls.for_mode(Mode.A, base_dir=_runtime_base)
|
|
1291
|
-
mode = Mode(data.get("mode", "a"))
|
|
1292
|
-
llm_data = data.get("llm", {})
|
|
1293
|
-
emb_data = data.get("embedding", {})
|
|
1294
|
-
# V3.5.9: read base_dir before constructing config so for_mode() builds
|
|
1295
|
-
# db_path from the user's directory, not DEFAULT_BASE_DIR.
|
|
1296
|
-
if _environment_base is not None:
|
|
1297
|
-
raw_base_dir = _environment_base
|
|
1298
|
-
else:
|
|
1299
|
-
try:
|
|
1300
|
-
raw_base_dir = canonical_data_root(
|
|
1301
|
-
configured_base_dir=data.get("base_dir", _runtime_base),
|
|
1302
|
-
)
|
|
1303
|
-
except Exception:
|
|
1304
|
-
raw_base_dir = _runtime_base
|
|
1305
|
-
config = cls.for_mode(
|
|
1306
|
-
mode,
|
|
1307
|
-
llm_provider=llm_data.get("provider", ""),
|
|
1308
|
-
llm_model=llm_data.get("model", ""),
|
|
1309
|
-
llm_api_key=llm_data.get("api_key", ""),
|
|
1310
|
-
llm_api_base=llm_data.get("base_url", ""),
|
|
1311
|
-
embedding_provider=emb_data.get("provider", ""),
|
|
1312
|
-
embedding_endpoint=emb_data.get("api_endpoint", ""),
|
|
1313
|
-
embedding_key=emb_data.get("api_key", ""),
|
|
1314
|
-
embedding_deployment=emb_data.get("deployment_name", ""),
|
|
1315
|
-
embedding_model_name=emb_data.get("model_name", ""),
|
|
1316
|
-
embedding_dimension=int(emb_data.get("dimension", 0) or 0),
|
|
1317
|
-
base_dir=raw_base_dir,
|
|
1318
|
-
)
|
|
1319
|
-
config.active_profile = data.get("active_profile", "default")
|
|
1320
|
-
config.graph_backend = data.get("graph_backend", "auto")
|
|
1321
|
-
config.vector_backend = data.get("vector_backend", "auto")
|
|
1322
|
-
state = data.get("scale_engine_state", "local_core")
|
|
1323
|
-
config.scale_engine_state = (
|
|
1324
|
-
state if state in {"local_core", "prepared", "verified", "promoted"}
|
|
1325
|
-
else "local_core"
|
|
1326
|
-
)
|
|
1327
|
-
config.scale_auto_promote_enabled = bool(
|
|
1328
|
-
data.get("scale_auto_promote_enabled", True)
|
|
1329
|
-
)
|
|
1330
|
-
try:
|
|
1331
|
-
config.scale_auto_promote_min_edges = int(
|
|
1332
|
-
data.get("scale_auto_promote_min_edges", 100_000)
|
|
1333
|
-
)
|
|
1334
|
-
except (TypeError, ValueError):
|
|
1335
|
-
config.scale_auto_promote_min_edges = 100_000
|
|
1336
|
-
|
|
1337
|
-
# V3.3 config fields (additive — defaults work if missing from JSON)
|
|
1338
|
-
fg = data.get("forgetting", {})
|
|
1339
|
-
if fg:
|
|
1340
|
-
config.forgetting = ForgettingConfig(**{
|
|
1341
|
-
k: v for k, v in fg.items()
|
|
1342
|
-
if k in ForgettingConfig.__dataclass_fields__
|
|
1343
|
-
})
|
|
1344
|
-
|
|
1345
|
-
# quantization: nested PolarQuantConfig + QJLConfig are reconstructed
|
|
1346
|
-
# from their serialized dicts; scalar fields are passed through directly.
|
|
1347
|
-
# Unknown subkeys are filtered out for forward-compat safety.
|
|
1348
|
-
qt_raw = data.get("quantization", {})
|
|
1349
|
-
if qt_raw:
|
|
1350
|
-
try:
|
|
1351
|
-
polar_raw = qt_raw.get("polar", {})
|
|
1352
|
-
qjl_raw = qt_raw.get("qjl", {})
|
|
1353
|
-
polar = PolarQuantConfig(**{
|
|
1354
|
-
k: v for k, v in polar_raw.items()
|
|
1355
|
-
if k in PolarQuantConfig.__dataclass_fields__
|
|
1356
|
-
})
|
|
1357
|
-
qjl = QJLConfig(**{
|
|
1358
|
-
k: v for k, v in qjl_raw.items()
|
|
1359
|
-
if k in QJLConfig.__dataclass_fields__
|
|
1360
|
-
})
|
|
1361
|
-
qt_scalars = {
|
|
1362
|
-
k: v for k, v in qt_raw.items()
|
|
1363
|
-
if k in QuantizationConfig.__dataclass_fields__
|
|
1364
|
-
and k not in ("polar", "qjl")
|
|
1365
|
-
}
|
|
1366
|
-
config.quantization = QuantizationConfig(polar=polar, qjl=qjl, **qt_scalars)
|
|
1367
|
-
except (TypeError, ValueError) as exc:
|
|
1368
|
-
logger.warning(
|
|
1369
|
-
"Ignoring invalid quantization config (%s) — using defaults", exc
|
|
1370
|
-
)
|
|
1371
|
-
|
|
1372
|
-
# sagq: valid_bit_widths is serialized as a list by dataclasses.asdict()
|
|
1373
|
-
# and must be coerced back to a tuple to satisfy the field annotation and
|
|
1374
|
-
# preserve identity through a round-trip.
|
|
1375
|
-
sq_raw = data.get("sagq", {})
|
|
1376
|
-
if sq_raw:
|
|
1377
|
-
try:
|
|
1378
|
-
sq_kwargs = {
|
|
1379
|
-
k: v for k, v in sq_raw.items()
|
|
1380
|
-
if k in SAGQConfig.__dataclass_fields__
|
|
1381
|
-
}
|
|
1382
|
-
if "valid_bit_widths" in sq_kwargs:
|
|
1383
|
-
sq_kwargs["valid_bit_widths"] = tuple(sq_kwargs["valid_bit_widths"])
|
|
1384
|
-
config.sagq = SAGQConfig(**sq_kwargs)
|
|
1385
|
-
except (TypeError, ValueError) as exc:
|
|
1386
|
-
logger.warning(
|
|
1387
|
-
"Ignoring invalid sagq config (%s) — using defaults", exc
|
|
1388
|
-
)
|
|
1389
|
-
|
|
1390
|
-
# auto_invoke: dict fields (weights, act_r_weights, mode_a_weights) are
|
|
1391
|
-
# preserved as-is by asdict(); no special reconstruction required.
|
|
1392
|
-
ai_raw = data.get("auto_invoke", {})
|
|
1393
|
-
if ai_raw:
|
|
1394
|
-
try:
|
|
1395
|
-
config.auto_invoke = AutoInvokeConfig(**{
|
|
1396
|
-
k: v for k, v in ai_raw.items()
|
|
1397
|
-
if k in AutoInvokeConfig.__dataclass_fields__
|
|
1398
|
-
})
|
|
1399
|
-
except (TypeError, ValueError) as exc:
|
|
1400
|
-
logger.warning(
|
|
1401
|
-
"Ignoring invalid auto_invoke config (%s) — using defaults", exc
|
|
1402
|
-
)
|
|
1403
|
-
|
|
1404
|
-
rt = data.get("retrieval", {})
|
|
1405
|
-
if rt:
|
|
1406
|
-
# V3.3.2 migration: add ONNX cross-encoder backend field.
|
|
1407
|
-
# Pre-3.3.2 configs lacked cross_encoder_backend. Add it,
|
|
1408
|
-
# but NEVER override an explicit use_cross_encoder setting.
|
|
1409
|
-
# The user's explicit choice always wins.
|
|
1410
|
-
if "cross_encoder_backend" not in rt:
|
|
1411
|
-
rt.setdefault("cross_encoder_model", "cross-encoder/ms-marco-MiniLM-L-12-v2")
|
|
1412
|
-
rt["cross_encoder_backend"] = "" # V3.3.18: PyTorch (ONNX CoreML leaks on ARM64)
|
|
1413
|
-
# Only auto-enable if user didn't explicitly set the field
|
|
1414
|
-
rt.setdefault("use_cross_encoder", True)
|
|
1415
|
-
config.retrieval = RetrievalConfig(**{
|
|
1416
|
-
k: v for k, v in rt.items()
|
|
1417
|
-
if k in RetrievalConfig.__dataclass_fields__
|
|
1418
|
-
})
|
|
1419
|
-
|
|
1420
|
-
# 4.1.0 (#124): restore the two sections save() now writes. This runs
|
|
1421
|
-
# AFTER for_mode() has applied its presets, so a value someone chose
|
|
1422
|
-
# beats the mode default -- the same ordering retrieval already relies
|
|
1423
|
-
# on. A malformed section falls back to the dataclass defaults rather
|
|
1424
|
-
# than raising, matching quantization and sagq above: a corrupt config
|
|
1425
|
-
# file must not make `slm` unrunnable.
|
|
1426
|
-
mth = data.get("math")
|
|
1427
|
-
if isinstance(mth, dict) and mth:
|
|
1428
|
-
try:
|
|
1429
|
-
fields = {
|
|
1430
|
-
k: v for k, v in mth.items()
|
|
1431
|
-
if k in MathConfig.__dataclass_fields__
|
|
1432
|
-
}
|
|
1433
|
-
# JSON has no tuple. Written as a list, it must come back a
|
|
1434
|
-
# tuple or every reader that unpacks a pair gets a list and the
|
|
1435
|
-
# difference surfaces somewhere far from here.
|
|
1436
|
-
rng = fields.get("langevin_weight_range")
|
|
1437
|
-
if isinstance(rng, list):
|
|
1438
|
-
fields["langevin_weight_range"] = tuple(rng)
|
|
1439
|
-
config.math = MathConfig(**fields)
|
|
1440
|
-
except (TypeError, ValueError) as exc:
|
|
1441
|
-
logger.warning(
|
|
1442
|
-
"Ignoring invalid math config (%s) — using defaults", exc
|
|
1443
|
-
)
|
|
1444
|
-
|
|
1445
|
-
cw = data.get("channel_weights")
|
|
1446
|
-
if isinstance(cw, dict) and cw:
|
|
1447
|
-
try:
|
|
1448
|
-
config.channel_weights = ChannelWeights(**{
|
|
1449
|
-
k: v for k, v in cw.items()
|
|
1450
|
-
if k in ChannelWeights.__dataclass_fields__
|
|
1451
|
-
})
|
|
1452
|
-
except (TypeError, ValueError) as exc:
|
|
1453
|
-
logger.warning(
|
|
1454
|
-
"Ignoring invalid channel_weights config (%s) — using defaults",
|
|
1455
|
-
exc,
|
|
1456
|
-
)
|
|
1457
|
-
|
|
1458
|
-
# V3.4.3 config fields (additive — missing keys get dataclass defaults)
|
|
1459
|
-
config.daemon_idle_timeout = data.get("daemon_idle_timeout", 0)
|
|
1460
|
-
config.daemon_port = data.get("daemon_port", 8765)
|
|
1461
|
-
config.session_idle_close_hours = float(
|
|
1462
|
-
data.get("session_idle_close_hours", 24.0) or 24.0
|
|
1463
|
-
)
|
|
1464
|
-
config.session_idle_close_max_per_pass = int(
|
|
1465
|
-
data.get("session_idle_close_max_per_pass", 50) or 50
|
|
1466
|
-
)
|
|
1467
|
-
config.daemon_legacy_port = data.get("daemon_legacy_port", 8767)
|
|
1468
|
-
config.daemon_enable_legacy_port = data.get("daemon_enable_legacy_port", True)
|
|
1469
|
-
config.entity_compilation_enabled = data.get("entity_compilation_enabled", True)
|
|
1470
|
-
config.entity_compilation_retrieval_boost = data.get(
|
|
1471
|
-
"entity_compilation_retrieval_boost", 1.0,
|
|
1472
|
-
)
|
|
1473
|
-
config.mesh_enabled = data.get("mesh_enabled", True)
|
|
1474
|
-
|
|
1475
|
-
# v4.0.6: GDPR retention window (additive — defaults to 90 if absent)
|
|
1476
|
-
try:
|
|
1477
|
-
config.retention_window_days = int(data.get("retention_window_days", 90))
|
|
1478
|
-
except (TypeError, ValueError):
|
|
1479
|
-
config.retention_window_days = 90
|
|
1480
|
-
|
|
1481
|
-
# V3.4.10: Evolution config
|
|
1482
|
-
evo = data.get("evolution", {})
|
|
1483
|
-
if evo:
|
|
1484
|
-
config.evolution = EvolutionConfig(**{
|
|
1485
|
-
k: v for k, v in evo.items()
|
|
1486
|
-
if k in EvolutionConfig.__dataclass_fields__
|
|
1487
|
-
})
|
|
1488
|
-
|
|
1489
|
-
# V3.6.9: Health monitor config (BUG-A — previously silently ignored)
|
|
1490
|
-
hlth = data.get("health", {})
|
|
1491
|
-
if hlth:
|
|
1492
|
-
config.health = HealthConfig(**{
|
|
1493
|
-
k: v for k, v in hlth.items()
|
|
1494
|
-
if k in HealthConfig.__dataclass_fields__
|
|
1495
|
-
})
|
|
1496
|
-
|
|
1497
|
-
# v3.8.4-G: Graph pruning config (#84) — additive, no migration needed.
|
|
1498
|
-
# Old configs without this section silently fall back to GraphPruningConfig()
|
|
1499
|
-
# defaults, which reproduce the existing _MAX_DEGREE_PER_NODE=100 behaviour.
|
|
1500
|
-
gp_raw = data.get("graph_pruning", {})
|
|
1501
|
-
if gp_raw:
|
|
1502
|
-
try:
|
|
1503
|
-
gp_kwargs = {
|
|
1504
|
-
k: v for k, v in gp_raw.items()
|
|
1505
|
-
if k in GraphPruningConfig.__dataclass_fields__
|
|
1506
|
-
}
|
|
1507
|
-
gp = GraphPruningConfig(**gp_kwargs)
|
|
1508
|
-
# Validate and clamp out-of-range values
|
|
1509
|
-
max_deg = gp.max_degree_per_node if gp.max_degree_per_node >= 1 else 100
|
|
1510
|
-
min_w = max(0.0, min(1.0, gp.min_edge_weight))
|
|
1511
|
-
config.graph_pruning = GraphPruningConfig(
|
|
1512
|
-
max_degree_per_node=max_deg,
|
|
1513
|
-
min_edge_weight=min_w,
|
|
1514
|
-
enabled=gp.enabled,
|
|
1515
|
-
)
|
|
1516
|
-
except (ValueError, TypeError) as exc:
|
|
1517
|
-
logger.warning(
|
|
1518
|
-
"graph_pruning config invalid (%s) — using defaults", exc
|
|
1519
|
-
)
|
|
1520
|
-
config.graph_pruning = GraphPruningConfig()
|
|
1521
|
-
|
|
1522
|
-
# V3.4.65: Injection config (additive — defaults if missing from JSON)
|
|
1523
|
-
inj = data.get("injection", {}) or {}
|
|
1524
|
-
config.injection = InjectionConfig(
|
|
1525
|
-
enabled=bool(inj.get("enabled", True)),
|
|
1526
|
-
total_budget_tokens_a=int(inj.get("total_budget_tokens_a", 2000)),
|
|
1527
|
-
total_budget_tokens_b=int(inj.get("total_budget_tokens_b", 4000)),
|
|
1528
|
-
total_budget_tokens_c=int(inj.get("total_budget_tokens_c", 8000)),
|
|
1529
|
-
per_memory_max_tokens=int(inj.get("per_memory_max_tokens", 600)),
|
|
1530
|
-
core_block_enabled=bool(inj.get("core_block_enabled", True)),
|
|
1531
|
-
core_block_max_facts=int(inj.get("core_block_max_facts", 5)),
|
|
1532
|
-
core_block_max_tokens=int(inj.get("core_block_max_tokens", 1000)),
|
|
1533
|
-
core_block_importance_min=float(inj.get("core_block_importance_min", 0.8)),
|
|
1534
|
-
core_block_min_access_count=int(inj.get("core_block_min_access_count", 2)),
|
|
1535
|
-
edge_ordering=bool(inj.get("edge_ordering", True)),
|
|
1536
|
-
trust_first_party=bool(inj.get("trust_first_party", False)),
|
|
1537
|
-
prestage_max_response_bytes=int(inj.get("prestage_max_response_bytes", 64 * 1024)),
|
|
1538
|
-
)
|
|
1539
|
-
|
|
1540
|
-
# Multi-scope memory: scope weights
|
|
1541
|
-
sw = data.get("scope_weights", {})
|
|
1542
|
-
if sw:
|
|
1543
|
-
# v3.6.15: a malformed value (e.g. negative weight) must NOT brick
|
|
1544
|
-
# every `slm` command via an uncaught ValueError out of load().
|
|
1545
|
-
# Fall back to defaults and warn — the config is recoverable.
|
|
1546
|
-
try:
|
|
1547
|
-
config.scope_weights = ScopeWeights(**{
|
|
1548
|
-
k: v for k, v in sw.items()
|
|
1549
|
-
if k in ScopeWeights.__dataclass_fields__
|
|
1550
|
-
})
|
|
1551
|
-
except (ValueError, TypeError) as exc:
|
|
1552
|
-
logger.warning(
|
|
1553
|
-
"Ignoring invalid scope_weights in config (%s) — using defaults", exc
|
|
1554
|
-
)
|
|
1555
|
-
|
|
1556
|
-
# Multi-scope memory: behaviour defaults (default scope + recall visibility)
|
|
1557
|
-
sc = data.get("scope", {})
|
|
1558
|
-
if sc:
|
|
1559
|
-
# Same guard: a typo'd default_scope must not crash the whole CLI.
|
|
1560
|
-
try:
|
|
1561
|
-
config.scope = ScopeConfig(**{
|
|
1562
|
-
k: v for k, v in sc.items()
|
|
1563
|
-
if k in ScopeConfig.__dataclass_fields__
|
|
1564
|
-
})
|
|
1565
|
-
except (ValueError, TypeError) as exc:
|
|
1566
|
-
logger.warning(
|
|
1567
|
-
"Ignoring invalid scope config (%s) — using shared-off defaults", exc
|
|
1568
|
-
)
|
|
1569
|
-
|
|
1570
|
-
# Preserve the raw loaded dict so save() can return every key that was
|
|
1571
|
-
# present in the file, including unknown forward-compat keys and nested
|
|
1572
|
-
# sections whose subkeys this version does not model. The attribute is
|
|
1573
|
-
# intentionally private (leading underscore) and is not a declared field,
|
|
1574
|
-
# so dataclasses.asdict() and dataclasses.replace() ignore it — callers
|
|
1575
|
-
# that clone a config via replace() will lose unknown-key preservation,
|
|
1576
|
-
# which is acceptable: the primary contract is load→save round-trip.
|
|
1577
|
-
config._raw_preserved: dict = dict(data) # type: ignore[attr-defined]
|
|
1578
|
-
return config
|
|
1579
|
-
|
|
1580
|
-
def save(
|
|
1581
|
-
self,
|
|
1582
|
-
config_path: Path | None = None,
|
|
1583
|
-
*,
|
|
1584
|
-
mode_change: bool = False,
|
|
1585
|
-
) -> None:
|
|
1586
|
-
"""Save config to JSON file.
|
|
1587
|
-
|
|
1588
|
-
v3.4.34: mode protection. If the existing config.json has a mode
|
|
1589
|
-
that differs from ``self.mode`` and the caller did NOT pass
|
|
1590
|
-
``mode_change=True``, the EXISTING mode is preserved. This
|
|
1591
|
-
prevents accidental mode resets when code creates a fresh
|
|
1592
|
-
``SLMConfig()`` (defaults to Mode A) and calls ``save()`` to
|
|
1593
|
-
persist an unrelated field change.
|
|
1594
|
-
|
|
1595
|
-
Callers that intentionally switch mode (``slm mode b``, the MCP
|
|
1596
|
-
``set_mode`` tool, the dashboard PUT ``/api/v3/mode``) MUST pass
|
|
1597
|
-
``mode_change=True``.
|
|
1598
|
-
"""
|
|
1599
|
-
import json
|
|
1600
|
-
path = config_path or (self.base_dir / "config.json")
|
|
1601
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
1602
|
-
# Read existing config to preserve V3.3 fields not in this save
|
|
1603
|
-
existing = {}
|
|
1604
|
-
if path.exists():
|
|
1605
|
-
try:
|
|
1606
|
-
existing = json.loads(path.read_text())
|
|
1607
|
-
except (json.JSONDecodeError, OSError):
|
|
1608
|
-
pass
|
|
1609
|
-
|
|
1610
|
-
# v3.4.34: mode protection — preserve user's mode unless explicitly changing
|
|
1611
|
-
effective_mode = self.mode.value
|
|
1612
|
-
existing_mode = existing.get("mode")
|
|
1613
|
-
if existing_mode and existing_mode != effective_mode and not mode_change:
|
|
1614
|
-
logger.warning(
|
|
1615
|
-
"SLMConfig.save(): mode change blocked (%s → %s). "
|
|
1616
|
-
"Pass mode_change=True to override. Preserving '%s'.",
|
|
1617
|
-
existing_mode, effective_mode, existing_mode,
|
|
1618
|
-
)
|
|
1619
|
-
effective_mode = existing_mode
|
|
1620
|
-
|
|
1621
|
-
data = {
|
|
1622
|
-
"mode": effective_mode,
|
|
1623
|
-
"active_profile": self.active_profile,
|
|
1624
|
-
"graph_backend": self.graph_backend,
|
|
1625
|
-
"vector_backend": self.vector_backend,
|
|
1626
|
-
"scale_engine_state": self.scale_engine_state,
|
|
1627
|
-
"scale_auto_promote_enabled": self.scale_auto_promote_enabled,
|
|
1628
|
-
"scale_auto_promote_min_edges": self.scale_auto_promote_min_edges,
|
|
1629
|
-
"base_dir": str(self.base_dir), # V3.5.9: persist so load() can restore custom paths
|
|
1630
|
-
"llm": {
|
|
1631
|
-
"provider": self.llm.provider,
|
|
1632
|
-
"model": self.llm.model,
|
|
1633
|
-
"api_key": self.llm.api_key,
|
|
1634
|
-
"base_url": self.llm.api_base,
|
|
1635
|
-
},
|
|
1636
|
-
"embedding": {
|
|
1637
|
-
"model_name": self.embedding.model_name,
|
|
1638
|
-
"dimension": self.embedding.dimension,
|
|
1639
|
-
"provider": self.embedding.provider,
|
|
1640
|
-
"api_endpoint": self.embedding.api_endpoint,
|
|
1641
|
-
"api_key": self.embedding.api_key,
|
|
1642
|
-
"deployment_name": self.embedding.deployment_name,
|
|
1643
|
-
},
|
|
1644
|
-
# Persist the complete retrieval contract. Saving only the
|
|
1645
|
-
# cross-encoder subset silently reset channel limits, evidence
|
|
1646
|
-
# floors, and agentic settings on the next mode/provider change.
|
|
1647
|
-
"retrieval": asdict(self.retrieval),
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
# V3.4.11: Persist evolution config (C-CONFIGSAVE fix)
|
|
1651
|
-
data["evolution"] = {
|
|
1652
|
-
"enabled": self.evolution.enabled,
|
|
1653
|
-
"backend": self.evolution.backend,
|
|
1654
|
-
"max_evolutions_per_cycle": self.evolution.max_evolutions_per_cycle,
|
|
1655
|
-
"mutation_model": self.evolution.mutation_model,
|
|
1656
|
-
"verify_model": self.evolution.verify_model,
|
|
1657
|
-
"confirm_model": self.evolution.confirm_model,
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
# V3.4.65: Persist injection config
|
|
1661
|
-
data["injection"] = {
|
|
1662
|
-
"enabled": self.injection.enabled,
|
|
1663
|
-
"total_budget_tokens_a": self.injection.total_budget_tokens_a,
|
|
1664
|
-
"total_budget_tokens_b": self.injection.total_budget_tokens_b,
|
|
1665
|
-
"total_budget_tokens_c": self.injection.total_budget_tokens_c,
|
|
1666
|
-
"per_memory_max_tokens": self.injection.per_memory_max_tokens,
|
|
1667
|
-
"core_block_enabled": self.injection.core_block_enabled,
|
|
1668
|
-
"core_block_max_facts": self.injection.core_block_max_facts,
|
|
1669
|
-
"core_block_max_tokens": self.injection.core_block_max_tokens,
|
|
1670
|
-
"core_block_importance_min": self.injection.core_block_importance_min,
|
|
1671
|
-
"core_block_min_access_count": self.injection.core_block_min_access_count,
|
|
1672
|
-
"edge_ordering": self.injection.edge_ordering,
|
|
1673
|
-
"trust_first_party": self.injection.trust_first_party,
|
|
1674
|
-
"prestage_max_response_bytes": self.injection.prestage_max_response_bytes,
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
|
-
# Multi-scope memory: scope weights
|
|
1678
|
-
data["scope_weights"] = {
|
|
1679
|
-
"personal": self.scope_weights.personal,
|
|
1680
|
-
"shared": self.scope_weights.shared,
|
|
1681
|
-
"global_": self.scope_weights.global_,
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
# Multi-scope memory: behaviour defaults
|
|
1685
|
-
data["scope"] = self.scope.as_dict()
|
|
1686
|
-
|
|
1687
|
-
# v3.8.4-G: Persist graph pruning config (#84).
|
|
1688
|
-
# Always written — additive key, never overwrites unrelated sections.
|
|
1689
|
-
data["graph_pruning"] = {
|
|
1690
|
-
"max_degree_per_node": self.graph_pruning.max_degree_per_node,
|
|
1691
|
-
"min_edge_weight": self.graph_pruning.min_edge_weight,
|
|
1692
|
-
"enabled": self.graph_pruning.enabled,
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
# Daemon, entity-compilation, and mesh scalar settings were loaded by
|
|
1696
|
-
# load() but omitted from earlier versions of this method. Write them
|
|
1697
|
-
# explicitly so a load-then-save is lossless for these fields.
|
|
1698
|
-
data["daemon_idle_timeout"] = self.daemon_idle_timeout
|
|
1699
|
-
data["daemon_port"] = self.daemon_port
|
|
1700
|
-
data["session_idle_close_hours"] = self.session_idle_close_hours
|
|
1701
|
-
data["session_idle_close_max_per_pass"] = self.session_idle_close_max_per_pass
|
|
1702
|
-
data["daemon_legacy_port"] = self.daemon_legacy_port
|
|
1703
|
-
data["daemon_enable_legacy_port"] = self.daemon_enable_legacy_port
|
|
1704
|
-
data["entity_compilation_enabled"] = self.entity_compilation_enabled
|
|
1705
|
-
data["entity_compilation_retrieval_boost"] = self.entity_compilation_retrieval_boost
|
|
1706
|
-
data["mesh_enabled"] = self.mesh_enabled
|
|
1707
|
-
# v4.0.6: GDPR retention window — always written so load→save is lossless.
|
|
1708
|
-
data["retention_window_days"] = self.retention_window_days
|
|
1709
|
-
|
|
1710
|
-
# Typed in-memory config sections. Preserve any on-disk subkeys this
|
|
1711
|
-
# version does not model (forward-compat or externally-tuned fields such
|
|
1712
|
-
# as forgetting.half_life_days), then overlay the current in-memory
|
|
1713
|
-
# dataclass values so a programmatic mutation still survives a
|
|
1714
|
-
# save/load cycle while unmodeled subkeys are never dropped.
|
|
1715
|
-
# dataclasses.asdict() recurses into nested dataclasses
|
|
1716
|
-
# (quantization.polar, quantization.qjl).
|
|
1717
|
-
#
|
|
1718
|
-
# embedding_signature: has no typed in-memory model — it is an opaque blob
|
|
1719
|
-
# written by external tooling (see below).
|
|
1720
|
-
# ``math`` and ``channel_weights`` join this loop as of 4.1.0. Both are
|
|
1721
|
-
# read by engine wiring -- ``math.sheaf_contradiction_threshold`` by the
|
|
1722
|
-
# consistency checker, ``channel_weights`` as the retrieval base weights
|
|
1723
|
-
# -- and neither was ever written here or restored in ``load()``. Tuning
|
|
1724
|
-
# either one, by hand or by switching mode, survived until the next
|
|
1725
|
-
# restart and then silently reverted to the dataclass default, with
|
|
1726
|
-
# nothing said. Reported as #124.
|
|
1727
|
-
for _section, _obj in (
|
|
1728
|
-
("forgetting", self.forgetting),
|
|
1729
|
-
("quantization", self.quantization),
|
|
1730
|
-
("sagq", self.sagq),
|
|
1731
|
-
("auto_invoke", self.auto_invoke),
|
|
1732
|
-
("math", self.math),
|
|
1733
|
-
("channel_weights", self.channel_weights),
|
|
1734
|
-
):
|
|
1735
|
-
_base = existing.get(_section)
|
|
1736
|
-
_merged = dict(_base) if isinstance(_base, dict) else {}
|
|
1737
|
-
_merged.update(asdict(_obj))
|
|
1738
|
-
data[_section] = _merged
|
|
1739
|
-
|
|
1740
|
-
# Merge any keys from the last load() that are not covered by the
|
|
1741
|
-
# explicit serialization above. This includes unknown forward-compat
|
|
1742
|
-
# top-level keys and nested sections whose subkeys this version does
|
|
1743
|
-
# not model (e.g. a "health" dict that carries vendor-extension fields).
|
|
1744
|
-
# Explicitly serialized keys always take precedence; preserved keys are
|
|
1745
|
-
# only written when the slot is vacant in the output dict.
|
|
1746
|
-
_raw = getattr(self, "_raw_preserved", None)
|
|
1747
|
-
if _raw:
|
|
1748
|
-
for _k, _v in _raw.items():
|
|
1749
|
-
if _k not in data:
|
|
1750
|
-
data[_k] = _v
|
|
1751
|
-
|
|
1752
|
-
# embedding_signature has no typed in-memory model — it is an opaque blob
|
|
1753
|
-
# written by external tooling. Preserve the on-disk value verbatim so a
|
|
1754
|
-
# load→save cycle does not erase externally written metadata.
|
|
1755
|
-
if "embedding_signature" in existing:
|
|
1756
|
-
data["embedding_signature"] = existing["embedding_signature"]
|
|
1757
|
-
|
|
1758
|
-
# Atomic write: a crash mid-write must NOT leave a truncated/corrupt
|
|
1759
|
-
# config.json (which would make every subsequent `slm` call fail to load).
|
|
1760
|
-
import os as _os
|
|
1761
|
-
import tempfile as _tempfile
|
|
1762
|
-
|
|
1763
|
-
_fd, _tmp_name = _tempfile.mkstemp(
|
|
1764
|
-
prefix=f".{path.name}.", suffix=".tmp", dir=path.parent,
|
|
1765
|
-
)
|
|
1766
|
-
_tmp = Path(_tmp_name)
|
|
1767
|
-
try:
|
|
1768
|
-
with _os.fdopen(_fd, "w", encoding="utf-8") as _handle:
|
|
1769
|
-
json.dump(data, _handle, indent=2)
|
|
1770
|
-
_handle.write("\n")
|
|
1771
|
-
_handle.flush()
|
|
1772
|
-
_os.fsync(_handle.fileno())
|
|
1773
|
-
_os.chmod(_tmp, 0o600)
|
|
1774
|
-
_os.replace(_tmp, path)
|
|
1775
|
-
_os.chmod(path, 0o600)
|
|
1776
|
-
except BaseException:
|
|
1777
|
-
try:
|
|
1778
|
-
_tmp.unlink(missing_ok=True)
|
|
1779
|
-
except OSError:
|
|
1780
|
-
pass
|
|
1781
|
-
raise
|
|
1782
|
-
|
|
1783
|
-
@staticmethod
|
|
1784
|
-
def provider_presets() -> dict[str, dict[str, str]]:
|
|
1785
|
-
"""Provider presets for setup wizard."""
|
|
1786
|
-
return {
|
|
1787
|
-
"openai": {
|
|
1788
|
-
"base_url": "https://api.openai.com/v1",
|
|
1789
|
-
"model": "gpt-4.1-mini",
|
|
1790
|
-
"embedding_model": "text-embedding-3-large",
|
|
1791
|
-
"env_key": "OPENAI_API_KEY",
|
|
1792
|
-
},
|
|
1793
|
-
"anthropic": {
|
|
1794
|
-
"base_url": "https://api.anthropic.com",
|
|
1795
|
-
"model": "claude-sonnet-4-6",
|
|
1796
|
-
"embedding_model": "",
|
|
1797
|
-
"env_key": "ANTHROPIC_API_KEY",
|
|
1798
|
-
},
|
|
1799
|
-
"ollama": {
|
|
1800
|
-
"base_url": "http://localhost:11434",
|
|
1801
|
-
"model": "llama3.2",
|
|
1802
|
-
"embedding_model": "nomic-embed-text",
|
|
1803
|
-
"env_key": "",
|
|
1804
|
-
},
|
|
1805
|
-
"openrouter": {
|
|
1806
|
-
"base_url": "https://openrouter.ai/api/v1",
|
|
1807
|
-
"model": "openai/gpt-4.1-mini",
|
|
1808
|
-
"embedding_model": "",
|
|
1809
|
-
"env_key": "OPENROUTER_API_KEY",
|
|
1810
|
-
},
|
|
1811
|
-
}
|
|
1812
|
-
|
|
1813
|
-
@classmethod
|
|
1814
|
-
def default(cls) -> SLMConfig:
|
|
1815
|
-
"""Create default Mode A configuration."""
|
|
1816
|
-
return cls.for_mode(Mode.A)
|
|
1817
|
-
|
|
1818
|
-
@classmethod
|
|
1819
|
-
def for_mode(
|
|
1820
|
-
cls,
|
|
1821
|
-
mode: Mode,
|
|
1822
|
-
base_dir: Path | None = None,
|
|
1823
|
-
*,
|
|
1824
|
-
llm_provider: str = "",
|
|
1825
|
-
llm_model: str = "",
|
|
1826
|
-
llm_api_key: str = "",
|
|
1827
|
-
llm_api_base: str = "",
|
|
1828
|
-
embedding_provider: str = "",
|
|
1829
|
-
embedding_endpoint: str = "",
|
|
1830
|
-
embedding_key: str = "",
|
|
1831
|
-
embedding_deployment: str = "",
|
|
1832
|
-
embedding_model_name: str = "",
|
|
1833
|
-
embedding_dimension: int = 0,
|
|
1834
|
-
) -> SLMConfig:
|
|
1835
|
-
"""Create config with mode-appropriate defaults."""
|
|
1836
|
-
# resolve base dir via slm_home() at call time when base_dir is not explicit.
|
|
1837
|
-
if base_dir is None:
|
|
1838
|
-
try:
|
|
1839
|
-
from superlocalmemory.cli._lazy_init import slm_home as _slm_home
|
|
1840
|
-
_base = _slm_home()
|
|
1841
|
-
except Exception:
|
|
1842
|
-
_base = DEFAULT_BASE_DIR
|
|
1843
|
-
else:
|
|
1844
|
-
_base = base_dir
|
|
1845
|
-
|
|
1846
|
-
if mode == Mode.A:
|
|
1847
|
-
# V3.4.24: If user chose "openai" provider, honour their custom
|
|
1848
|
-
# endpoint/model/dimension. Otherwise use local defaults.
|
|
1849
|
-
_a_provider = embedding_provider or "sentence-transformers"
|
|
1850
|
-
if _a_provider == "openai" and embedding_endpoint:
|
|
1851
|
-
_a_emb = EmbeddingConfig(
|
|
1852
|
-
model_name=embedding_model_name or "nomic-ai/nomic-embed-text-v1.5",
|
|
1853
|
-
dimension=embedding_dimension or 768,
|
|
1854
|
-
provider="openai",
|
|
1855
|
-
api_endpoint=embedding_endpoint,
|
|
1856
|
-
api_key=embedding_key,
|
|
1857
|
-
)
|
|
1858
|
-
else:
|
|
1859
|
-
_a_emb = EmbeddingConfig(
|
|
1860
|
-
model_name="nomic-ai/nomic-embed-text-v1.5",
|
|
1861
|
-
dimension=768,
|
|
1862
|
-
provider=_a_provider,
|
|
1863
|
-
)
|
|
1864
|
-
return cls(
|
|
1865
|
-
mode=mode,
|
|
1866
|
-
base_dir=_base,
|
|
1867
|
-
embedding=_a_emb,
|
|
1868
|
-
llm=LLMConfig(), # No LLM
|
|
1869
|
-
# Mode A's promise is that nothing puts a model on any path, and
|
|
1870
|
-
# a subsystem should refuse because the mode says so, not because
|
|
1871
|
-
# it happened to be handed llm=None. Three of the four places
|
|
1872
|
-
# that build the consolidator pass only a database, so today the
|
|
1873
|
-
# guard holds by accident of wiring; this makes it hold by
|
|
1874
|
-
# configuration, which is the thing a reader can check.
|
|
1875
|
-
ccq=CCQConfig(use_llm_gist=False),
|
|
1876
|
-
temporal_validator=TemporalValidatorConfig(mode="a"),
|
|
1877
|
-
retrieval=RetrievalConfig(
|
|
1878
|
-
# V3.3.2: ONNX cross-encoder enabled for all modes (~200MB)
|
|
1879
|
-
use_cross_encoder=True,
|
|
1880
|
-
# V3.3.19: Enable 1 round of rule-based query decomposition.
|
|
1881
|
-
# The enhanced _heuristic_expand generates entity+action
|
|
1882
|
-
# sub-queries that dramatically improve multi-hop retrieval.
|
|
1883
|
-
agentic_max_rounds=1,
|
|
1884
|
-
),
|
|
1885
|
-
math=MathConfig(
|
|
1886
|
-
sheaf_contradiction_threshold=0.45, # 768d threshold
|
|
1887
|
-
),
|
|
1888
|
-
)
|
|
1889
|
-
|
|
1890
|
-
if mode == Mode.B:
|
|
1891
|
-
# V3.4.24: If user chose "openai" provider with a custom endpoint
|
|
1892
|
-
# (e.g. local vLLM, LiteLLM, Ollama /v1), honour it.
|
|
1893
|
-
_b_provider = embedding_provider or "ollama"
|
|
1894
|
-
if _b_provider == "openai" and embedding_endpoint:
|
|
1895
|
-
_b_emb = EmbeddingConfig(
|
|
1896
|
-
model_name=embedding_model_name or "nomic-ai/nomic-embed-text-v1.5",
|
|
1897
|
-
dimension=embedding_dimension or 768,
|
|
1898
|
-
provider="openai",
|
|
1899
|
-
api_endpoint=embedding_endpoint,
|
|
1900
|
-
api_key=embedding_key,
|
|
1901
|
-
)
|
|
1902
|
-
else:
|
|
1903
|
-
_b_emb = EmbeddingConfig(
|
|
1904
|
-
model_name="nomic-ai/nomic-embed-text-v1.5",
|
|
1905
|
-
dimension=768,
|
|
1906
|
-
provider=_b_provider,
|
|
1907
|
-
)
|
|
1908
|
-
return cls(
|
|
1909
|
-
mode=mode,
|
|
1910
|
-
base_dir=_base,
|
|
1911
|
-
embedding=_b_emb,
|
|
1912
|
-
llm=LLMConfig(
|
|
1913
|
-
provider=llm_provider or "ollama",
|
|
1914
|
-
model=llm_model or "llama3.2",
|
|
1915
|
-
api_base=llm_api_base or "http://localhost:11434",
|
|
1916
|
-
api_key=llm_api_key or "",
|
|
1917
|
-
),
|
|
1918
|
-
temporal_validator=TemporalValidatorConfig(mode="b"),
|
|
1919
|
-
retrieval=RetrievalConfig(
|
|
1920
|
-
# V3.3.2: ONNX cross-encoder enabled for all modes (~200MB)
|
|
1921
|
-
use_cross_encoder=True,
|
|
1922
|
-
),
|
|
1923
|
-
)
|
|
1924
|
-
|
|
1925
|
-
# Mode C — FULL POWER, UNRESTRICTED
|
|
1926
|
-
# Don't carry over local-only providers (ollama) to cloud mode
|
|
1927
|
-
c_provider = llm_provider if llm_provider not in ("ollama", "") else "openrouter"
|
|
1928
|
-
c_model = llm_model if llm_provider not in ("ollama", "") else "anthropic/claude-sonnet-4"
|
|
1929
|
-
# V3.4.24: If user chose "openai" provider, honour it in Mode C too.
|
|
1930
|
-
_c_emb_provider = embedding_provider or ""
|
|
1931
|
-
if _c_emb_provider == "openai" and embedding_endpoint:
|
|
1932
|
-
_c_emb = EmbeddingConfig(
|
|
1933
|
-
model_name=embedding_model_name or "text-embedding-3-large",
|
|
1934
|
-
dimension=embedding_dimension or 3072,
|
|
1935
|
-
provider="openai",
|
|
1936
|
-
api_endpoint=embedding_endpoint,
|
|
1937
|
-
api_key=embedding_key,
|
|
1938
|
-
)
|
|
1939
|
-
elif embedding_endpoint:
|
|
1940
|
-
_c_emb = EmbeddingConfig(
|
|
1941
|
-
model_name=embedding_model_name or "text-embedding-3-large",
|
|
1942
|
-
dimension=embedding_dimension or 3072,
|
|
1943
|
-
provider=_c_emb_provider,
|
|
1944
|
-
api_endpoint=embedding_endpoint,
|
|
1945
|
-
api_key=embedding_key,
|
|
1946
|
-
deployment_name=embedding_deployment,
|
|
1947
|
-
)
|
|
1948
|
-
else:
|
|
1949
|
-
# Mode C upgrades LLM capability; it must not silently require a
|
|
1950
|
-
# second paid embedding provider. A 3072-dim cloud embedding
|
|
1951
|
-
# contract without an endpoint/key was auto-routed to the local
|
|
1952
|
-
# 768-dim Ollama embedder, which made ingestion fail at vector
|
|
1953
|
-
# materialization. Cloud embeddings remain an explicit opt-in.
|
|
1954
|
-
_c_emb = EmbeddingConfig(
|
|
1955
|
-
# Honour an on-disk embedding model when one was configured (the
|
|
1956
|
-
# load() path passes it through); default to the local nomic
|
|
1957
|
-
# model so Mode C never silently requires a paid cloud embedder.
|
|
1958
|
-
model_name=embedding_model_name or "nomic-ai/nomic-embed-text-v1.5",
|
|
1959
|
-
dimension=embedding_dimension or 768,
|
|
1960
|
-
)
|
|
1961
|
-
return cls(
|
|
1962
|
-
mode=mode,
|
|
1963
|
-
base_dir=_base,
|
|
1964
|
-
embedding=_c_emb,
|
|
1965
|
-
llm=LLMConfig(
|
|
1966
|
-
provider=c_provider,
|
|
1967
|
-
model=c_model,
|
|
1968
|
-
api_key=llm_api_key,
|
|
1969
|
-
api_base=llm_api_base,
|
|
1970
|
-
),
|
|
1971
|
-
temporal_validator=TemporalValidatorConfig(mode="c"),
|
|
1972
|
-
channel_weights=ChannelWeights(
|
|
1973
|
-
semantic=1.5,
|
|
1974
|
-
bm25=1.2,
|
|
1975
|
-
entity_graph=1.3,
|
|
1976
|
-
temporal=1.0,
|
|
1977
|
-
spreading_activation=1.2, # Phase 3: SA boost in Mode C
|
|
1978
|
-
hopfield=1.0, # Phase G: Hopfield in Mode C
|
|
1979
|
-
),
|
|
1980
|
-
retrieval=RetrievalConfig(
|
|
1981
|
-
use_cross_encoder=True,
|
|
1982
|
-
semantic_top_k=80,
|
|
1983
|
-
agentic_max_rounds=2, # EverMemOS 2-round
|
|
1984
|
-
),
|
|
1985
|
-
math=MathConfig(
|
|
1986
|
-
sheaf_contradiction_threshold=0.65, # Higher for 3072d embeddings
|
|
1987
|
-
),
|
|
1988
|
-
)
|
|
1989
|
-
|
|
1990
|
-
# ------------------------------------------------------------------
|
|
1991
|
-
# 3-Mode config system (v3.4.54)
|
|
1992
|
-
# ------------------------------------------------------------------
|
|
1993
|
-
|
|
1994
|
-
@staticmethod
|
|
1995
|
-
def _mode_config_path(base_dir: Path, mode: "Mode") -> Path:
|
|
1996
|
-
"""Return the per-mode config file path."""
|
|
1997
|
-
from superlocalmemory.storage.models import Mode as _M
|
|
1998
|
-
_names = {
|
|
1999
|
-
_M.A: "mode_a.json",
|
|
2000
|
-
_M.B: "mode_b.json",
|
|
2001
|
-
_M.C: "mode_c.json",
|
|
2002
|
-
}
|
|
2003
|
-
return base_dir / _names.get(mode, "mode_a.json")
|
|
2004
|
-
|
|
2005
|
-
@staticmethod
|
|
2006
|
-
def read_current_mode(base_dir: Path | None = None) -> str:
|
|
2007
|
-
"""Read the current mode from the ``current_mode`` file.
|
|
2008
|
-
|
|
2009
|
-
Returns ``\"b\"`` (the default) if the file doesn't exist.
|
|
2010
|
-
"""
|
|
2011
|
-
# resolve via slm_home() at call time when base_dir is not explicit.
|
|
2012
|
-
if base_dir is None:
|
|
2013
|
-
try:
|
|
2014
|
-
from superlocalmemory.cli._lazy_init import slm_home as _slm_home
|
|
2015
|
-
_base = _slm_home()
|
|
2016
|
-
except Exception:
|
|
2017
|
-
_base = DEFAULT_BASE_DIR
|
|
2018
|
-
else:
|
|
2019
|
-
_base = base_dir
|
|
2020
|
-
_f = _base / CURRENT_MODE_FILE
|
|
2021
|
-
try:
|
|
2022
|
-
return _f.read_text(encoding="utf-8").strip().lower() or "b"
|
|
2023
|
-
except (OSError, FileNotFoundError):
|
|
2024
|
-
return "b"
|
|
2025
|
-
|
|
2026
|
-
@staticmethod
|
|
2027
|
-
def write_current_mode(mode: str, base_dir: Path | None = None) -> None:
|
|
2028
|
-
"""Write the current mode letter to ``current_mode``."""
|
|
2029
|
-
# resolve via slm_home() at call time when base_dir is not explicit.
|
|
2030
|
-
if base_dir is None:
|
|
2031
|
-
try:
|
|
2032
|
-
from superlocalmemory.cli._lazy_init import slm_home as _slm_home
|
|
2033
|
-
_base = _slm_home()
|
|
2034
|
-
except Exception:
|
|
2035
|
-
_base = DEFAULT_BASE_DIR
|
|
2036
|
-
else:
|
|
2037
|
-
_base = base_dir
|
|
2038
|
-
_base.mkdir(parents=True, exist_ok=True)
|
|
2039
|
-
(_base / CURRENT_MODE_FILE).write_text(
|
|
2040
|
-
mode.lower().strip(), encoding="utf-8",
|
|
2041
|
-
)
|
|
2042
|
-
|
|
2043
|
-
@classmethod
|
|
2044
|
-
def switch_mode(
|
|
2045
|
-
cls,
|
|
2046
|
-
new_mode: str,
|
|
2047
|
-
base_dir: Path | None = None,
|
|
2048
|
-
) -> "SLMConfig":
|
|
2049
|
-
"""Switch to a different mode, preserving ALL non-mode settings.
|
|
2050
|
-
|
|
2051
|
-
v3.4.58 fix: Only ``config.mode`` changes. The previous implementation
|
|
2052
|
-
called ``for_mode()`` (which resets embedding/LLM/retrieval to mode
|
|
2053
|
-
defaults) whenever the target mode's per-mode file didn't exist.
|
|
2054
|
-
This silently clobbered custom embeddings, endpoints, and LLM config.
|
|
2055
|
-
|
|
2056
|
-
Correct behavior:
|
|
2057
|
-
- Load the current (old) config from config.json.
|
|
2058
|
-
- Save it to mode_{old}.json for the 3-mode system.
|
|
2059
|
-
- If mode_{new}.json exists, load it (user had a prior config for that mode).
|
|
2060
|
-
- If mode_{new}.json does NOT exist: start from the OLD config, change
|
|
2061
|
-
only the mode field. This preserves embedding, retrieval, forgetting, etc.
|
|
2062
|
-
- Exception: if user has NO LLM provider AND is switching to B/C, populate
|
|
2063
|
-
sensible LLM defaults so the daemon doesn't start dead.
|
|
2064
|
-
- Write the result to config.json (backward compat) and current_mode.
|
|
2065
|
-
|
|
2066
|
-
Returns the new active config.
|
|
2067
|
-
"""
|
|
2068
|
-
import copy
|
|
2069
|
-
import dataclasses
|
|
2070
|
-
|
|
2071
|
-
from superlocalmemory.storage.models import Mode as _M
|
|
2072
|
-
# resolve via slm_home() at call time when base_dir is not explicit.
|
|
2073
|
-
if base_dir is None:
|
|
2074
|
-
try:
|
|
2075
|
-
from superlocalmemory.cli._lazy_init import slm_home as _slm_home
|
|
2076
|
-
_base = _slm_home()
|
|
2077
|
-
except Exception:
|
|
2078
|
-
_base = DEFAULT_BASE_DIR
|
|
2079
|
-
else:
|
|
2080
|
-
_base = base_dir
|
|
2081
|
-
_base.mkdir(parents=True, exist_ok=True)
|
|
2082
|
-
|
|
2083
|
-
old_config = cls.load(_base / "config.json")
|
|
2084
|
-
old_mode = old_config.mode.value.lower()
|
|
2085
|
-
new_mode_val = _M(new_mode.lower())
|
|
2086
|
-
|
|
2087
|
-
# 1. Save current config to its per-mode file (preserve customizations)
|
|
2088
|
-
if old_mode != new_mode.lower():
|
|
2089
|
-
old_path = cls._mode_config_path(_base, old_config.mode)
|
|
2090
|
-
old_config.save(old_path)
|
|
2091
|
-
|
|
2092
|
-
# 2. Determine new config:
|
|
2093
|
-
# a) mode_{new}.json exists → user had prior config for this mode, use it
|
|
2094
|
-
# b) otherwise → copy old config, change only mode
|
|
2095
|
-
new_path = cls._mode_config_path(_base, new_mode_val)
|
|
2096
|
-
if new_path.exists():
|
|
2097
|
-
try:
|
|
2098
|
-
new_config = cls.load(new_path)
|
|
2099
|
-
if new_config.mode != new_mode_val:
|
|
2100
|
-
# Corrupt/mismatched mode file — rebuild preserving old values
|
|
2101
|
-
new_config = copy.copy(old_config)
|
|
2102
|
-
new_config.mode = new_mode_val
|
|
2103
|
-
except Exception:
|
|
2104
|
-
new_config = copy.copy(old_config)
|
|
2105
|
-
new_config.mode = new_mode_val
|
|
2106
|
-
else:
|
|
2107
|
-
# First time switching to this mode: start from old config, change mode only.
|
|
2108
|
-
# Use dataclasses.replace() to produce a new frozen-compatible object.
|
|
2109
|
-
new_config = dataclasses.replace(old_config, mode=new_mode_val)
|
|
2110
|
-
|
|
2111
|
-
# 3. LLM default population — ONLY if user has no provider AND switching to B/C.
|
|
2112
|
-
# Never overwrites an existing provider.
|
|
2113
|
-
if new_mode_val in (_M.B, _M.C) and not new_config.llm.provider:
|
|
2114
|
-
if new_mode_val == _M.B:
|
|
2115
|
-
new_config = dataclasses.replace(
|
|
2116
|
-
new_config,
|
|
2117
|
-
llm=LLMConfig(
|
|
2118
|
-
provider="ollama",
|
|
2119
|
-
model="llama3.2",
|
|
2120
|
-
api_base="http://localhost:11434",
|
|
2121
|
-
),
|
|
2122
|
-
)
|
|
2123
|
-
else: # Mode C
|
|
2124
|
-
new_config = dataclasses.replace(
|
|
2125
|
-
new_config,
|
|
2126
|
-
llm=LLMConfig(
|
|
2127
|
-
provider="openrouter",
|
|
2128
|
-
model="anthropic/claude-sonnet-4",
|
|
2129
|
-
),
|
|
2130
|
-
)
|
|
2131
|
-
|
|
2132
|
-
# 4. Save as active config.json (backward compat)
|
|
2133
|
-
new_config.save(_base / "config.json", mode_change=True)
|
|
2134
|
-
|
|
2135
|
-
# 5. Write current_mode
|
|
2136
|
-
cls.write_current_mode(new_mode, _base)
|
|
2137
|
-
|
|
2138
|
-
return new_config
|
|
2139
|
-
|
|
2140
|
-
@classmethod
|
|
2141
|
-
def migrate_to_3mode(cls, base_dir: Path | None = None) -> bool:
|
|
2142
|
-
"""One-time migration: config.json → 3-mode system.
|
|
2143
|
-
|
|
2144
|
-
Called on daemon boot. Idempotent — if ``current_mode`` already
|
|
2145
|
-
exists, this is a no-op. Returns True if migration was performed.
|
|
2146
|
-
"""
|
|
2147
|
-
# resolve via slm_home() at call time when base_dir is not explicit.
|
|
2148
|
-
if base_dir is None:
|
|
2149
|
-
try:
|
|
2150
|
-
from superlocalmemory.cli._lazy_init import slm_home as _slm_home
|
|
2151
|
-
_base = _slm_home()
|
|
2152
|
-
except Exception:
|
|
2153
|
-
_base = DEFAULT_BASE_DIR
|
|
2154
|
-
else:
|
|
2155
|
-
_base = base_dir
|
|
2156
|
-
_current = _base / CURRENT_MODE_FILE
|
|
2157
|
-
if _current.exists():
|
|
2158
|
-
return False # already migrated
|
|
2159
|
-
|
|
2160
|
-
legacy = _base / "config.json"
|
|
2161
|
-
if not legacy.exists():
|
|
2162
|
-
# No config at all — write defaults and current_mode
|
|
2163
|
-
from superlocalmemory.storage.models import Mode as _M
|
|
2164
|
-
_def = cls.for_mode(_M.B, base_dir=_base)
|
|
2165
|
-
_def.save(legacy)
|
|
2166
|
-
cls.write_current_mode("b", _base)
|
|
2167
|
-
for _m in (_M.A, _M.B, _M.C):
|
|
2168
|
-
_mp = cls._mode_config_path(_base, _m)
|
|
2169
|
-
if not _mp.exists():
|
|
2170
|
-
try:
|
|
2171
|
-
_mc = cls.for_mode(_m, base_dir=_base)
|
|
2172
|
-
_mc.save(_mp)
|
|
2173
|
-
except Exception:
|
|
2174
|
-
pass
|
|
2175
|
-
return True
|
|
2176
|
-
|
|
2177
|
-
# Migrate existing config.json → mode_{current}.json
|
|
2178
|
-
try:
|
|
2179
|
-
config = cls.load(legacy)
|
|
2180
|
-
current = config.mode.value.lower()
|
|
2181
|
-
cls.write_current_mode(current, _base)
|
|
2182
|
-
|
|
2183
|
-
# Save current config to its mode file
|
|
2184
|
-
mode_path = cls._mode_config_path(_base, config.mode)
|
|
2185
|
-
config.save(mode_path)
|
|
2186
|
-
|
|
2187
|
-
# Generate other mode files from defaults
|
|
2188
|
-
from superlocalmemory.storage.models import Mode as _M
|
|
2189
|
-
for _m in (_M.A, _M.B, _M.C):
|
|
2190
|
-
_mp = cls._mode_config_path(_base, _m)
|
|
2191
|
-
if not _mp.exists():
|
|
2192
|
-
try:
|
|
2193
|
-
_mc = cls.for_mode(_m, base_dir=_base)
|
|
2194
|
-
_mc.save(_mp)
|
|
2195
|
-
except Exception:
|
|
2196
|
-
pass
|
|
2197
|
-
|
|
2198
|
-
logger.info(
|
|
2199
|
-
"3-mode config system migrated: config.json → mode_%s.json. "
|
|
2200
|
-
"All three mode configs generated.", current,
|
|
2201
|
-
)
|
|
2202
|
-
return True
|
|
2203
|
-
except Exception:
|
|
2204
|
-
return False
|