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,1052 +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 — Embedding Service (Subprocess-Isolated).
|
|
6
|
-
|
|
7
|
-
All PyTorch/model work runs in a SEPARATE subprocess. The main process
|
|
8
|
-
(dashboard, MCP, CLI) never imports torch and stays at ~60 MB.
|
|
9
|
-
|
|
10
|
-
The worker subprocess has a configurable idle timeout and respawns on the
|
|
11
|
-
next embed call when it has been unloaded.
|
|
12
|
-
|
|
13
|
-
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
from __future__ import annotations
|
|
17
|
-
|
|
18
|
-
import atexit
|
|
19
|
-
import json
|
|
20
|
-
import logging
|
|
21
|
-
import os
|
|
22
|
-
import subprocess
|
|
23
|
-
import sys
|
|
24
|
-
import threading
|
|
25
|
-
import time
|
|
26
|
-
import weakref
|
|
27
|
-
from contextlib import contextmanager
|
|
28
|
-
from pathlib import Path
|
|
29
|
-
from typing import TYPE_CHECKING, Iterator
|
|
30
|
-
|
|
31
|
-
import numpy as np
|
|
32
|
-
|
|
33
|
-
from superlocalmemory.core.config import EmbeddingConfig
|
|
34
|
-
|
|
35
|
-
# Track all live embedding services for atexit cleanup
|
|
36
|
-
_live_embedding_services: set[weakref.ref] = set()
|
|
37
|
-
|
|
38
|
-
if TYPE_CHECKING:
|
|
39
|
-
from numpy.typing import NDArray
|
|
40
|
-
|
|
41
|
-
logger = logging.getLogger(__name__)
|
|
42
|
-
|
|
43
|
-
# Fisher variance constants
|
|
44
|
-
_FISHER_VAR_MIN = 0.05
|
|
45
|
-
_FISHER_VAR_MAX = 2.0
|
|
46
|
-
_FISHER_VAR_RANGE = _FISHER_VAR_MAX - _FISHER_VAR_MIN
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class DimensionMismatchError(RuntimeError):
|
|
50
|
-
"""Raised when the actual embedding dimension differs from config."""
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
# ---------------------------------------------------------------------------
|
|
54
|
-
# V3.3.28: System-wide concurrency guard for embedding workers.
|
|
55
|
-
#
|
|
56
|
-
# The memory blast incident (April 7, 2026) was caused by 20+ concurrent
|
|
57
|
-
# `slm observe` CLI processes each spawning their own embedding_worker
|
|
58
|
-
# subprocess (1.4 GB each). This file lock ensures only MAX_CONCURRENT
|
|
59
|
-
# embedding workers can exist across ALL processes on the machine.
|
|
60
|
-
#
|
|
61
|
-
# Primary defense: daemon routing (cmd_observe → daemon → singleton engine).
|
|
62
|
-
# This lock is the secondary safety net for when the daemon isn't available.
|
|
63
|
-
# ---------------------------------------------------------------------------
|
|
64
|
-
|
|
65
|
-
_MAX_CONCURRENT_WORKERS = int(os.environ.get("SLM_MAX_EMBEDDING_WORKERS", 1))
|
|
66
|
-
_embedding_lock_fd: int | None = None
|
|
67
|
-
_embedding_lock_state_guard = threading.Lock()
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def _embedding_lock_file() -> Path:
|
|
71
|
-
from superlocalmemory.infra.data_root import state_path
|
|
72
|
-
|
|
73
|
-
return state_path(".embedding.lock")
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def _embedding_pid_file() -> Path:
|
|
77
|
-
from superlocalmemory.infra.data_root import state_path
|
|
78
|
-
|
|
79
|
-
return state_path(".embedding-worker.pid")
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def _is_embedding_worker_alive() -> bool:
|
|
83
|
-
"""Check if an embedding worker PID file exists and that PID is alive.
|
|
84
|
-
|
|
85
|
-
v3.4.13: Machine-wide singleton guard. Before spawning a new worker,
|
|
86
|
-
check if one is already running. Prevents duplicate 1.6GB workers.
|
|
87
|
-
"""
|
|
88
|
-
try:
|
|
89
|
-
pid_file = _embedding_pid_file()
|
|
90
|
-
if not pid_file.exists():
|
|
91
|
-
return False
|
|
92
|
-
pid = int(pid_file.read_text().strip())
|
|
93
|
-
os.kill(pid, 0) # Signal 0 = check if alive
|
|
94
|
-
return True
|
|
95
|
-
except (ValueError, OSError, ProcessLookupError):
|
|
96
|
-
# PID file invalid or process dead — clean up stale file
|
|
97
|
-
_embedding_pid_file().unlink(missing_ok=True)
|
|
98
|
-
return False
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def register_embedding_worker_pid(pid: int) -> None:
|
|
102
|
-
"""Write the embedding worker PID to the machine-wide PID file."""
|
|
103
|
-
pid_file = _embedding_pid_file()
|
|
104
|
-
pid_file.parent.mkdir(parents=True, exist_ok=True)
|
|
105
|
-
pid_file.write_text(str(pid))
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def acquire_embedding_lock(timeout: float = 5.0) -> bool:
|
|
109
|
-
"""Acquire system-wide embedding worker lock.
|
|
110
|
-
|
|
111
|
-
The caller must re-check the PID file after acquisition before spawning.
|
|
112
|
-
POSIX uses flock; Windows uses a one-byte msvcrt lock.
|
|
113
|
-
Returns True if lock acquired (safe to spawn), False if another worker active.
|
|
114
|
-
"""
|
|
115
|
-
global _embedding_lock_fd
|
|
116
|
-
|
|
117
|
-
# Serialize local contenders as well as cross-process contenders. The file
|
|
118
|
-
# descriptor stays local until its OS lock succeeds, so a failed acquire
|
|
119
|
-
# can never overwrite and leak the descriptor that owns the live worker.
|
|
120
|
-
with _embedding_lock_state_guard:
|
|
121
|
-
if _embedding_lock_fd is not None:
|
|
122
|
-
return False
|
|
123
|
-
if _is_embedding_worker_alive():
|
|
124
|
-
return False
|
|
125
|
-
|
|
126
|
-
lock_file = _embedding_lock_file()
|
|
127
|
-
lock_file.parent.mkdir(parents=True, exist_ok=True)
|
|
128
|
-
candidate_fd: int | None = None
|
|
129
|
-
try:
|
|
130
|
-
candidate_fd = os.open(str(lock_file), os.O_CREAT | os.O_RDWR)
|
|
131
|
-
deadline = time.time() + timeout
|
|
132
|
-
while time.time() < deadline:
|
|
133
|
-
try:
|
|
134
|
-
if sys.platform == "win32":
|
|
135
|
-
import msvcrt
|
|
136
|
-
|
|
137
|
-
if os.fstat(candidate_fd).st_size == 0:
|
|
138
|
-
os.write(candidate_fd, b"\0")
|
|
139
|
-
os.lseek(candidate_fd, 0, os.SEEK_SET)
|
|
140
|
-
msvcrt.locking(candidate_fd, msvcrt.LK_NBLCK, 1)
|
|
141
|
-
else:
|
|
142
|
-
import fcntl
|
|
143
|
-
|
|
144
|
-
fcntl.flock(
|
|
145
|
-
candidate_fd,
|
|
146
|
-
fcntl.LOCK_EX | fcntl.LOCK_NB,
|
|
147
|
-
)
|
|
148
|
-
_embedding_lock_fd = candidate_fd
|
|
149
|
-
return True
|
|
150
|
-
except (BlockingIOError, OSError):
|
|
151
|
-
time.sleep(0.2)
|
|
152
|
-
os.close(candidate_fd)
|
|
153
|
-
return False
|
|
154
|
-
except Exception:
|
|
155
|
-
if candidate_fd is not None:
|
|
156
|
-
try:
|
|
157
|
-
os.close(candidate_fd)
|
|
158
|
-
except OSError:
|
|
159
|
-
pass
|
|
160
|
-
return False
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
def release_embedding_lock() -> None:
|
|
164
|
-
"""Release system-wide embedding worker lock."""
|
|
165
|
-
global _embedding_lock_fd
|
|
166
|
-
with _embedding_lock_state_guard:
|
|
167
|
-
if _embedding_lock_fd is None:
|
|
168
|
-
return
|
|
169
|
-
try:
|
|
170
|
-
if sys.platform == "win32":
|
|
171
|
-
import msvcrt
|
|
172
|
-
|
|
173
|
-
os.lseek(_embedding_lock_fd, 0, os.SEEK_SET)
|
|
174
|
-
msvcrt.locking(_embedding_lock_fd, msvcrt.LK_UNLCK, 1)
|
|
175
|
-
else:
|
|
176
|
-
import fcntl
|
|
177
|
-
|
|
178
|
-
fcntl.flock(_embedding_lock_fd, fcntl.LOCK_UN)
|
|
179
|
-
os.close(_embedding_lock_fd)
|
|
180
|
-
except Exception:
|
|
181
|
-
try:
|
|
182
|
-
os.close(_embedding_lock_fd)
|
|
183
|
-
except OSError:
|
|
184
|
-
pass
|
|
185
|
-
_embedding_lock_fd = None
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
_IDLE_TIMEOUT_SECONDS = 1800 # 30 minutes — keep interactive sessions warm.
|
|
189
|
-
# V3.8.1: existing-user soaks showed that the five-minute policy repeatedly
|
|
190
|
-
# recycled a ~1.1 GB local model and imposed 20-30 second cold starts. The
|
|
191
|
-
# explicit environment override remains available for low-RAM installations.
|
|
192
|
-
_IDLE_TIMEOUT_SECONDS = int(os.environ.get("SLM_EMBED_IDLE_TIMEOUT", _IDLE_TIMEOUT_SECONDS))
|
|
193
|
-
# V3.3.21: Configurable response timeout — 180s default, but batch ingestion
|
|
194
|
-
# (2-turn chunks across 10 conversations) needs 600s+ to survive cold-start
|
|
195
|
-
# model downloads and ARM64 ONNX compilation pauses.
|
|
196
|
-
_SUBPROCESS_RESPONSE_TIMEOUT = int(os.environ.get("SLM_EMBED_RESPONSE_TIMEOUT", 180))
|
|
197
|
-
# V3.3.21: Increase recycle threshold to 5000 (was 1000). With 2-turn chunks,
|
|
198
|
-
# a single conversation produces ~50-80 store calls. 10 conversations = 500-800.
|
|
199
|
-
# Recycling at 1000 caused mid-ingestion worker death → timeout cascade.
|
|
200
|
-
_WORKER_RECYCLE_AFTER = int(os.environ.get("SLM_EMBED_RECYCLE_AFTER", 5000))
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
class EmbeddingService:
|
|
204
|
-
"""Subprocess-isolated embedding service.
|
|
205
|
-
|
|
206
|
-
All model inference runs in a child process. The main process never
|
|
207
|
-
imports torch/sentence-transformers, keeping its memory at ~60 MB.
|
|
208
|
-
|
|
209
|
-
The worker auto-kills after 2 min idle. First embed after idle takes
|
|
210
|
-
~3 sec (model reload). Subsequent embeds are instant (<100ms).
|
|
211
|
-
"""
|
|
212
|
-
|
|
213
|
-
def __init__(self, config: EmbeddingConfig) -> None:
|
|
214
|
-
self._config = config
|
|
215
|
-
self._lock = threading.Lock()
|
|
216
|
-
self._worker_proc: subprocess.Popen | None = None
|
|
217
|
-
self._available = True
|
|
218
|
-
self._last_used: float = 0.0
|
|
219
|
-
self._idle_timer: threading.Timer | None = None
|
|
220
|
-
self._worker_ready = False
|
|
221
|
-
self._owns_worker_lock = False
|
|
222
|
-
self._request_count: int = 0
|
|
223
|
-
self._http_client: object | None = None
|
|
224
|
-
self._remote_ready = False
|
|
225
|
-
|
|
226
|
-
# Register for atexit cleanup (prevent orphaned workers)
|
|
227
|
-
ref = weakref.ref(self, _live_embedding_services.discard)
|
|
228
|
-
_live_embedding_services.add(ref)
|
|
229
|
-
|
|
230
|
-
def __del__(self) -> None:
|
|
231
|
-
"""Kill worker subprocess when service is garbage-collected."""
|
|
232
|
-
try:
|
|
233
|
-
self._kill_worker()
|
|
234
|
-
except Exception:
|
|
235
|
-
pass
|
|
236
|
-
try:
|
|
237
|
-
if self._http_client is not None:
|
|
238
|
-
self._http_client.close()
|
|
239
|
-
except Exception:
|
|
240
|
-
pass
|
|
241
|
-
|
|
242
|
-
@property
|
|
243
|
-
def is_available(self) -> bool:
|
|
244
|
-
"""Check if embedding service can produce embeddings."""
|
|
245
|
-
if self._config.is_openai_compatible:
|
|
246
|
-
return bool(self._config.api_endpoint)
|
|
247
|
-
if self._config.is_cloud:
|
|
248
|
-
return bool(self._config.api_endpoint and self._config.api_key)
|
|
249
|
-
return self._available
|
|
250
|
-
|
|
251
|
-
@property
|
|
252
|
-
def is_warm(self) -> bool:
|
|
253
|
-
"""Return whether the configured backend has served a request.
|
|
254
|
-
|
|
255
|
-
``is_available`` only means that the backend may be started. A local
|
|
256
|
-
sentence-transformers cold start can take minutes on Apple Silicon, so
|
|
257
|
-
background enrichment must not mistake availability for readiness and
|
|
258
|
-
occupy the only worker ahead of an interactive recall.
|
|
259
|
-
"""
|
|
260
|
-
config = getattr(self, "_config", None)
|
|
261
|
-
if config is not None and (
|
|
262
|
-
config.is_openai_compatible or config.is_cloud
|
|
263
|
-
):
|
|
264
|
-
return bool(
|
|
265
|
-
getattr(self, "_remote_ready", False)
|
|
266
|
-
and self.is_available
|
|
267
|
-
)
|
|
268
|
-
proc = getattr(self, "_worker_proc", None)
|
|
269
|
-
if proc is None or getattr(self, "_request_count", 0) <= 0:
|
|
270
|
-
return False
|
|
271
|
-
try:
|
|
272
|
-
return proc.poll() is None
|
|
273
|
-
except Exception:
|
|
274
|
-
return False
|
|
275
|
-
|
|
276
|
-
@property
|
|
277
|
-
def dimension(self) -> int:
|
|
278
|
-
return self._config.dimension
|
|
279
|
-
|
|
280
|
-
def unload(self, timeout: float = 1.0) -> bool:
|
|
281
|
-
"""Release the worker without blocking daemon shutdown on an embed call.
|
|
282
|
-
|
|
283
|
-
An in-flight request owns ``_lock`` while it waits for the worker's
|
|
284
|
-
response. Shutdown must not wait behind a wedged response: callers
|
|
285
|
-
can continue teardown and the worker process will be handled by the
|
|
286
|
-
process supervisor if necessary.
|
|
287
|
-
"""
|
|
288
|
-
if not self._lock.acquire(timeout=max(0.0, timeout)):
|
|
289
|
-
logger.warning("EmbeddingService: unload skipped; embed worker is busy")
|
|
290
|
-
return False
|
|
291
|
-
try:
|
|
292
|
-
self._kill_worker()
|
|
293
|
-
logger.info("EmbeddingService: worker killed (idle timeout)")
|
|
294
|
-
return True
|
|
295
|
-
finally:
|
|
296
|
-
self._lock.release()
|
|
297
|
-
|
|
298
|
-
def shutdown(self, timeout: float = 1.0) -> None:
|
|
299
|
-
"""Force bounded process teardown even when an embed call owns the lock.
|
|
300
|
-
|
|
301
|
-
Shutdown is stronger than the idle-time ``unload`` operation. Once
|
|
302
|
-
the engine is closing, no new request may use this service, so it is
|
|
303
|
-
safe to detach and terminate a wedged child without waiting behind the
|
|
304
|
-
request lock.
|
|
305
|
-
"""
|
|
306
|
-
acquired = self._lock.acquire(timeout=max(0.0, timeout))
|
|
307
|
-
try:
|
|
308
|
-
self._kill_worker(timeout=min(max(0.0, timeout), 1.0))
|
|
309
|
-
finally:
|
|
310
|
-
if acquired:
|
|
311
|
-
self._lock.release()
|
|
312
|
-
|
|
313
|
-
# ------------------------------------------------------------------
|
|
314
|
-
# Public API
|
|
315
|
-
# ------------------------------------------------------------------
|
|
316
|
-
|
|
317
|
-
@contextmanager
|
|
318
|
-
def _request_lock(self) -> Iterator[None]:
|
|
319
|
-
"""Admit one model request without starving an interactive recall.
|
|
320
|
-
|
|
321
|
-
A background caller may pass the recall gate and then queue behind an
|
|
322
|
-
in-flight model request. If a recall arrives while it is queued, a
|
|
323
|
-
plain mutex can let that background caller retake the worker first.
|
|
324
|
-
Re-check the gate after acquiring the mutex so at most the already
|
|
325
|
-
running background request can delay a newly arrived recall.
|
|
326
|
-
"""
|
|
327
|
-
from superlocalmemory.core.recall_gate import (
|
|
328
|
-
in_flight,
|
|
329
|
-
is_background_work,
|
|
330
|
-
wait_for_foreground_idle,
|
|
331
|
-
)
|
|
332
|
-
|
|
333
|
-
if not is_background_work():
|
|
334
|
-
with self._lock:
|
|
335
|
-
yield
|
|
336
|
-
return
|
|
337
|
-
|
|
338
|
-
while True:
|
|
339
|
-
wait_for_foreground_idle()
|
|
340
|
-
self._lock.acquire()
|
|
341
|
-
if in_flight() == 0:
|
|
342
|
-
break
|
|
343
|
-
self._lock.release()
|
|
344
|
-
try:
|
|
345
|
-
yield
|
|
346
|
-
finally:
|
|
347
|
-
self._lock.release()
|
|
348
|
-
|
|
349
|
-
def embed(self, text: str) -> list[float] | None:
|
|
350
|
-
"""Embed a single text string. Returns list of floats or None."""
|
|
351
|
-
if not text or not text.strip():
|
|
352
|
-
raise ValueError("Cannot embed empty text")
|
|
353
|
-
from superlocalmemory.core.recall_gate import wait_for_foreground_idle
|
|
354
|
-
wait_for_foreground_idle()
|
|
355
|
-
if self._config.is_openai_compatible:
|
|
356
|
-
try:
|
|
357
|
-
vecs = self._openai_compatible_embed_batch([text])
|
|
358
|
-
vec = vecs[0]
|
|
359
|
-
self._validate_dimension(np.asarray(vec))
|
|
360
|
-
self._remote_ready = True
|
|
361
|
-
return vec
|
|
362
|
-
except Exception:
|
|
363
|
-
self._remote_ready = False
|
|
364
|
-
raise
|
|
365
|
-
if self._config.is_cloud:
|
|
366
|
-
try:
|
|
367
|
-
vec = self._cloud_embed_single(text)
|
|
368
|
-
self._validate_dimension(np.asarray(vec))
|
|
369
|
-
self._remote_ready = True
|
|
370
|
-
return vec
|
|
371
|
-
except Exception:
|
|
372
|
-
self._remote_ready = False
|
|
373
|
-
raise
|
|
374
|
-
result = self._subprocess_embed([text])
|
|
375
|
-
if result is None:
|
|
376
|
-
return None
|
|
377
|
-
vec = result[0]
|
|
378
|
-
self._validate_dimension(np.asarray(vec))
|
|
379
|
-
return vec
|
|
380
|
-
|
|
381
|
-
def embed_batch(self, texts: list[str]) -> list[list[float] | None]:
|
|
382
|
-
"""Embed a batch of texts."""
|
|
383
|
-
if not texts:
|
|
384
|
-
raise ValueError("Cannot embed empty batch")
|
|
385
|
-
from superlocalmemory.core.recall_gate import is_background_work
|
|
386
|
-
if is_background_work():
|
|
387
|
-
# A single large background batch can own the only local inference
|
|
388
|
-
# worker for tens of seconds. Slice it so a recall arriving after
|
|
389
|
-
# this call started gets priority before the next text.
|
|
390
|
-
return [self.embed(text) for text in texts]
|
|
391
|
-
if self._config.is_openai_compatible:
|
|
392
|
-
try:
|
|
393
|
-
results = self._openai_compatible_embed_batch(texts)
|
|
394
|
-
for vec in results:
|
|
395
|
-
if vec is not None:
|
|
396
|
-
self._validate_dimension(np.asarray(vec))
|
|
397
|
-
self._remote_ready = any(vec is not None for vec in results)
|
|
398
|
-
return results
|
|
399
|
-
except Exception:
|
|
400
|
-
self._remote_ready = False
|
|
401
|
-
raise
|
|
402
|
-
if self._config.is_cloud:
|
|
403
|
-
try:
|
|
404
|
-
results = self._cloud_embed_batch(texts)
|
|
405
|
-
for vec in results:
|
|
406
|
-
if vec is not None:
|
|
407
|
-
self._validate_dimension(np.asarray(vec))
|
|
408
|
-
self._remote_ready = any(vec is not None for vec in results)
|
|
409
|
-
return results
|
|
410
|
-
except Exception:
|
|
411
|
-
self._remote_ready = False
|
|
412
|
-
raise
|
|
413
|
-
result = self._subprocess_embed(texts)
|
|
414
|
-
if result is None:
|
|
415
|
-
return [None] * len(texts)
|
|
416
|
-
for vec in result:
|
|
417
|
-
if vec is not None:
|
|
418
|
-
self._validate_dimension(np.asarray(vec))
|
|
419
|
-
return result
|
|
420
|
-
|
|
421
|
-
def compute_fisher_params(
|
|
422
|
-
self, embedding: list[float],
|
|
423
|
-
) -> tuple[list[float], list[float]]:
|
|
424
|
-
"""Compute Fisher-Rao parameters from a raw embedding."""
|
|
425
|
-
arr = np.asarray(embedding, dtype=np.float64)
|
|
426
|
-
norm = float(np.linalg.norm(arr))
|
|
427
|
-
if norm < 1e-10:
|
|
428
|
-
mean = np.zeros(len(arr), dtype=np.float64)
|
|
429
|
-
variance = np.full(len(arr), _FISHER_VAR_MAX, dtype=np.float64)
|
|
430
|
-
return mean.tolist(), variance.tolist()
|
|
431
|
-
mean = arr / norm
|
|
432
|
-
abs_mean = np.abs(mean)
|
|
433
|
-
max_val = float(np.max(abs_mean)) + 1e-10
|
|
434
|
-
signal_strength = abs_mean / max_val
|
|
435
|
-
variance = _FISHER_VAR_MAX - _FISHER_VAR_RANGE * signal_strength
|
|
436
|
-
variance = np.clip(variance, _FISHER_VAR_MIN, _FISHER_VAR_MAX)
|
|
437
|
-
return mean.tolist(), variance.tolist()
|
|
438
|
-
|
|
439
|
-
# ------------------------------------------------------------------
|
|
440
|
-
# Subprocess worker management
|
|
441
|
-
# ------------------------------------------------------------------
|
|
442
|
-
|
|
443
|
-
def _subprocess_embed(self, texts: list[str]) -> list[list[float]] | None:
|
|
444
|
-
"""Send texts to worker subprocess, get embeddings back.
|
|
445
|
-
|
|
446
|
-
Includes a timeout (_SUBPROCESS_RESPONSE_TIMEOUT seconds) so the CLI
|
|
447
|
-
never hangs indefinitely on cold model loads or network issues.
|
|
448
|
-
"""
|
|
449
|
-
with self._request_lock():
|
|
450
|
-
# Only an explicit terminal disable (``False``) short-circuits. A
|
|
451
|
-
# ``None`` availability is the recall-health self-heal's "re-probe"
|
|
452
|
-
# signal (recall_health._heal_embedder) — it must fall through and
|
|
453
|
-
# respawn the worker, matching OllamaEmbedder's tri-state
|
|
454
|
-
# convention. Using ``not self._available`` here bricked the local
|
|
455
|
-
# worker on the first heal tick, because ``None`` is falsy.
|
|
456
|
-
if self._available is False:
|
|
457
|
-
return None
|
|
458
|
-
# Worker recycling: restart after N requests to prevent
|
|
459
|
-
# C++ allocator fragmentation over long-running sessions.
|
|
460
|
-
if self._request_count >= _WORKER_RECYCLE_AFTER and self._worker_proc is not None:
|
|
461
|
-
logger.info("Recycling embedding worker after %d requests", self._request_count)
|
|
462
|
-
self._kill_worker()
|
|
463
|
-
self._request_count = 0
|
|
464
|
-
|
|
465
|
-
self._ensure_worker()
|
|
466
|
-
if self._worker_proc is None:
|
|
467
|
-
return None
|
|
468
|
-
|
|
469
|
-
req = json.dumps({
|
|
470
|
-
"cmd": "embed",
|
|
471
|
-
"texts": texts,
|
|
472
|
-
"model_name": self._config.model_name,
|
|
473
|
-
"dimension": self._config.dimension,
|
|
474
|
-
}) + "\n"
|
|
475
|
-
|
|
476
|
-
try:
|
|
477
|
-
self._worker_proc.stdin.write(req)
|
|
478
|
-
self._worker_proc.stdin.flush()
|
|
479
|
-
resp_line = self._readline_with_timeout(
|
|
480
|
-
self._worker_proc.stdout,
|
|
481
|
-
_SUBPROCESS_RESPONSE_TIMEOUT,
|
|
482
|
-
)
|
|
483
|
-
if not resp_line:
|
|
484
|
-
logger.warning(
|
|
485
|
-
"Embedding worker timed out after %ds. "
|
|
486
|
-
"Run 'slm setup' to download models and verify installation.",
|
|
487
|
-
_SUBPROCESS_RESPONSE_TIMEOUT,
|
|
488
|
-
)
|
|
489
|
-
# Print to stderr so CLI users see this even without logging
|
|
490
|
-
print(
|
|
491
|
-
f"\n⚠ Embedding worker did not respond within "
|
|
492
|
-
f"{_SUBPROCESS_RESPONSE_TIMEOUT}s.\n"
|
|
493
|
-
f" Run: slm setup (download models + verify)\n"
|
|
494
|
-
f" Run: slm doctor (diagnose issues)\n",
|
|
495
|
-
file=sys.stderr,
|
|
496
|
-
)
|
|
497
|
-
self._kill_worker()
|
|
498
|
-
return None
|
|
499
|
-
resp = json.loads(resp_line)
|
|
500
|
-
if not resp.get("ok"):
|
|
501
|
-
logger.warning("Worker error: %s", resp.get("error"))
|
|
502
|
-
# A well-formed worker error is a terminal local
|
|
503
|
-
# dependency/model failure, not a transient pipe race.
|
|
504
|
-
# Disable this service and terminate the child so every
|
|
505
|
-
# recall does not respawn a heavyweight failing process.
|
|
506
|
-
self._available = False
|
|
507
|
-
self._kill_worker()
|
|
508
|
-
return None
|
|
509
|
-
# A successful embed proves the worker is healthy, so clear any
|
|
510
|
-
# transient/``None`` availability left by a self-heal re-probe
|
|
511
|
-
# back to a definite ``True``. Without this the flag lingers at
|
|
512
|
-
# ``None`` and the next ``not``-style check elsewhere re-blocks.
|
|
513
|
-
self._available = True
|
|
514
|
-
self._reset_idle_timer()
|
|
515
|
-
self._request_count += 1
|
|
516
|
-
return resp["vectors"]
|
|
517
|
-
except (BrokenPipeError, OSError, json.JSONDecodeError) as exc:
|
|
518
|
-
logger.warning(
|
|
519
|
-
"Embedding worker communication failed: %s — respawning.",
|
|
520
|
-
exc,
|
|
521
|
-
)
|
|
522
|
-
self._kill_worker()
|
|
523
|
-
# V3.3.16: Auto-retry once after worker death (RSS watchdog
|
|
524
|
-
# or crash). Respawn + re-send instead of returning None.
|
|
525
|
-
try:
|
|
526
|
-
self._ensure_worker()
|
|
527
|
-
if self._worker_proc is not None:
|
|
528
|
-
self._worker_proc.stdin.write(req)
|
|
529
|
-
self._worker_proc.stdin.flush()
|
|
530
|
-
resp_line = self._readline_with_timeout(
|
|
531
|
-
self._worker_proc.stdout,
|
|
532
|
-
_SUBPROCESS_RESPONSE_TIMEOUT,
|
|
533
|
-
)
|
|
534
|
-
if resp_line:
|
|
535
|
-
resp = json.loads(resp_line)
|
|
536
|
-
if resp.get("ok"):
|
|
537
|
-
self._reset_idle_timer()
|
|
538
|
-
self._request_count = 1
|
|
539
|
-
return resp["vectors"]
|
|
540
|
-
except Exception:
|
|
541
|
-
self._kill_worker()
|
|
542
|
-
return None
|
|
543
|
-
|
|
544
|
-
@staticmethod
|
|
545
|
-
def _readline_with_timeout(stream, timeout_seconds: float) -> str:
|
|
546
|
-
"""Read a line from stream with a timeout. Returns '' on timeout.
|
|
547
|
-
|
|
548
|
-
Prefer a deadline-driven selector poll of the stream's file descriptor
|
|
549
|
-
(POSIX pipes). That path never spawns a helper thread, so a hung
|
|
550
|
-
embedding worker cannot leak reader threads or pin the pipe FD across
|
|
551
|
-
timeouts. A thread fallback remains only for streams without a usable
|
|
552
|
-
fileno (unit-test mocks) and for Windows, where selectors cannot wait
|
|
553
|
-
on pipes.
|
|
554
|
-
"""
|
|
555
|
-
import selectors
|
|
556
|
-
|
|
557
|
-
timeout_seconds = max(0.0, float(timeout_seconds))
|
|
558
|
-
fd: int | None
|
|
559
|
-
try:
|
|
560
|
-
raw_fd = stream.fileno()
|
|
561
|
-
fd = raw_fd if isinstance(raw_fd, int) else None
|
|
562
|
-
except (AttributeError, OSError, ValueError, TypeError):
|
|
563
|
-
fd = None
|
|
564
|
-
|
|
565
|
-
# Windows select()/selectors only accept sockets, not subprocess pipes.
|
|
566
|
-
if fd is not None and sys.platform != "win32":
|
|
567
|
-
try:
|
|
568
|
-
with selectors.DefaultSelector() as sel:
|
|
569
|
-
sel.register(fd, selectors.EVENT_READ)
|
|
570
|
-
events = sel.select(timeout=timeout_seconds)
|
|
571
|
-
if not events:
|
|
572
|
-
logger.warning(
|
|
573
|
-
"Embedding worker did not respond within %ds",
|
|
574
|
-
timeout_seconds,
|
|
575
|
-
)
|
|
576
|
-
return ""
|
|
577
|
-
# Readable or EOF. Protocol is one JSON line per response;
|
|
578
|
-
# the worker writes a complete line before we are woken.
|
|
579
|
-
line = stream.readline()
|
|
580
|
-
return line if line else ""
|
|
581
|
-
except (OSError, ValueError) as exc:
|
|
582
|
-
# Closed/invalid FD mid-wait — same as empty to the caller
|
|
583
|
-
# (which kills and may respawn the worker).
|
|
584
|
-
logger.debug("Embedding readline selector failed: %s", exc)
|
|
585
|
-
return ""
|
|
586
|
-
|
|
587
|
-
result_container: list[str] = []
|
|
588
|
-
error_container: list[Exception] = []
|
|
589
|
-
|
|
590
|
-
def _read() -> None:
|
|
591
|
-
try:
|
|
592
|
-
result_container.append(stream.readline())
|
|
593
|
-
except Exception as exc:
|
|
594
|
-
error_container.append(exc)
|
|
595
|
-
|
|
596
|
-
# Name contains ``_read`` so leak detectors can find abandoned readers.
|
|
597
|
-
reader = threading.Thread(
|
|
598
|
-
target=_read, daemon=True, name="slm_embed_readline_read",
|
|
599
|
-
)
|
|
600
|
-
reader.start()
|
|
601
|
-
reader.join(timeout=timeout_seconds)
|
|
602
|
-
|
|
603
|
-
if reader.is_alive():
|
|
604
|
-
logger.warning(
|
|
605
|
-
"Embedding worker did not respond within %ds", timeout_seconds,
|
|
606
|
-
)
|
|
607
|
-
# Close/shutdown the stream so the blocked readline() returns and
|
|
608
|
-
# the reader thread can exit. Raising alone would leak the thread
|
|
609
|
-
# (and its FD) on Windows pipes and fileno-less mocks.
|
|
610
|
-
for closer_name in ("close", "shutdown"):
|
|
611
|
-
closer = getattr(stream, closer_name, None)
|
|
612
|
-
if not callable(closer):
|
|
613
|
-
continue
|
|
614
|
-
try:
|
|
615
|
-
if closer_name == "shutdown":
|
|
616
|
-
try:
|
|
617
|
-
closer(True) # type: ignore[misc]
|
|
618
|
-
except TypeError:
|
|
619
|
-
closer()
|
|
620
|
-
else:
|
|
621
|
-
closer()
|
|
622
|
-
except Exception:
|
|
623
|
-
pass
|
|
624
|
-
# Bound the join so a stuck closer cannot hang the caller forever.
|
|
625
|
-
reader.join(timeout=min(1.0, max(0.05, timeout_seconds)))
|
|
626
|
-
return ""
|
|
627
|
-
if error_container:
|
|
628
|
-
raise error_container[0]
|
|
629
|
-
return result_container[0] if result_container else ""
|
|
630
|
-
|
|
631
|
-
@staticmethod
|
|
632
|
-
def _check_memory_pressure() -> bool:
|
|
633
|
-
"""Check if system has enough memory to spawn a worker.
|
|
634
|
-
|
|
635
|
-
V3.3.28: Prevents spawning embedding workers (1.4 GB each) when
|
|
636
|
-
the system is already under memory pressure. Returns True if safe.
|
|
637
|
-
"""
|
|
638
|
-
min_available_gb = float(os.environ.get("SLM_MIN_AVAILABLE_MEMORY_GB", "2.0"))
|
|
639
|
-
try:
|
|
640
|
-
if sys.platform == "darwin":
|
|
641
|
-
# macOS: use vm_stat to get free + inactive pages
|
|
642
|
-
import subprocess as _sp
|
|
643
|
-
result = _sp.run(["vm_stat"], capture_output=True, text=True, timeout=5)
|
|
644
|
-
if result.returncode == 0:
|
|
645
|
-
lines = result.stdout.split("\n")
|
|
646
|
-
page_size = 16384 # default on Apple Silicon
|
|
647
|
-
free_pages = 0
|
|
648
|
-
for line in lines:
|
|
649
|
-
if "page size of" in line:
|
|
650
|
-
try:
|
|
651
|
-
page_size = int(line.split()[-2])
|
|
652
|
-
except (ValueError, IndexError):
|
|
653
|
-
pass
|
|
654
|
-
if "Pages free" in line or "Pages inactive" in line:
|
|
655
|
-
try:
|
|
656
|
-
free_pages += int(line.split()[-1].rstrip("."))
|
|
657
|
-
except (ValueError, IndexError):
|
|
658
|
-
pass
|
|
659
|
-
available_gb = (free_pages * page_size) / (1024 ** 3)
|
|
660
|
-
if available_gb < min_available_gb:
|
|
661
|
-
logger.warning(
|
|
662
|
-
"Low memory (%.1f GB available, need %.1f GB) — "
|
|
663
|
-
"deferring embedding worker spawn",
|
|
664
|
-
available_gb, min_available_gb,
|
|
665
|
-
)
|
|
666
|
-
return False
|
|
667
|
-
else:
|
|
668
|
-
# Linux/other: use /proc/meminfo or psutil
|
|
669
|
-
try:
|
|
670
|
-
with open("/proc/meminfo") as f:
|
|
671
|
-
for line in f:
|
|
672
|
-
if line.startswith("MemAvailable:"):
|
|
673
|
-
available_kb = int(line.split()[1])
|
|
674
|
-
available_gb = available_kb / (1024 * 1024)
|
|
675
|
-
if available_gb < min_available_gb:
|
|
676
|
-
logger.warning(
|
|
677
|
-
"Low memory (%.1f GB available) — "
|
|
678
|
-
"deferring embedding worker spawn",
|
|
679
|
-
available_gb,
|
|
680
|
-
)
|
|
681
|
-
return False
|
|
682
|
-
break
|
|
683
|
-
except FileNotFoundError:
|
|
684
|
-
pass # Not Linux, allow through
|
|
685
|
-
except Exception:
|
|
686
|
-
pass # On error, allow through (don't block functionality)
|
|
687
|
-
return True
|
|
688
|
-
|
|
689
|
-
def _ensure_worker(self) -> None:
|
|
690
|
-
"""Spawn worker subprocess if not running.
|
|
691
|
-
|
|
692
|
-
v3.4.13: Machine-wide singleton — checks PID file before spawning.
|
|
693
|
-
Only ONE embedding_worker can exist at a time on the machine.
|
|
694
|
-
"""
|
|
695
|
-
if self._worker_proc is not None:
|
|
696
|
-
if self._worker_proc.poll() is None:
|
|
697
|
-
return
|
|
698
|
-
# An unexpectedly exited child still leaves this service holding
|
|
699
|
-
# its lifetime flock. Fully close the dead process and release that
|
|
700
|
-
# lock before attempting the normal acquire/spawn sequence. Merely
|
|
701
|
-
# dropping the Popen reference makes the process deadlock against
|
|
702
|
-
# its own old flock until the acquire timeout expires.
|
|
703
|
-
self._kill_worker()
|
|
704
|
-
|
|
705
|
-
# Serialize the check/spawn/register sequence across processes. Checking
|
|
706
|
-
# the PID file without this lock allows two cold callers to both see no
|
|
707
|
-
# worker and launch memory-heavy children.
|
|
708
|
-
if not acquire_embedding_lock():
|
|
709
|
-
logger.debug("Embedding worker owned by another process")
|
|
710
|
-
self._available = False
|
|
711
|
-
return
|
|
712
|
-
if _is_embedding_worker_alive():
|
|
713
|
-
release_embedding_lock()
|
|
714
|
-
logger.debug("Embedding worker already alive after lock acquisition")
|
|
715
|
-
self._available = False
|
|
716
|
-
return
|
|
717
|
-
|
|
718
|
-
# V3.3.28: Check memory pressure before spawning
|
|
719
|
-
if not self._check_memory_pressure():
|
|
720
|
-
release_embedding_lock()
|
|
721
|
-
logger.warning("Skipping embedding worker spawn due to memory pressure")
|
|
722
|
-
self._available = False
|
|
723
|
-
return
|
|
724
|
-
|
|
725
|
-
worker_module = "superlocalmemory.core.embedding_worker"
|
|
726
|
-
try:
|
|
727
|
-
env = {
|
|
728
|
-
**os.environ,
|
|
729
|
-
"CUDA_VISIBLE_DEVICES": "",
|
|
730
|
-
"PYTORCH_MPS_HIGH_WATERMARK_RATIO": "0.0",
|
|
731
|
-
"PYTORCH_MPS_MEM_LIMIT": "0",
|
|
732
|
-
"PYTORCH_ENABLE_MPS_FALLBACK": "1",
|
|
733
|
-
"TOKENIZERS_PARALLELISM": "false",
|
|
734
|
-
"TORCH_DEVICE": "cpu",
|
|
735
|
-
"ORT_DISABLE_COREML": "1",
|
|
736
|
-
# Restore parallel OpenMP. The package caps OMP_NUM_THREADS
|
|
737
|
-
# globally to avoid a torch+lightgbm libomp SIGSEGV in the
|
|
738
|
-
# main process. This worker loads torch but never lightgbm,
|
|
739
|
-
# so there is no collision risk and full parallelism is safe.
|
|
740
|
-
"OMP_NUM_THREADS": str(os.cpu_count() or 4),
|
|
741
|
-
}
|
|
742
|
-
from superlocalmemory.core.platform_utils import popen_platform_kwargs
|
|
743
|
-
self._worker_proc = subprocess.Popen(
|
|
744
|
-
[sys.executable, "-m", worker_module],
|
|
745
|
-
stdin=subprocess.PIPE,
|
|
746
|
-
stdout=subprocess.PIPE,
|
|
747
|
-
stderr=subprocess.DEVNULL,
|
|
748
|
-
text=True,
|
|
749
|
-
bufsize=1,
|
|
750
|
-
env=env,
|
|
751
|
-
**popen_platform_kwargs(),
|
|
752
|
-
)
|
|
753
|
-
# v3.4.13: Register PID for machine-wide singleton guard
|
|
754
|
-
register_embedding_worker_pid(self._worker_proc.pid)
|
|
755
|
-
self._owns_worker_lock = True
|
|
756
|
-
logger.info("Embedding worker spawned (PID %d)", self._worker_proc.pid)
|
|
757
|
-
self._worker_ready = True
|
|
758
|
-
except Exception as exc:
|
|
759
|
-
failed_proc = self._worker_proc
|
|
760
|
-
if failed_proc is not None:
|
|
761
|
-
try:
|
|
762
|
-
failed_proc.terminate()
|
|
763
|
-
failed_proc.wait(timeout=3)
|
|
764
|
-
except Exception:
|
|
765
|
-
try:
|
|
766
|
-
failed_proc.kill()
|
|
767
|
-
failed_proc.wait(timeout=3)
|
|
768
|
-
except Exception:
|
|
769
|
-
pass
|
|
770
|
-
release_embedding_lock()
|
|
771
|
-
self._owns_worker_lock = False
|
|
772
|
-
logger.warning(
|
|
773
|
-
"Failed to spawn embedding worker: %s. "
|
|
774
|
-
"Run 'slm doctor' to verify your Python environment. "
|
|
775
|
-
"Using Python: %s",
|
|
776
|
-
exc, sys.executable,
|
|
777
|
-
)
|
|
778
|
-
self._available = False
|
|
779
|
-
self._worker_proc = None
|
|
780
|
-
|
|
781
|
-
def _kill_worker(self, timeout: float = 3.0) -> None:
|
|
782
|
-
"""Terminate the worker and close every owned pipe exactly once."""
|
|
783
|
-
if self._idle_timer is not None:
|
|
784
|
-
self._idle_timer.cancel()
|
|
785
|
-
self._idle_timer = None
|
|
786
|
-
|
|
787
|
-
proc = self._worker_proc
|
|
788
|
-
if proc is not None:
|
|
789
|
-
# Detach first so re-entrant/finalizer cleanup is idempotent.
|
|
790
|
-
self._worker_proc = None
|
|
791
|
-
self._worker_ready = False
|
|
792
|
-
try:
|
|
793
|
-
proc.stdin.write('{"cmd":"quit"}\n')
|
|
794
|
-
proc.stdin.flush()
|
|
795
|
-
proc.wait(timeout=max(0.0, timeout))
|
|
796
|
-
except Exception:
|
|
797
|
-
try:
|
|
798
|
-
returncode = proc.poll()
|
|
799
|
-
except Exception:
|
|
800
|
-
returncode = None
|
|
801
|
-
# MagicMock/unknown poll results are treated conservatively as
|
|
802
|
-
# live; a real exited child always reports an integer code.
|
|
803
|
-
if returncode is None or not isinstance(returncode, int):
|
|
804
|
-
try:
|
|
805
|
-
proc.kill()
|
|
806
|
-
proc.wait(timeout=max(0.0, timeout))
|
|
807
|
-
except Exception:
|
|
808
|
-
pass
|
|
809
|
-
finally:
|
|
810
|
-
# TextIOWrapper.close() can itself raise BrokenPipeError while
|
|
811
|
-
# flushing buffered stdin. Suppress it here, while the stream
|
|
812
|
-
# is still strongly referenced, so it cannot surface later as
|
|
813
|
-
# an unraisable finalizer warning.
|
|
814
|
-
for stream_name in ("stdin", "stdout", "stderr"):
|
|
815
|
-
stream = getattr(proc, stream_name, None)
|
|
816
|
-
if stream is not None:
|
|
817
|
-
try:
|
|
818
|
-
stream.close()
|
|
819
|
-
except (BrokenPipeError, OSError, ValueError):
|
|
820
|
-
pass
|
|
821
|
-
if getattr(self, "_owns_worker_lock", False):
|
|
822
|
-
try:
|
|
823
|
-
pid_file = _embedding_pid_file()
|
|
824
|
-
if (
|
|
825
|
-
proc is not None
|
|
826
|
-
and pid_file.exists()
|
|
827
|
-
and pid_file.read_text().strip() == str(proc.pid)
|
|
828
|
-
):
|
|
829
|
-
pid_file.unlink(missing_ok=True)
|
|
830
|
-
except (OSError, ValueError):
|
|
831
|
-
pass
|
|
832
|
-
finally:
|
|
833
|
-
self._owns_worker_lock = False
|
|
834
|
-
release_embedding_lock()
|
|
835
|
-
|
|
836
|
-
def _reset_idle_timer(self) -> None:
|
|
837
|
-
"""Reset the configurable worker-idle timer.
|
|
838
|
-
|
|
839
|
-
F5 fix: opportunistically evict the worker mid-window when memory
|
|
840
|
-
pressure is detected (``_check_memory_pressure`` returns False).
|
|
841
|
-
This prevents the idle-timeout window from holding a worker alive
|
|
842
|
-
while system memory is exhausted.
|
|
843
|
-
"""
|
|
844
|
-
if self._idle_timer is not None:
|
|
845
|
-
self._idle_timer.cancel()
|
|
846
|
-
if not self._check_memory_pressure():
|
|
847
|
-
# Pressure detected — kill worker immediately; do not schedule a
|
|
848
|
-
# new idle timer so no further embedding work is attempted until
|
|
849
|
-
# the next explicit request reloads the worker.
|
|
850
|
-
self._kill_worker()
|
|
851
|
-
self._idle_timer = None
|
|
852
|
-
return
|
|
853
|
-
self._idle_timer = threading.Timer(
|
|
854
|
-
_IDLE_TIMEOUT_SECONDS, self.unload,
|
|
855
|
-
)
|
|
856
|
-
self._idle_timer.daemon = True
|
|
857
|
-
self._idle_timer.start()
|
|
858
|
-
self._last_used = time.time()
|
|
859
|
-
|
|
860
|
-
# ------------------------------------------------------------------
|
|
861
|
-
# OpenAI-compatible embedding (V3.4.24 — any /v1/embeddings endpoint)
|
|
862
|
-
# ------------------------------------------------------------------
|
|
863
|
-
|
|
864
|
-
def _get_http_client(self):
|
|
865
|
-
"""Reusable httpx client for OpenAI-compatible endpoints."""
|
|
866
|
-
if self._http_client is None:
|
|
867
|
-
import httpx
|
|
868
|
-
self._http_client = httpx.Client(
|
|
869
|
-
timeout=httpx.Timeout(connect=5.0, read=30.0, write=10.0, pool=5.0),
|
|
870
|
-
)
|
|
871
|
-
return self._http_client
|
|
872
|
-
|
|
873
|
-
def _openai_compatible_embed_batch(
|
|
874
|
-
self, texts: list[str], *, max_retries: int = 3,
|
|
875
|
-
) -> list[list[float]]:
|
|
876
|
-
"""Encode via any OpenAI-compatible embedding API.
|
|
877
|
-
|
|
878
|
-
V3.4.24: Standard ``/v1/embeddings`` format. Works with Ollama,
|
|
879
|
-
vLLM, LiteLLM, text-embeddings-inference, and any endpoint that
|
|
880
|
-
implements the OpenAI embeddings spec.
|
|
881
|
-
"""
|
|
882
|
-
endpoint = self._config.api_endpoint.rstrip("/")
|
|
883
|
-
if not endpoint.endswith("/embeddings"):
|
|
884
|
-
endpoint = f"{endpoint}/embeddings"
|
|
885
|
-
headers = {"Content-Type": "application/json"}
|
|
886
|
-
if self._config.api_key:
|
|
887
|
-
headers["Authorization"] = f"Bearer {self._config.api_key}"
|
|
888
|
-
body = {
|
|
889
|
-
"input": texts,
|
|
890
|
-
"model": self._config.model_name,
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
client = self._get_http_client()
|
|
894
|
-
last_error: Exception | None = None
|
|
895
|
-
for attempt in range(max_retries):
|
|
896
|
-
from superlocalmemory.core.materialization_control import (
|
|
897
|
-
MaterializationDeferred,
|
|
898
|
-
)
|
|
899
|
-
from superlocalmemory.core.recall_gate import (
|
|
900
|
-
background_preempt_requested,
|
|
901
|
-
is_background_work,
|
|
902
|
-
)
|
|
903
|
-
if background_preempt_requested():
|
|
904
|
-
raise MaterializationDeferred(
|
|
905
|
-
"background embedding yielded to runtime transition"
|
|
906
|
-
)
|
|
907
|
-
try:
|
|
908
|
-
request_kwargs = {"headers": headers, "json": body}
|
|
909
|
-
if is_background_work():
|
|
910
|
-
# Runtime reconfigure drains admitted operations in five
|
|
911
|
-
# seconds. A background remote read must leave enough
|
|
912
|
-
# scheduling margin to observe that transition and release
|
|
913
|
-
# its lease, while interactive recall keeps the provider's
|
|
914
|
-
# normal timeout budget.
|
|
915
|
-
request_kwargs["timeout"] = 3.5
|
|
916
|
-
resp = client.post(endpoint, **request_kwargs)
|
|
917
|
-
resp.raise_for_status()
|
|
918
|
-
if background_preempt_requested():
|
|
919
|
-
raise MaterializationDeferred(
|
|
920
|
-
"background embedding yielded to runtime transition"
|
|
921
|
-
)
|
|
922
|
-
data = resp.json()
|
|
923
|
-
if "data" not in data or not isinstance(data["data"], list):
|
|
924
|
-
raise ValueError(
|
|
925
|
-
f"Unexpected response: missing 'data' array. Keys: {list(data.keys())}"
|
|
926
|
-
)
|
|
927
|
-
results: list[list[float]] = []
|
|
928
|
-
for item in sorted(data["data"], key=lambda d: d["index"]):
|
|
929
|
-
results.append(item["embedding"])
|
|
930
|
-
if len(results) != len(texts):
|
|
931
|
-
logger.warning(
|
|
932
|
-
"Embedding count mismatch: sent %d texts, got %d vectors",
|
|
933
|
-
len(texts), len(results),
|
|
934
|
-
)
|
|
935
|
-
return results
|
|
936
|
-
except Exception as exc:
|
|
937
|
-
if isinstance(exc, MaterializationDeferred):
|
|
938
|
-
raise
|
|
939
|
-
if background_preempt_requested():
|
|
940
|
-
raise MaterializationDeferred(
|
|
941
|
-
"background embedding yielded to runtime transition"
|
|
942
|
-
) from exc
|
|
943
|
-
last_error = exc
|
|
944
|
-
if attempt < max_retries - 1:
|
|
945
|
-
time.sleep(2 ** attempt)
|
|
946
|
-
raise RuntimeError(
|
|
947
|
-
f"OpenAI-compatible embedding failed after {max_retries} retries: "
|
|
948
|
-
f"{last_error}"
|
|
949
|
-
)
|
|
950
|
-
|
|
951
|
-
# ------------------------------------------------------------------
|
|
952
|
-
# Cloud embedding (no subprocess needed — just HTTP)
|
|
953
|
-
# ------------------------------------------------------------------
|
|
954
|
-
|
|
955
|
-
def _cloud_embed_single(self, text: str) -> list[float]:
|
|
956
|
-
vecs = self._cloud_embed_batch([text])
|
|
957
|
-
return vecs[0]
|
|
958
|
-
|
|
959
|
-
def _cloud_embed_batch(
|
|
960
|
-
self, texts: list[str], *, max_retries: int = 3,
|
|
961
|
-
) -> list[list[float]]:
|
|
962
|
-
"""Encode via Azure OpenAI embedding API with retry.
|
|
963
|
-
|
|
964
|
-
V3.6.14: Reuses self._get_http_client() (shared persistent connection)
|
|
965
|
-
instead of creating a fresh httpx.Client per call/retry. resp.json()
|
|
966
|
-
is now called inside the try block while the response object is still
|
|
967
|
-
in scope, eliminating reliance on httpx body-buffering after close.
|
|
968
|
-
"""
|
|
969
|
-
url = (
|
|
970
|
-
f"{self._config.api_endpoint.rstrip('/')}/openai/deployments/"
|
|
971
|
-
f"{self._config.deployment_name}/embeddings"
|
|
972
|
-
f"?api-version={self._config.api_version}"
|
|
973
|
-
)
|
|
974
|
-
headers = {
|
|
975
|
-
"Content-Type": "application/json",
|
|
976
|
-
"api-key": self._config.api_key,
|
|
977
|
-
}
|
|
978
|
-
body = {"input": texts, "model": self._config.deployment_name}
|
|
979
|
-
client = self._get_http_client()
|
|
980
|
-
last_error: Exception | None = None
|
|
981
|
-
for attempt in range(max_retries):
|
|
982
|
-
from superlocalmemory.core.materialization_control import (
|
|
983
|
-
MaterializationDeferred,
|
|
984
|
-
)
|
|
985
|
-
from superlocalmemory.core.recall_gate import (
|
|
986
|
-
background_preempt_requested,
|
|
987
|
-
is_background_work,
|
|
988
|
-
)
|
|
989
|
-
if background_preempt_requested():
|
|
990
|
-
raise MaterializationDeferred(
|
|
991
|
-
"background embedding yielded to runtime transition"
|
|
992
|
-
)
|
|
993
|
-
try:
|
|
994
|
-
request_kwargs = {"headers": headers, "json": body}
|
|
995
|
-
if is_background_work():
|
|
996
|
-
request_kwargs["timeout"] = 3.5
|
|
997
|
-
resp = client.post(url, **request_kwargs)
|
|
998
|
-
resp.raise_for_status()
|
|
999
|
-
if background_preempt_requested():
|
|
1000
|
-
raise MaterializationDeferred(
|
|
1001
|
-
"background embedding yielded to runtime transition"
|
|
1002
|
-
)
|
|
1003
|
-
data = resp.json()
|
|
1004
|
-
results = []
|
|
1005
|
-
for item in sorted(data["data"], key=lambda d: d["index"]):
|
|
1006
|
-
results.append(item["embedding"])
|
|
1007
|
-
return results
|
|
1008
|
-
except Exception as exc:
|
|
1009
|
-
if isinstance(exc, MaterializationDeferred):
|
|
1010
|
-
raise
|
|
1011
|
-
if background_preempt_requested():
|
|
1012
|
-
raise MaterializationDeferred(
|
|
1013
|
-
"background embedding yielded to runtime transition"
|
|
1014
|
-
) from exc
|
|
1015
|
-
last_error = exc
|
|
1016
|
-
if attempt < max_retries - 1:
|
|
1017
|
-
time.sleep(2 ** attempt)
|
|
1018
|
-
raise RuntimeError(f"Cloud embedding failed: {last_error}")
|
|
1019
|
-
|
|
1020
|
-
# ------------------------------------------------------------------
|
|
1021
|
-
# Validation
|
|
1022
|
-
# ------------------------------------------------------------------
|
|
1023
|
-
|
|
1024
|
-
def _validate_dimension(self, vec: NDArray) -> None:
|
|
1025
|
-
actual = len(vec)
|
|
1026
|
-
if actual != self._config.dimension:
|
|
1027
|
-
raise DimensionMismatchError(
|
|
1028
|
-
f"Embedding dimension {actual} != expected {self._config.dimension}"
|
|
1029
|
-
)
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
# ---------------------------------------------------------------------------
|
|
1033
|
-
# Module-level atexit: kill ALL embedding workers on process exit
|
|
1034
|
-
# ---------------------------------------------------------------------------
|
|
1035
|
-
|
|
1036
|
-
def _cleanup_all_embedding_services() -> None:
|
|
1037
|
-
"""Kill all embedding worker subprocesses on interpreter exit.
|
|
1038
|
-
|
|
1039
|
-
Prevents orphaned 500-800 MB sentence-transformer workers surviving
|
|
1040
|
-
after parent exits (especially during test runs with parallel agents).
|
|
1041
|
-
"""
|
|
1042
|
-
for ref in list(_live_embedding_services):
|
|
1043
|
-
svc = ref()
|
|
1044
|
-
if svc is not None:
|
|
1045
|
-
try:
|
|
1046
|
-
svc._kill_worker()
|
|
1047
|
-
except Exception:
|
|
1048
|
-
pass
|
|
1049
|
-
_live_embedding_services.clear()
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
atexit.register(_cleanup_all_embedding_services)
|