superlocalmemory 4.1.13 → 4.1.15
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 +103 -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,909 +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
|
-
"""SLM Daemon — client functions for communicating with the unified daemon.
|
|
6
|
-
|
|
7
|
-
The unified daemon (server/unified_daemon.py) runs as a single FastAPI/uvicorn
|
|
8
|
-
process on port 8765, with port 8767 as a backward-compat TCP redirect.
|
|
9
|
-
|
|
10
|
-
This module contains CLIENT functions used by CLI commands:
|
|
11
|
-
- is_daemon_running(): check if daemon is alive
|
|
12
|
-
- ensure_daemon(): start daemon if not running
|
|
13
|
-
- stop_daemon(): gracefully stop the daemon
|
|
14
|
-
- daemon_request(): send HTTP request to daemon
|
|
15
|
-
|
|
16
|
-
The actual daemon server code is in server/unified_daemon.py.
|
|
17
|
-
|
|
18
|
-
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
19
|
-
License: AGPL-3.0-or-later
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
from __future__ import annotations
|
|
23
|
-
|
|
24
|
-
import json
|
|
25
|
-
import logging
|
|
26
|
-
import os
|
|
27
|
-
import socket
|
|
28
|
-
import sys
|
|
29
|
-
import time
|
|
30
|
-
from dataclasses import replace
|
|
31
|
-
|
|
32
|
-
from superlocalmemory.infra.daemon_identity import (
|
|
33
|
-
build_descriptor,
|
|
34
|
-
descriptor_matches_health,
|
|
35
|
-
descriptor_path,
|
|
36
|
-
process_create_time_for,
|
|
37
|
-
read_descriptor,
|
|
38
|
-
write_descriptor,
|
|
39
|
-
)
|
|
40
|
-
from superlocalmemory.infra.data_root import (
|
|
41
|
-
assert_no_durable_root_conflict,
|
|
42
|
-
state_path,
|
|
43
|
-
)
|
|
44
|
-
from superlocalmemory.infra.process_identity import (
|
|
45
|
-
compare_start_tokens,
|
|
46
|
-
process_start_token_for,
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
logger = logging.getLogger(__name__)
|
|
50
|
-
|
|
51
|
-
try:
|
|
52
|
-
_DEFAULT_PORT = int(os.environ.get("SLM_DAEMON_PORT", "") or 8765)
|
|
53
|
-
except ValueError:
|
|
54
|
-
_DEFAULT_PORT = 8765
|
|
55
|
-
_LEGACY_PORT = 8767 # backward-compat redirect
|
|
56
|
-
_DEFAULT_IDLE_TIMEOUT = 0 # v3.4.3: 24/7 default (was 1800)
|
|
57
|
-
_PID_FILE = None # test-only override; runtime resolution stays dynamic
|
|
58
|
-
_PORT_FILE = None # test-only override; runtime resolution stays dynamic
|
|
59
|
-
_EXPECTED_DESCRIPTOR_UNSET = object()
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# ---------------------------------------------------------------------------
|
|
63
|
-
# Client: check if daemon running + send requests
|
|
64
|
-
# ---------------------------------------------------------------------------
|
|
65
|
-
|
|
66
|
-
def _is_pid_alive(pid: int) -> bool:
|
|
67
|
-
"""Cross-platform check if a process with given PID exists."""
|
|
68
|
-
try:
|
|
69
|
-
import psutil
|
|
70
|
-
return psutil.pid_exists(pid)
|
|
71
|
-
except ImportError:
|
|
72
|
-
try:
|
|
73
|
-
os.kill(pid, 0)
|
|
74
|
-
return True
|
|
75
|
-
except (ProcessLookupError, PermissionError):
|
|
76
|
-
return False
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
_CREATE_TIME_TOLERANCE_SECONDS = 1.0
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def _health_proves_descriptor_ownership(descriptor) -> bool:
|
|
83
|
-
"""Return whether the live health endpoint proves this exact daemon.
|
|
84
|
-
|
|
85
|
-
This is a *stronger* ownership proof than any process-table comparison. To
|
|
86
|
-
pass, a process listening on the descriptor's port must echo the random
|
|
87
|
-
128-bit ``instance_id`` and the SHA-256 fingerprint of the 256-bit
|
|
88
|
-
capability token -- both of which exist only inside the mode-0600
|
|
89
|
-
``daemon.json`` -- alongside its own PID, namespace, owner and port. A
|
|
90
|
-
process that merely inherited a recycled PID cannot produce any of that.
|
|
91
|
-
"""
|
|
92
|
-
health = _fetch_health(descriptor.port)
|
|
93
|
-
if health is None:
|
|
94
|
-
return False
|
|
95
|
-
return descriptor_matches_health(descriptor, health)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def _resolve_descriptor_liveness(descriptor) -> tuple[bool, str]:
|
|
99
|
-
"""Return ``(is_alive, evidence)`` for the descriptor's recorded process.
|
|
100
|
-
|
|
101
|
-
Ownership is decided by the strongest available evidence, never by the
|
|
102
|
-
wall clock alone:
|
|
103
|
-
|
|
104
|
-
1. The PID must exist and must not be a zombie.
|
|
105
|
-
2. A clock-independent start token settles it exactly, with no tolerance.
|
|
106
|
-
This is the path that fixes issue #104: under WSL2 the boot time behind
|
|
107
|
-
``psutil.create_time`` drifts against the wall clock during a session,
|
|
108
|
-
so a recorded creation time stops matching the *same* live process
|
|
109
|
-
(~35s after ~4 minutes). A start token cannot drift, so no tolerance
|
|
110
|
-
constant is needed and none can silently expire.
|
|
111
|
-
3. Otherwise fall back to comparing creation times, for descriptors written
|
|
112
|
-
by an older release and for platforms with no token (Windows, where the
|
|
113
|
-
kernel creation time is already immune to clock adjustment).
|
|
114
|
-
4. A creation-time mismatch is *not* proof of PID reuse -- it is exactly
|
|
115
|
-
what a stepped clock looks like -- so before condemning a running
|
|
116
|
-
daemon, ask the daemon to prove its identity over loopback. Only if that
|
|
117
|
-
cryptographic proof also fails is the process declared foreign.
|
|
118
|
-
"""
|
|
119
|
-
if not _is_pid_alive(descriptor.pid):
|
|
120
|
-
return False, "process_exited"
|
|
121
|
-
try:
|
|
122
|
-
import psutil
|
|
123
|
-
except ImportError:
|
|
124
|
-
# Without psutil, PID existence is the only signal there is.
|
|
125
|
-
return True, "pid_exists_without_psutil"
|
|
126
|
-
try:
|
|
127
|
-
process = psutil.Process(descriptor.pid)
|
|
128
|
-
# A terminated daemon can remain in the process table briefly as a
|
|
129
|
-
# zombie while its parent reaps it. PID existence is therefore not
|
|
130
|
-
# liveness and must not block a namespace-owned restart.
|
|
131
|
-
if not process.is_running() or process.status() == psutil.STATUS_ZOMBIE:
|
|
132
|
-
return False, "process_zombie"
|
|
133
|
-
actual_create_time = float(process.create_time())
|
|
134
|
-
except Exception:
|
|
135
|
-
return False, "process_unreadable"
|
|
136
|
-
|
|
137
|
-
recorded_token = getattr(descriptor, "process_start_token", None)
|
|
138
|
-
if recorded_token:
|
|
139
|
-
verdict = compare_start_tokens(
|
|
140
|
-
recorded_token, process_start_token_for(descriptor.pid),
|
|
141
|
-
)
|
|
142
|
-
if verdict is True:
|
|
143
|
-
return True, "start_token_match"
|
|
144
|
-
if verdict is False:
|
|
145
|
-
return False, "start_token_mismatch"
|
|
146
|
-
|
|
147
|
-
drift = abs(actual_create_time - float(descriptor.process_create_time))
|
|
148
|
-
if drift <= _CREATE_TIME_TOLERANCE_SECONDS:
|
|
149
|
-
return True, "create_time_match"
|
|
150
|
-
|
|
151
|
-
if _health_proves_descriptor_ownership(descriptor):
|
|
152
|
-
logger.debug(
|
|
153
|
-
"descriptor creation time drifted by %.3fs for pid %s; owned "
|
|
154
|
-
"daemon confirmed by health identity instead",
|
|
155
|
-
drift, descriptor.pid,
|
|
156
|
-
)
|
|
157
|
-
return True, "health_identity_match"
|
|
158
|
-
return False, "identity_mismatch"
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
def _descriptor_process_is_alive(descriptor) -> bool:
|
|
162
|
-
"""Reject stale descriptors when a PID has been reused by another process."""
|
|
163
|
-
return _resolve_descriptor_liveness(descriptor)[0]
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def _is_port_available(port: int) -> bool:
|
|
167
|
-
"""Return whether the daemon port can be exclusively bound right now."""
|
|
168
|
-
try:
|
|
169
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as candidate:
|
|
170
|
-
if sys.platform == "win32":
|
|
171
|
-
# Winsock SO_REUSEADDR can bind an address that is still
|
|
172
|
-
# occupied, so it cannot prove shutdown completion. Request
|
|
173
|
-
# exclusive ownership where available and otherwise use the
|
|
174
|
-
# default non-reuse bind contract.
|
|
175
|
-
exclusive = getattr(socket, "SO_EXCLUSIVEADDRUSE", None)
|
|
176
|
-
if exclusive is not None:
|
|
177
|
-
candidate.setsockopt(socket.SOL_SOCKET, exclusive, 1)
|
|
178
|
-
else:
|
|
179
|
-
# On POSIX, mirror Uvicorn's reuse contract so a closed
|
|
180
|
-
# listener's TIME_WAIT sockets do not block a safe restart.
|
|
181
|
-
candidate.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
182
|
-
candidate.bind(("127.0.0.1", port))
|
|
183
|
-
return True
|
|
184
|
-
except OSError:
|
|
185
|
-
return False
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
def _has_tcp_listener(port: int) -> bool:
|
|
189
|
-
"""Return whether a process is actively accepting on the daemon port."""
|
|
190
|
-
try:
|
|
191
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as candidate:
|
|
192
|
-
candidate.settimeout(0.5)
|
|
193
|
-
return candidate.connect_ex(("127.0.0.1", port)) == 0
|
|
194
|
-
except OSError:
|
|
195
|
-
return False
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def wait_for_owned_daemon_shutdown(
|
|
199
|
-
descriptor,
|
|
200
|
-
timeout: float = 25.0,
|
|
201
|
-
*,
|
|
202
|
-
legacy_pid: int | None = None,
|
|
203
|
-
legacy_port: int | None = None,
|
|
204
|
-
) -> bool:
|
|
205
|
-
"""Wait for the stopped instance *and* its TCP listener to be gone.
|
|
206
|
-
|
|
207
|
-
Restart must never spawn a replacement just because the descriptor was
|
|
208
|
-
removed: a graceful shutdown can remove it before Uvicorn releases the
|
|
209
|
-
port. The 25-second budget covers Uvicorn's 10-second graceful drain plus
|
|
210
|
-
SLM worker cleanup. A descriptor carries process creation time, so PID
|
|
211
|
-
reuse cannot make this wait target an unrelated process.
|
|
212
|
-
"""
|
|
213
|
-
port = (
|
|
214
|
-
descriptor.port
|
|
215
|
-
if descriptor is not None
|
|
216
|
-
else legacy_port if legacy_port is not None else _DEFAULT_PORT
|
|
217
|
-
)
|
|
218
|
-
deadline = time.monotonic() + timeout
|
|
219
|
-
while time.monotonic() < deadline:
|
|
220
|
-
process_alive = bool(
|
|
221
|
-
_descriptor_process_is_alive(descriptor)
|
|
222
|
-
if descriptor is not None
|
|
223
|
-
else legacy_pid is not None and _is_verified_legacy_process(legacy_pid)
|
|
224
|
-
)
|
|
225
|
-
if not process_alive and _is_port_available(port):
|
|
226
|
-
return True
|
|
227
|
-
time.sleep(0.1)
|
|
228
|
-
return False
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
def is_daemon_running() -> bool:
|
|
232
|
-
"""Return True only for a daemon owned by this canonical data namespace.
|
|
233
|
-
|
|
234
|
-
A PID or an HTTP 200 proves liveness, not ownership. V3.7 requires the
|
|
235
|
-
private local descriptor and the health endpoint to agree on namespace,
|
|
236
|
-
process instance, capability fingerprint, owner, PID, protocol, and port.
|
|
237
|
-
"""
|
|
238
|
-
local_descriptor_path = descriptor_path()
|
|
239
|
-
descriptor = read_descriptor()
|
|
240
|
-
if descriptor is not None:
|
|
241
|
-
if not _descriptor_process_is_alive(descriptor):
|
|
242
|
-
return False
|
|
243
|
-
if descriptor.state == "starting":
|
|
244
|
-
return True
|
|
245
|
-
health = _fetch_health(descriptor.port)
|
|
246
|
-
return health is not None and descriptor_matches_health(descriptor, health)
|
|
247
|
-
|
|
248
|
-
# A malformed or foreign descriptor must fail closed; never fall through
|
|
249
|
-
# to legacy PID/port adoption in the same namespace.
|
|
250
|
-
if local_descriptor_path.exists():
|
|
251
|
-
return False
|
|
252
|
-
|
|
253
|
-
legacy = _verified_legacy_health()
|
|
254
|
-
return legacy is not None
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
def owned_daemon_process_alive() -> bool:
|
|
258
|
-
"""Return whether an owned daemon *process* is alive, HTTP aside.
|
|
259
|
-
|
|
260
|
-
``is_daemon_running()`` additionally requires a live, matching
|
|
261
|
-
``/health`` response, which conflates two different questions: "is there
|
|
262
|
-
a process I need to stop" and "is it ready to serve requests right now."
|
|
263
|
-
A daemon whose event loop is synchronously blocked by a long-running
|
|
264
|
-
handler (``/maintenance/run``, ``/consolidate/cognitive``) cannot answer
|
|
265
|
-
the second for the duration of that call, but the answer to the first is
|
|
266
|
-
still yes. Callers that only need to decide whether a stop is owed
|
|
267
|
-
(``slm restart`` Step 1) should use this instead, so a transiently busy
|
|
268
|
-
daemon is not skipped as "already stopped" while it keeps running and
|
|
269
|
-
holding the port — which then made Step 3 refuse to start a second
|
|
270
|
-
daemon on the still-occupied port and fail the whole restart.
|
|
271
|
-
"""
|
|
272
|
-
descriptor = read_descriptor()
|
|
273
|
-
if descriptor is not None:
|
|
274
|
-
return _descriptor_process_is_alive(descriptor)
|
|
275
|
-
if descriptor_path().exists():
|
|
276
|
-
return False
|
|
277
|
-
return _verified_legacy_health() is not None
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
def _fetch_health(port: int) -> dict | None:
|
|
281
|
-
"""Fetch loopback health without following cross-namespace discovery."""
|
|
282
|
-
try:
|
|
283
|
-
import urllib.request
|
|
284
|
-
|
|
285
|
-
expected_url = f"http://127.0.0.1:{port}/health"
|
|
286
|
-
response = urllib.request.urlopen(
|
|
287
|
-
expected_url, timeout=2,
|
|
288
|
-
)
|
|
289
|
-
if response.status != 200:
|
|
290
|
-
return None
|
|
291
|
-
geturl = getattr(response, "geturl", None)
|
|
292
|
-
final_url = geturl() if callable(geturl) else None
|
|
293
|
-
if final_url is not None and final_url != expected_url:
|
|
294
|
-
return None
|
|
295
|
-
payload = json.loads(response.read().decode())
|
|
296
|
-
return payload if isinstance(payload, dict) else None
|
|
297
|
-
except Exception:
|
|
298
|
-
return None
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
def _is_verified_legacy_process(pid: int) -> bool:
|
|
302
|
-
"""One-release bridge for a same-root V3.6 unified-daemon process."""
|
|
303
|
-
if not _is_pid_alive(pid):
|
|
304
|
-
return False
|
|
305
|
-
try:
|
|
306
|
-
import psutil
|
|
307
|
-
|
|
308
|
-
process = psutil.Process(pid)
|
|
309
|
-
command = " ".join(process.cmdline())
|
|
310
|
-
return "superlocalmemory.server.unified_daemon" in command
|
|
311
|
-
except Exception:
|
|
312
|
-
return False
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
def _verified_legacy_health() -> dict | None:
|
|
316
|
-
"""Accept legacy health only with a verified same-root daemon PID file."""
|
|
317
|
-
pid_file = descriptor_path().with_name("daemon.pid")
|
|
318
|
-
port_file = descriptor_path().with_name("daemon.port")
|
|
319
|
-
try:
|
|
320
|
-
pid = int(pid_file.read_text().strip())
|
|
321
|
-
port = int(port_file.read_text().strip()) if port_file.exists() else _DEFAULT_PORT
|
|
322
|
-
except (OSError, ValueError):
|
|
323
|
-
return None
|
|
324
|
-
if not _is_verified_legacy_process(pid):
|
|
325
|
-
return None
|
|
326
|
-
health = _fetch_health(port)
|
|
327
|
-
if health is None or int(health.get("pid", -1)) != pid:
|
|
328
|
-
return None
|
|
329
|
-
# Identity-bearing health without a descriptor is not legacy and cannot
|
|
330
|
-
# be adopted. It belongs to another namespace or stale state.
|
|
331
|
-
if health.get("daemon_protocol") is not None:
|
|
332
|
-
return None
|
|
333
|
-
return {**health, "_legacy_port": port}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
def _get_port() -> int:
|
|
337
|
-
descriptor = read_descriptor()
|
|
338
|
-
if descriptor is not None:
|
|
339
|
-
return descriptor.port
|
|
340
|
-
if descriptor_path().exists():
|
|
341
|
-
return _DEFAULT_PORT
|
|
342
|
-
legacy = _verified_legacy_health()
|
|
343
|
-
if legacy is not None:
|
|
344
|
-
return int(legacy["_legacy_port"])
|
|
345
|
-
return _DEFAULT_PORT
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
class DaemonRefused(RuntimeError):
|
|
349
|
-
"""The daemon answered, and the answer was no.
|
|
350
|
-
|
|
351
|
-
Raised for HTTP 401 and 403 only. Distinct from ``daemon_request``
|
|
352
|
-
returning ``None``, which means the daemon could not be reached or did not
|
|
353
|
-
answer usefully. Callers that fall back to a direct engine write MUST let
|
|
354
|
-
this propagate or exit on it: falling back after a refusal performs, as the
|
|
355
|
-
machine owner, exactly the write the workspace just declined.
|
|
356
|
-
"""
|
|
357
|
-
|
|
358
|
-
def __init__(self, status: int, path: str = "") -> None:
|
|
359
|
-
self.status = int(status)
|
|
360
|
-
self.path = path
|
|
361
|
-
super().__init__(
|
|
362
|
-
f"the daemon refused this request (HTTP {status})"
|
|
363
|
-
+ (f" for {path}" if path else "")
|
|
364
|
-
)
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
class DaemonConflict(RuntimeError):
|
|
368
|
-
"""A deterministic daemon conflict that the caller must resolve."""
|
|
369
|
-
|
|
370
|
-
def __init__(self, detail: str) -> None:
|
|
371
|
-
self.detail = detail or "daemon request conflicted with current state"
|
|
372
|
-
super().__init__(self.detail)
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
def daemon_request(
|
|
376
|
-
method: str,
|
|
377
|
-
path: str,
|
|
378
|
-
body: dict | None = None,
|
|
379
|
-
*,
|
|
380
|
-
timeout_seconds: float = 30.0,
|
|
381
|
-
expected_descriptor=_EXPECTED_DESCRIPTOR_UNSET,
|
|
382
|
-
expected_legacy: dict | None = None,
|
|
383
|
-
verify_health: bool = True,
|
|
384
|
-
preserve_conflict: bool = False,
|
|
385
|
-
) -> dict | None:
|
|
386
|
-
"""Send a request only after validating the owned daemon identity.
|
|
387
|
-
|
|
388
|
-
``verify_health`` — when True (the default), a ``GET /health`` preflight
|
|
389
|
-
must succeed and match the descriptor before the real request is sent.
|
|
390
|
-
That preflight needs the daemon's event loop to be free to answer HTTP,
|
|
391
|
-
which is a *readiness* question, not a *liveness* one: a daemon whose
|
|
392
|
-
loop is synchronously blocked by a long-running handler (e.g.
|
|
393
|
-
``/maintenance/run``, ``/consolidate/cognitive``, neither of which is
|
|
394
|
-
offloaded to a thread the way ``/recall`` was for exactly this reason in
|
|
395
|
-
v3.4.52) cannot answer /health for the duration of that call even though
|
|
396
|
-
the process is fully alive and listening. Callers that have already
|
|
397
|
-
proven process-level ownership some other way (e.g. ``stop_daemon()`` via
|
|
398
|
-
``_descriptor_process_is_alive``) should pass ``verify_health=False`` so a
|
|
399
|
-
busy-but-alive daemon does not get misreported as not running. Only
|
|
400
|
-
meaningful for the descriptor path — the legacy bridge has no capability
|
|
401
|
-
header and still needs health to identify its target.
|
|
402
|
-
"""
|
|
403
|
-
legacy = None
|
|
404
|
-
if expected_legacy is not None:
|
|
405
|
-
# Legacy daemons have no capability header. Bind the compatibility
|
|
406
|
-
# request to the captured PID+port and refuse to adopt a replacement
|
|
407
|
-
# descriptor or a different legacy process during this stop.
|
|
408
|
-
if read_descriptor() is not None:
|
|
409
|
-
return None
|
|
410
|
-
current_legacy = _verified_legacy_health()
|
|
411
|
-
if current_legacy is None or (
|
|
412
|
-
int(current_legacy.get("pid", -1))
|
|
413
|
-
!= int(expected_legacy.get("pid", -2))
|
|
414
|
-
or int(current_legacy.get("_legacy_port", -1))
|
|
415
|
-
!= int(expected_legacy.get("_legacy_port", -2))
|
|
416
|
-
):
|
|
417
|
-
return None
|
|
418
|
-
descriptor = None
|
|
419
|
-
legacy = current_legacy
|
|
420
|
-
else:
|
|
421
|
-
descriptor = (
|
|
422
|
-
read_descriptor()
|
|
423
|
-
if expected_descriptor is _EXPECTED_DESCRIPTOR_UNSET
|
|
424
|
-
else expected_descriptor
|
|
425
|
-
)
|
|
426
|
-
capability: str | None = None
|
|
427
|
-
target_instance: str | None = None
|
|
428
|
-
if descriptor is not None:
|
|
429
|
-
if verify_health:
|
|
430
|
-
health = _fetch_health(descriptor.port)
|
|
431
|
-
if health is None or not descriptor_matches_health(descriptor, health):
|
|
432
|
-
return None
|
|
433
|
-
if method.upper() == "GET" and path == "/health":
|
|
434
|
-
return health
|
|
435
|
-
port = descriptor.port
|
|
436
|
-
capability = descriptor.capability
|
|
437
|
-
target_instance = descriptor.instance_id
|
|
438
|
-
elif descriptor_path().exists():
|
|
439
|
-
return None
|
|
440
|
-
else:
|
|
441
|
-
legacy = legacy or _verified_legacy_health()
|
|
442
|
-
if legacy is None:
|
|
443
|
-
return None
|
|
444
|
-
if method.upper() == "GET" and path == "/health":
|
|
445
|
-
return {key: value for key, value in legacy.items() if key != "_legacy_port"}
|
|
446
|
-
port = int(legacy["_legacy_port"])
|
|
447
|
-
try:
|
|
448
|
-
import urllib.error
|
|
449
|
-
import urllib.request
|
|
450
|
-
url = f"http://127.0.0.1:{port}{path}"
|
|
451
|
-
data = json.dumps(body).encode() if body else None
|
|
452
|
-
headers = {"Content-Type": "application/json"} if data else {}
|
|
453
|
-
if capability is not None and target_instance is not None:
|
|
454
|
-
headers["X-SLM-Daemon-Capability"] = capability
|
|
455
|
-
headers["X-SLM-Target-Instance"] = target_instance
|
|
456
|
-
# Daemon ownership proves that this CLI targets the local instance; it
|
|
457
|
-
# does not replace a dashboard user's profile-scoped authorization in
|
|
458
|
-
# governed workspaces. The user opts in by supplying an explicit
|
|
459
|
-
# session through the process environment (never logged or persisted).
|
|
460
|
-
user_session = os.environ.get("SLM_USER_SESSION", "").strip()
|
|
461
|
-
if user_session:
|
|
462
|
-
headers["X-SLM-User-Session"] = user_session
|
|
463
|
-
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
|
464
|
-
resp = urllib.request.urlopen(req, timeout=timeout_seconds)
|
|
465
|
-
return json.loads(resp.read().decode())
|
|
466
|
-
except urllib.error.HTTPError as exc:
|
|
467
|
-
# A refusal is an answer, not a failure to get one. Returning None here
|
|
468
|
-
# made "you are not allowed to do this" indistinguishable from "the
|
|
469
|
-
# daemon is not running", and every caller that falls back to a local
|
|
470
|
-
# engine write treated the first as the second -- so a workspace that
|
|
471
|
-
# required a login refused the write over HTTP and then performed it
|
|
472
|
-
# locally as the machine owner.
|
|
473
|
-
if exc.code in (401, 403):
|
|
474
|
-
raise DaemonRefused(exc.code, path) from exc
|
|
475
|
-
if exc.code == 409 and preserve_conflict:
|
|
476
|
-
detail = "daemon request conflicted with current state"
|
|
477
|
-
try:
|
|
478
|
-
payload = json.loads(exc.read().decode())
|
|
479
|
-
if isinstance(payload, dict) and payload.get("detail"):
|
|
480
|
-
detail = str(payload["detail"])
|
|
481
|
-
except Exception:
|
|
482
|
-
pass
|
|
483
|
-
raise DaemonConflict(detail) from exc
|
|
484
|
-
return None
|
|
485
|
-
except Exception:
|
|
486
|
-
return None
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
_LOCK_FILE = None # test-only override; runtime resolution stays dynamic
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
def _pid_file_path():
|
|
493
|
-
return _PID_FILE or state_path("daemon.pid")
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
def _port_file_path():
|
|
497
|
-
return _PORT_FILE or state_path("daemon.port")
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
def _lock_file_path():
|
|
501
|
-
return _LOCK_FILE or state_path("daemon.lock")
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
def _start_daemon_subprocess() -> bool:
|
|
505
|
-
"""Spawn the unified daemon subprocess and wait for readiness.
|
|
506
|
-
|
|
507
|
-
v3.4.42: Extracted from ensure_daemon() so callers that already hold
|
|
508
|
-
daemon.lock (e.g. cmd_restart Step 2) can start the daemon WITHOUT
|
|
509
|
-
triggering a second flock acquisition. BSD-style flock blocks per-fd
|
|
510
|
-
even within the same process, so the previous code path produced a
|
|
511
|
-
self-deadlock when called from Step 3 of `slm restart`: the lock held
|
|
512
|
-
by Step 2 caused ensure_daemon's own flock to fail with EWOULDBLOCK,
|
|
513
|
-
falling into the wait-for-someone-else branch and timing out at 60s
|
|
514
|
-
even though the daemon would have started cleanly.
|
|
515
|
-
|
|
516
|
-
PRECONDITION: caller has either acquired daemon.lock OR is certain no
|
|
517
|
-
other CLI/MCP process is racing to start a daemon (e.g. we just killed
|
|
518
|
-
everything in `slm restart` Step 1).
|
|
519
|
-
|
|
520
|
-
Returns True if daemon is reachable on the health endpoint within
|
|
521
|
-
60 seconds, False otherwise.
|
|
522
|
-
"""
|
|
523
|
-
if is_daemon_running():
|
|
524
|
-
return True
|
|
525
|
-
# Never create a descriptor for a child that cannot own the listener.
|
|
526
|
-
# A closed connection in TIME_WAIT is not a listener and is safe: the
|
|
527
|
-
# server reserves its socket with SO_REUSEADDR during bootstrap.
|
|
528
|
-
if _has_tcp_listener(_DEFAULT_PORT):
|
|
529
|
-
logger.warning("daemon port %d is already owned; refusing a second start", _DEFAULT_PORT)
|
|
530
|
-
return False
|
|
531
|
-
assert_no_durable_root_conflict()
|
|
532
|
-
|
|
533
|
-
import subprocess
|
|
534
|
-
|
|
535
|
-
from superlocalmemory import __version__ as _slm_version
|
|
536
|
-
# v3.6.9 (#33): pass SLM_DAEMON_PORT as explicit --port= so the daemon
|
|
537
|
-
# binds the right port even when the env var reaches the subprocess.
|
|
538
|
-
_target_port = _DEFAULT_PORT
|
|
539
|
-
cmd = [
|
|
540
|
-
sys.executable, "-m", "superlocalmemory.server.unified_daemon",
|
|
541
|
-
"--start", f"--port={_target_port}",
|
|
542
|
-
]
|
|
543
|
-
log_dir = state_path("logs")
|
|
544
|
-
log_dir.mkdir(parents=True, exist_ok=True)
|
|
545
|
-
log_file = log_dir / "daemon.log"
|
|
546
|
-
|
|
547
|
-
kwargs: dict = {}
|
|
548
|
-
if sys.platform == "win32":
|
|
549
|
-
kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
|
|
550
|
-
else:
|
|
551
|
-
kwargs["start_new_session"] = True
|
|
552
|
-
|
|
553
|
-
# v3.4.60: Force OMP_NUM_THREADS=1 in daemon env BEFORE Python imports
|
|
554
|
-
# numpy/torch/lightgbm. Setting it in __init__.py is too late on M5 Pro —
|
|
555
|
-
# by the time superlocalmemory.__init__ runs, libomp has already been
|
|
556
|
-
# initialized by an earlier import, causing the SIGSEGV at
|
|
557
|
-
# __kmp_suspend_initialize_thread when lightgbm forks its worker pool.
|
|
558
|
-
# Forcing serial OpenMP eliminates the parallel barrier race entirely.
|
|
559
|
-
daemon_env = os.environ.copy()
|
|
560
|
-
daemon_env["OMP_NUM_THREADS"] = "1"
|
|
561
|
-
daemon_env["KMP_DUPLICATE_LIB_OK"] = "TRUE"
|
|
562
|
-
bootstrap_descriptor = build_descriptor(
|
|
563
|
-
port=_target_port,
|
|
564
|
-
version=_slm_version,
|
|
565
|
-
pid=os.getpid(),
|
|
566
|
-
state="starting",
|
|
567
|
-
)
|
|
568
|
-
daemon_env["SLM_DAEMON_INSTANCE_ID"] = bootstrap_descriptor.instance_id
|
|
569
|
-
daemon_env["SLM_DAEMON_CAPABILITY"] = bootstrap_descriptor.capability
|
|
570
|
-
kwargs["env"] = daemon_env
|
|
571
|
-
|
|
572
|
-
with open(log_file, "a") as lf:
|
|
573
|
-
proc = subprocess.Popen(cmd, stdout=lf, stderr=lf, **kwargs)
|
|
574
|
-
|
|
575
|
-
# Publish the exact child identity immediately so concurrent callers know
|
|
576
|
-
# this namespace is warming up. If the child won the race and already
|
|
577
|
-
# published the same instance as ready, never overwrite it with starting.
|
|
578
|
-
child_descriptor = replace(
|
|
579
|
-
bootstrap_descriptor,
|
|
580
|
-
pid=proc.pid,
|
|
581
|
-
process_create_time=process_create_time_for(proc.pid),
|
|
582
|
-
process_start_token=process_start_token_for(proc.pid),
|
|
583
|
-
)
|
|
584
|
-
current = read_descriptor()
|
|
585
|
-
if not (
|
|
586
|
-
current is not None
|
|
587
|
-
and current.instance_id == child_descriptor.instance_id
|
|
588
|
-
and current.pid == child_descriptor.pid
|
|
589
|
-
and current.state == "ready"
|
|
590
|
-
):
|
|
591
|
-
write_descriptor(child_descriptor)
|
|
592
|
-
|
|
593
|
-
# One-release compatibility mirrors; never sufficient for ownership.
|
|
594
|
-
_pid_file_path().write_text(str(proc.pid))
|
|
595
|
-
_port_file_path().write_text(str(_target_port))
|
|
596
|
-
|
|
597
|
-
return _wait_for_daemon(timeout=60)
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
def ensure_daemon(*, port: int | None = None) -> bool:
|
|
601
|
-
"""Start daemon if not running. Returns True if daemon is ready.
|
|
602
|
-
|
|
603
|
-
``port`` — when supplied, the daemon is started (or verified) on this port
|
|
604
|
-
instead of the configured default. The dashboard passes its own ``--port``
|
|
605
|
-
here so the bind authority matches the URL shown to the user.
|
|
606
|
-
|
|
607
|
-
v3.4.4 BULLETPROOF:
|
|
608
|
-
1. If PID alive → return True immediately (even if warming up)
|
|
609
|
-
2. File lock prevents two callers from starting concurrent daemons
|
|
610
|
-
3. After starting, waits for PID file (not health check) — fast detection
|
|
611
|
-
4. Cross-platform: macOS + Windows + Linux
|
|
612
|
-
|
|
613
|
-
v3.4.42: Refactored to delegate the actual subprocess start to
|
|
614
|
-
`_start_daemon_subprocess()`. Callers that already hold daemon.lock
|
|
615
|
-
(e.g. `slm restart` Step 3) should call that helper directly to avoid
|
|
616
|
-
the same-process flock self-deadlock that returned a false-negative
|
|
617
|
-
"failed to start" while the daemon was actually starting cleanly.
|
|
618
|
-
"""
|
|
619
|
-
if is_daemon_running():
|
|
620
|
-
return True
|
|
621
|
-
if (
|
|
622
|
-
os.environ.get("SLM_TEST_ISOLATION") == "1"
|
|
623
|
-
and os.environ.get("SLM_TEST_ALLOW_DAEMON_SPAWN") != "1"
|
|
624
|
-
):
|
|
625
|
-
logger.debug(
|
|
626
|
-
"pytest isolation blocked daemon spawn; use an owned daemon fixture",
|
|
627
|
-
)
|
|
628
|
-
return False
|
|
629
|
-
|
|
630
|
-
# File lock — prevent concurrent starts from multiple CLI/MCP calls
|
|
631
|
-
lock_fd = None
|
|
632
|
-
try:
|
|
633
|
-
lock_file = _lock_file_path()
|
|
634
|
-
lock_file.parent.mkdir(parents=True, exist_ok=True)
|
|
635
|
-
lock_fd = open(lock_file, "w")
|
|
636
|
-
|
|
637
|
-
# Cross-platform file locking
|
|
638
|
-
if sys.platform == "win32":
|
|
639
|
-
import msvcrt
|
|
640
|
-
try:
|
|
641
|
-
msvcrt.locking(lock_fd.fileno(), msvcrt.LK_NBLCK, 1)
|
|
642
|
-
except (IOError, OSError):
|
|
643
|
-
# Another process is starting the daemon — just wait for it
|
|
644
|
-
lock_fd.close()
|
|
645
|
-
return _wait_for_daemon(timeout=60)
|
|
646
|
-
else:
|
|
647
|
-
import fcntl
|
|
648
|
-
try:
|
|
649
|
-
fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
650
|
-
except (IOError, OSError):
|
|
651
|
-
lock_fd.close()
|
|
652
|
-
return _wait_for_daemon(timeout=60)
|
|
653
|
-
|
|
654
|
-
# Re-check after acquiring lock (another process may have started it)
|
|
655
|
-
if is_daemon_running():
|
|
656
|
-
return True
|
|
657
|
-
|
|
658
|
-
# v3.6.9 (#36): TCP-level check catches a systemd-started daemon that
|
|
659
|
-
# has bound the port but hasn't written a PID file yet (e.g. different
|
|
660
|
-
# HOME for the service user vs. the SSH user). If the port is already
|
|
661
|
-
# bound, don't start a second daemon — wait for HTTP readiness instead.
|
|
662
|
-
try:
|
|
663
|
-
import socket as _socket
|
|
664
|
-
with _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM) as _s:
|
|
665
|
-
_s.settimeout(1)
|
|
666
|
-
if _s.connect_ex(("127.0.0.1", _DEFAULT_PORT)) == 0:
|
|
667
|
-
return _wait_for_daemon(timeout=30)
|
|
668
|
-
except Exception:
|
|
669
|
-
pass
|
|
670
|
-
|
|
671
|
-
# Start unified daemon in background — delegated to helper so the
|
|
672
|
-
# same logic can be reused by callers that already hold the lock.
|
|
673
|
-
return _start_daemon_subprocess()
|
|
674
|
-
|
|
675
|
-
except Exception as exc:
|
|
676
|
-
# Daemon auto-start is the entry point for dashboard / mesh /
|
|
677
|
-
# health features; failure here silently disables all of them.
|
|
678
|
-
# Log at WARNING so operators can see it in production logs.
|
|
679
|
-
logger.warning("ensure_daemon error: %s (run `slm doctor`)", exc)
|
|
680
|
-
return False
|
|
681
|
-
finally:
|
|
682
|
-
if lock_fd:
|
|
683
|
-
try:
|
|
684
|
-
lock_fd.close()
|
|
685
|
-
except Exception:
|
|
686
|
-
pass
|
|
687
|
-
try:
|
|
688
|
-
_lock_file_path().unlink(missing_ok=True)
|
|
689
|
-
except Exception:
|
|
690
|
-
pass
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
def _wait_for_daemon(timeout: int = 60) -> bool:
|
|
694
|
-
"""Wait for matching owned health; liveness alone is never readiness."""
|
|
695
|
-
for _ in range(timeout * 2): # check every 0.5s
|
|
696
|
-
time.sleep(0.5)
|
|
697
|
-
descriptor = read_descriptor()
|
|
698
|
-
if descriptor is not None:
|
|
699
|
-
if not _descriptor_process_is_alive(descriptor):
|
|
700
|
-
continue
|
|
701
|
-
health = _fetch_health(descriptor.port)
|
|
702
|
-
if health is not None and descriptor_matches_health(descriptor, health):
|
|
703
|
-
return True
|
|
704
|
-
continue
|
|
705
|
-
if descriptor_path().exists():
|
|
706
|
-
continue
|
|
707
|
-
if _verified_legacy_health() is not None:
|
|
708
|
-
return True
|
|
709
|
-
return False
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
_GENERIC_UNAVAILABLE = {
|
|
713
|
-
"reason": "unknown",
|
|
714
|
-
"message": "Owned daemon is unavailable; retry later.",
|
|
715
|
-
"hint": "Run `slm doctor`, then `slm restart` if it stays down.",
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
_LIVENESS_DIAGNOSIS = {
|
|
719
|
-
"process_exited": (
|
|
720
|
-
"daemon_process_exited",
|
|
721
|
-
"the recorded daemon process (pid {pid}) is no longer running",
|
|
722
|
-
"Start it again with `slm start`.",
|
|
723
|
-
),
|
|
724
|
-
"process_zombie": (
|
|
725
|
-
"daemon_process_exited",
|
|
726
|
-
"the recorded daemon process (pid {pid}) has exited and is awaiting "
|
|
727
|
-
"reaping by its parent",
|
|
728
|
-
"Start it again with `slm start`.",
|
|
729
|
-
),
|
|
730
|
-
"process_unreadable": (
|
|
731
|
-
"daemon_process_unreadable",
|
|
732
|
-
"the recorded daemon process (pid {pid}) could not be inspected; it "
|
|
733
|
-
"may belong to another user",
|
|
734
|
-
"Run `slm restart` to publish a fresh descriptor.",
|
|
735
|
-
),
|
|
736
|
-
"start_token_mismatch": (
|
|
737
|
-
"pid_reused_by_another_process",
|
|
738
|
-
"pid {pid} is alive but is a different process than the daemon that "
|
|
739
|
-
"wrote {path}; the daemon exited and its pid was recycled",
|
|
740
|
-
"Run `slm restart` to publish a fresh descriptor.",
|
|
741
|
-
),
|
|
742
|
-
"identity_mismatch": (
|
|
743
|
-
"daemon_identity_mismatch",
|
|
744
|
-
"pid {pid} did not match the process identity recorded in {path} and "
|
|
745
|
-
"the process on port {port} did not prove it owns that identity; the "
|
|
746
|
-
"recorded creation time can also diverge on its own if this machine's "
|
|
747
|
-
"clock is stepped (common under WSL2)",
|
|
748
|
-
"Run `slm restart` to publish a fresh descriptor.",
|
|
749
|
-
),
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
def describe_daemon_unavailability() -> dict[str, str]:
|
|
754
|
-
"""Explain *why* the owned daemon cannot be used, in actionable terms.
|
|
755
|
-
|
|
756
|
-
"Owned daemon is unavailable" is true of a stopped daemon, a recycled PID,
|
|
757
|
-
an unreachable port and an identity mismatch alike, which left issue #104's
|
|
758
|
-
reporter with nothing to act on. This names the specific evidence instead.
|
|
759
|
-
Diagnosis is best-effort and never raises: a broken diagnosis must not
|
|
760
|
-
replace the caller's real error.
|
|
761
|
-
"""
|
|
762
|
-
try:
|
|
763
|
-
return _describe_daemon_unavailability()
|
|
764
|
-
except Exception: # noqa: BLE001 - diagnosis is advisory only
|
|
765
|
-
return dict(_GENERIC_UNAVAILABLE)
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
def _describe_daemon_unavailability() -> dict[str, str]:
|
|
769
|
-
path = descriptor_path()
|
|
770
|
-
descriptor = read_descriptor()
|
|
771
|
-
if descriptor is None:
|
|
772
|
-
if path.exists():
|
|
773
|
-
return {
|
|
774
|
-
"reason": "descriptor_unusable",
|
|
775
|
-
"message": (
|
|
776
|
-
f"{path} is unreadable, malformed, or belongs to another "
|
|
777
|
-
f"data root or user."
|
|
778
|
-
),
|
|
779
|
-
"hint": "Run `slm restart` to publish a fresh descriptor.",
|
|
780
|
-
}
|
|
781
|
-
if _verified_legacy_health() is not None:
|
|
782
|
-
return {
|
|
783
|
-
"reason": "legacy_daemon_request_failed",
|
|
784
|
-
"message": (
|
|
785
|
-
"a pre-descriptor daemon answered health but rejected or "
|
|
786
|
-
"dropped the request."
|
|
787
|
-
),
|
|
788
|
-
"hint": "Run `slm restart` to upgrade it to an owned daemon.",
|
|
789
|
-
}
|
|
790
|
-
return {
|
|
791
|
-
"reason": "no_daemon",
|
|
792
|
-
"message": f"no daemon is registered for this data root ({path} is absent).",
|
|
793
|
-
"hint": "Run `slm start`.",
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
alive, evidence = _resolve_descriptor_liveness(descriptor)
|
|
797
|
-
if not alive:
|
|
798
|
-
reason, template, hint = _LIVENESS_DIAGNOSIS.get(
|
|
799
|
-
evidence,
|
|
800
|
-
(
|
|
801
|
-
"daemon_identity_mismatch",
|
|
802
|
-
"pid {pid} did not match the identity recorded in {path}",
|
|
803
|
-
"Run `slm restart` to publish a fresh descriptor.",
|
|
804
|
-
),
|
|
805
|
-
)
|
|
806
|
-
return {
|
|
807
|
-
"reason": reason,
|
|
808
|
-
"message": template.format(
|
|
809
|
-
pid=descriptor.pid, port=descriptor.port, path=path,
|
|
810
|
-
) + ".",
|
|
811
|
-
"hint": hint,
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
health = _fetch_health(descriptor.port)
|
|
815
|
-
if health is None:
|
|
816
|
-
return {
|
|
817
|
-
"reason": "daemon_unreachable",
|
|
818
|
-
"message": (
|
|
819
|
-
f"the owned daemon (pid {descriptor.pid}) is running but did "
|
|
820
|
-
f"not answer http://127.0.0.1:{descriptor.port}/health within "
|
|
821
|
-
f"2s."
|
|
822
|
-
),
|
|
823
|
-
"hint": (
|
|
824
|
-
"Check `slm logs` for a stalled request, or `slm restart` if "
|
|
825
|
-
"it stays unresponsive."
|
|
826
|
-
),
|
|
827
|
-
}
|
|
828
|
-
if not descriptor_matches_health(descriptor, health):
|
|
829
|
-
return {
|
|
830
|
-
"reason": "port_owned_by_another_daemon",
|
|
831
|
-
"message": (
|
|
832
|
-
f"port {descriptor.port} answered health but with a different "
|
|
833
|
-
f"daemon identity than {path} records."
|
|
834
|
-
),
|
|
835
|
-
"hint": (
|
|
836
|
-
"Another SuperLocalMemory instance holds that port. Stop it, "
|
|
837
|
-
"or set SLM_DAEMON_PORT to a free port."
|
|
838
|
-
),
|
|
839
|
-
}
|
|
840
|
-
return {
|
|
841
|
-
"reason": "request_rejected",
|
|
842
|
-
"message": (
|
|
843
|
-
f"the owned daemon (pid {descriptor.pid}) is healthy but rejected "
|
|
844
|
-
f"or dropped this request."
|
|
845
|
-
),
|
|
846
|
-
"hint": "Check `slm logs` for the failing request.",
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
def stop_daemon() -> bool:
|
|
851
|
-
"""Stop only the daemon proven to belong to this data namespace.
|
|
852
|
-
|
|
853
|
-
Machine-wide process-name scans are forbidden: they can kill another SLM
|
|
854
|
-
installation or a user's live workers during tests. V3.7 uses the owned
|
|
855
|
-
HTTP capability; the daemon itself terminates its child process tree.
|
|
856
|
-
Success means the owned process exited and released its listener, not just
|
|
857
|
-
that the asynchronous stop request was accepted.
|
|
858
|
-
|
|
859
|
-
A busy daemon is not a dead daemon. ``daemon_request()`` normally
|
|
860
|
-
preflights every call with ``GET /health`` before sending it, but that
|
|
861
|
-
preflight needs the daemon's single-threaded event loop to be free to
|
|
862
|
-
answer HTTP. ``/maintenance/run`` and ``/consolidate/cognitive`` run
|
|
863
|
-
multi-second (sometimes multi-minute) synchronous work directly inline in
|
|
864
|
-
their handlers with no thread offload, which blocks *every* request on
|
|
865
|
-
that loop, health included, for as long as they run. Reproduced live: a
|
|
866
|
-
genuine ``/maintenance/run`` call held the loop long enough that 15/15
|
|
867
|
-
health polls during the window timed out at exactly the 2s cap while
|
|
868
|
-
``ps``/``lsof`` proved the process never stopped listening — which is
|
|
869
|
-
exactly the "Daemon was not running" false report this fixes. Process
|
|
870
|
-
liveness (PID + clock-independent start token, proven below via
|
|
871
|
-
``_descriptor_process_is_alive``) is the fact that actually matters for
|
|
872
|
-
"should I try to stop this," so it is checked directly and the mutating
|
|
873
|
-
``/stop`` POST is sent with ``verify_health=False`` once that is proven —
|
|
874
|
-
the daemon still authenticates the request by its capability header on
|
|
875
|
-
arrival, so this loses no ownership guarantee, only the redundant,
|
|
876
|
-
stall-prone preflight round trip.
|
|
877
|
-
"""
|
|
878
|
-
descriptor = read_descriptor()
|
|
879
|
-
legacy = _verified_legacy_health() if descriptor is None else None
|
|
880
|
-
if descriptor is None and legacy is None:
|
|
881
|
-
return False
|
|
882
|
-
if descriptor is not None:
|
|
883
|
-
if not _descriptor_process_is_alive(descriptor):
|
|
884
|
-
return False
|
|
885
|
-
response = daemon_request(
|
|
886
|
-
"POST",
|
|
887
|
-
"/stop",
|
|
888
|
-
expected_descriptor=descriptor,
|
|
889
|
-
verify_health=False,
|
|
890
|
-
)
|
|
891
|
-
else:
|
|
892
|
-
if legacy is None:
|
|
893
|
-
return False
|
|
894
|
-
response = daemon_request(
|
|
895
|
-
"POST",
|
|
896
|
-
"/stop",
|
|
897
|
-
expected_legacy=legacy,
|
|
898
|
-
)
|
|
899
|
-
if not response or response.get("status") != "stopping":
|
|
900
|
-
return False
|
|
901
|
-
if descriptor is not None:
|
|
902
|
-
return wait_for_owned_daemon_shutdown(descriptor)
|
|
903
|
-
if legacy is None:
|
|
904
|
-
return False
|
|
905
|
-
return wait_for_owned_daemon_shutdown(
|
|
906
|
-
None,
|
|
907
|
-
legacy_pid=int(legacy["pid"]),
|
|
908
|
-
legacy_port=int(legacy["_legacy_port"]),
|
|
909
|
-
)
|