superlocalmemory 2.8.6 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -1
- package/NOTICE +63 -0
- package/README.md +165 -480
- package/bin/slm +17 -449
- package/bin/slm-npm +62 -48
- package/conftest.py +5 -0
- package/docs/api-reference.md +284 -0
- package/docs/architecture.md +149 -0
- package/docs/auto-memory.md +150 -0
- package/docs/cli-reference.md +276 -0
- package/docs/compliance.md +191 -0
- package/docs/configuration.md +182 -0
- package/docs/getting-started.md +102 -0
- package/docs/ide-setup.md +261 -0
- package/docs/mcp-tools.md +220 -0
- package/docs/migration-from-v2.md +170 -0
- package/docs/profiles.md +173 -0
- package/docs/troubleshooting.md +310 -0
- package/{configs → ide/configs}/antigravity-mcp.json +3 -3
- package/ide/configs/chatgpt-desktop-mcp.json +16 -0
- package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
- package/{configs → ide/configs}/codex-mcp.toml +4 -4
- package/{configs → ide/configs}/continue-mcp.yaml +4 -3
- package/{configs → ide/configs}/continue-skills.yaml +6 -6
- package/ide/configs/cursor-mcp.json +15 -0
- package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
- package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
- package/{configs → ide/configs}/opencode-mcp.json +2 -2
- package/{configs → ide/configs}/perplexity-mcp.json +2 -2
- package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
- package/{configs → ide/configs}/windsurf-mcp.json +3 -3
- package/{configs → ide/configs}/zed-mcp.json +2 -2
- package/{hooks → ide/hooks}/context-hook.js +9 -20
- package/ide/hooks/memory-list-skill.js +70 -0
- package/ide/hooks/memory-profile-skill.js +101 -0
- package/ide/hooks/memory-recall-skill.js +62 -0
- package/ide/hooks/memory-remember-skill.js +68 -0
- package/ide/hooks/memory-reset-skill.js +160 -0
- package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
- package/ide/integrations/langchain/README.md +106 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
- package/ide/integrations/langchain/pyproject.toml +38 -0
- package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
- package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
- package/ide/integrations/langchain/tests/test_security.py +117 -0
- package/ide/integrations/llamaindex/README.md +81 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
- package/ide/integrations/llamaindex/pyproject.toml +43 -0
- package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
- package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
- package/ide/integrations/llamaindex/tests/test_security.py +241 -0
- package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
- package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
- package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
- package/package.json +13 -22
- package/pyproject.toml +85 -0
- package/scripts/build-dmg.sh +417 -0
- package/scripts/install-skills.ps1 +334 -0
- package/scripts/postinstall.js +2 -2
- package/scripts/start-dashboard.ps1 +52 -0
- package/scripts/start-dashboard.sh +41 -0
- package/scripts/sync-wiki.ps1 +127 -0
- package/scripts/sync-wiki.sh +82 -0
- package/scripts/test-dmg.sh +161 -0
- package/scripts/test-npm-package.ps1 +252 -0
- package/scripts/test-npm-package.sh +207 -0
- package/scripts/verify-install.ps1 +294 -0
- package/scripts/verify-install.sh +266 -0
- package/src/superlocalmemory/__init__.py +0 -0
- package/src/superlocalmemory/attribution/__init__.py +9 -0
- package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
- package/src/superlocalmemory/attribution/signer.py +153 -0
- package/src/superlocalmemory/attribution/watermark.py +189 -0
- package/src/superlocalmemory/cli/__init__.py +5 -0
- package/src/superlocalmemory/cli/commands.py +245 -0
- package/src/superlocalmemory/cli/main.py +89 -0
- package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
- package/src/superlocalmemory/cli/post_install.py +99 -0
- package/src/superlocalmemory/cli/setup_wizard.py +129 -0
- package/src/superlocalmemory/compliance/__init__.py +0 -0
- package/src/superlocalmemory/compliance/abac.py +204 -0
- package/src/superlocalmemory/compliance/audit.py +314 -0
- package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
- package/src/superlocalmemory/compliance/gdpr.py +294 -0
- package/src/superlocalmemory/compliance/lifecycle.py +158 -0
- package/src/superlocalmemory/compliance/retention.py +232 -0
- package/src/superlocalmemory/compliance/scheduler.py +148 -0
- package/src/superlocalmemory/core/__init__.py +0 -0
- package/src/superlocalmemory/core/config.py +391 -0
- package/src/superlocalmemory/core/embeddings.py +293 -0
- package/src/superlocalmemory/core/engine.py +701 -0
- package/src/superlocalmemory/core/hooks.py +65 -0
- package/src/superlocalmemory/core/maintenance.py +172 -0
- package/src/superlocalmemory/core/modes.py +140 -0
- package/src/superlocalmemory/core/profiles.py +234 -0
- package/src/superlocalmemory/core/registry.py +117 -0
- package/src/superlocalmemory/dynamics/__init__.py +0 -0
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
- package/src/superlocalmemory/encoding/__init__.py +0 -0
- package/src/superlocalmemory/encoding/consolidator.py +485 -0
- package/src/superlocalmemory/encoding/emotional.py +125 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
- package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
- package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
- package/src/superlocalmemory/encoding/foresight.py +91 -0
- package/src/superlocalmemory/encoding/graph_builder.py +302 -0
- package/src/superlocalmemory/encoding/observation_builder.py +160 -0
- package/src/superlocalmemory/encoding/scene_builder.py +183 -0
- package/src/superlocalmemory/encoding/signal_inference.py +90 -0
- package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
- package/src/superlocalmemory/encoding/type_router.py +235 -0
- package/src/superlocalmemory/hooks/__init__.py +3 -0
- package/src/superlocalmemory/hooks/auto_capture.py +111 -0
- package/src/superlocalmemory/hooks/auto_recall.py +93 -0
- package/src/superlocalmemory/hooks/ide_connector.py +204 -0
- package/src/superlocalmemory/hooks/rules_engine.py +99 -0
- package/src/superlocalmemory/infra/__init__.py +3 -0
- package/src/superlocalmemory/infra/auth_middleware.py +82 -0
- package/src/superlocalmemory/infra/backup.py +317 -0
- package/src/superlocalmemory/infra/cache_manager.py +267 -0
- package/src/superlocalmemory/infra/event_bus.py +381 -0
- package/src/superlocalmemory/infra/rate_limiter.py +135 -0
- package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
- package/src/superlocalmemory/learning/__init__.py +0 -0
- package/src/superlocalmemory/learning/adaptive.py +172 -0
- package/src/superlocalmemory/learning/behavioral.py +490 -0
- package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
- package/src/superlocalmemory/learning/bootstrap.py +298 -0
- package/src/superlocalmemory/learning/cross_project.py +399 -0
- package/src/superlocalmemory/learning/database.py +376 -0
- package/src/superlocalmemory/learning/engagement.py +323 -0
- package/src/superlocalmemory/learning/features.py +138 -0
- package/src/superlocalmemory/learning/feedback.py +316 -0
- package/src/superlocalmemory/learning/outcomes.py +255 -0
- package/src/superlocalmemory/learning/project_context.py +366 -0
- package/src/superlocalmemory/learning/ranker.py +155 -0
- package/src/superlocalmemory/learning/source_quality.py +303 -0
- package/src/superlocalmemory/learning/workflows.py +309 -0
- package/src/superlocalmemory/llm/__init__.py +0 -0
- package/src/superlocalmemory/llm/backbone.py +316 -0
- package/src/superlocalmemory/math/__init__.py +0 -0
- package/src/superlocalmemory/math/fisher.py +356 -0
- package/src/superlocalmemory/math/langevin.py +398 -0
- package/src/superlocalmemory/math/sheaf.py +257 -0
- package/src/superlocalmemory/mcp/__init__.py +0 -0
- package/src/superlocalmemory/mcp/resources.py +245 -0
- package/src/superlocalmemory/mcp/server.py +61 -0
- package/src/superlocalmemory/mcp/tools.py +18 -0
- package/src/superlocalmemory/mcp/tools_core.py +305 -0
- package/src/superlocalmemory/mcp/tools_v28.py +223 -0
- package/src/superlocalmemory/mcp/tools_v3.py +286 -0
- package/src/superlocalmemory/retrieval/__init__.py +0 -0
- package/src/superlocalmemory/retrieval/agentic.py +295 -0
- package/src/superlocalmemory/retrieval/ann_index.py +223 -0
- package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
- package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
- package/src/superlocalmemory/retrieval/engine.py +390 -0
- package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
- package/src/superlocalmemory/retrieval/fusion.py +78 -0
- package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
- package/src/superlocalmemory/retrieval/reranker.py +154 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
- package/src/superlocalmemory/retrieval/strategy.py +96 -0
- package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
- package/src/superlocalmemory/server/__init__.py +1 -0
- package/src/superlocalmemory/server/api.py +248 -0
- package/src/superlocalmemory/server/routes/__init__.py +4 -0
- package/src/superlocalmemory/server/routes/agents.py +107 -0
- package/src/superlocalmemory/server/routes/backup.py +91 -0
- package/src/superlocalmemory/server/routes/behavioral.py +127 -0
- package/src/superlocalmemory/server/routes/compliance.py +160 -0
- package/src/superlocalmemory/server/routes/data_io.py +188 -0
- package/src/superlocalmemory/server/routes/events.py +183 -0
- package/src/superlocalmemory/server/routes/helpers.py +85 -0
- package/src/superlocalmemory/server/routes/learning.py +273 -0
- package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
- package/src/superlocalmemory/server/routes/memories.py +399 -0
- package/src/superlocalmemory/server/routes/profiles.py +219 -0
- package/src/superlocalmemory/server/routes/stats.py +346 -0
- package/src/superlocalmemory/server/routes/v3_api.py +365 -0
- package/src/superlocalmemory/server/routes/ws.py +82 -0
- package/src/superlocalmemory/server/security_middleware.py +57 -0
- package/src/superlocalmemory/server/ui.py +245 -0
- package/src/superlocalmemory/storage/__init__.py +0 -0
- package/src/superlocalmemory/storage/access_control.py +182 -0
- package/src/superlocalmemory/storage/database.py +594 -0
- package/src/superlocalmemory/storage/migrations.py +303 -0
- package/src/superlocalmemory/storage/models.py +406 -0
- package/src/superlocalmemory/storage/schema.py +726 -0
- package/src/superlocalmemory/storage/v2_migrator.py +317 -0
- package/src/superlocalmemory/trust/__init__.py +0 -0
- package/src/superlocalmemory/trust/gate.py +130 -0
- package/src/superlocalmemory/trust/provenance.py +124 -0
- package/src/superlocalmemory/trust/scorer.py +347 -0
- package/src/superlocalmemory/trust/signals.py +153 -0
- package/ui/index.html +278 -5
- package/ui/js/auto-settings.js +70 -0
- package/ui/js/dashboard.js +90 -0
- package/ui/js/fact-detail.js +92 -0
- package/ui/js/feedback.js +2 -2
- package/ui/js/ide-status.js +102 -0
- package/ui/js/math-health.js +98 -0
- package/ui/js/recall-lab.js +127 -0
- package/ui/js/settings.js +2 -2
- package/ui/js/trust-dashboard.js +73 -0
- package/api_server.py +0 -724
- package/bin/aider-smart +0 -72
- package/bin/superlocalmemoryv2-learning +0 -4
- package/bin/superlocalmemoryv2-list +0 -3
- package/bin/superlocalmemoryv2-patterns +0 -4
- package/bin/superlocalmemoryv2-profile +0 -3
- package/bin/superlocalmemoryv2-recall +0 -3
- package/bin/superlocalmemoryv2-remember +0 -3
- package/bin/superlocalmemoryv2-reset +0 -3
- package/bin/superlocalmemoryv2-status +0 -3
- package/configs/chatgpt-desktop-mcp.json +0 -16
- package/configs/cursor-mcp.json +0 -15
- package/hooks/memory-list-skill.js +0 -139
- package/hooks/memory-profile-skill.js +0 -273
- package/hooks/memory-recall-skill.js +0 -114
- package/hooks/memory-remember-skill.js +0 -127
- package/hooks/memory-reset-skill.js +0 -274
- package/mcp_server.py +0 -1808
- package/requirements-core.txt +0 -22
- package/requirements-learning.txt +0 -12
- package/requirements.txt +0 -12
- package/src/agent_registry.py +0 -411
- package/src/auth_middleware.py +0 -61
- package/src/auto_backup.py +0 -459
- package/src/behavioral/__init__.py +0 -49
- package/src/behavioral/behavioral_listener.py +0 -203
- package/src/behavioral/behavioral_patterns.py +0 -275
- package/src/behavioral/cross_project_transfer.py +0 -206
- package/src/behavioral/outcome_inference.py +0 -194
- package/src/behavioral/outcome_tracker.py +0 -193
- package/src/behavioral/tests/__init__.py +0 -4
- package/src/behavioral/tests/test_behavioral_integration.py +0 -108
- package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
- package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
- package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
- package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
- package/src/behavioral/tests/test_outcome_inference.py +0 -107
- package/src/behavioral/tests/test_outcome_tracker.py +0 -96
- package/src/cache_manager.py +0 -518
- package/src/compliance/__init__.py +0 -48
- package/src/compliance/abac_engine.py +0 -149
- package/src/compliance/abac_middleware.py +0 -116
- package/src/compliance/audit_db.py +0 -215
- package/src/compliance/audit_logger.py +0 -148
- package/src/compliance/retention_manager.py +0 -289
- package/src/compliance/retention_scheduler.py +0 -186
- package/src/compliance/tests/__init__.py +0 -4
- package/src/compliance/tests/test_abac_enforcement.py +0 -95
- package/src/compliance/tests/test_abac_engine.py +0 -124
- package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
- package/src/compliance/tests/test_audit_db.py +0 -123
- package/src/compliance/tests/test_audit_logger.py +0 -98
- package/src/compliance/tests/test_mcp_audit.py +0 -128
- package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
- package/src/compliance/tests/test_retention_manager.py +0 -131
- package/src/compliance/tests/test_retention_scheduler.py +0 -99
- package/src/compression/__init__.py +0 -25
- package/src/compression/cli.py +0 -150
- package/src/compression/cold_storage.py +0 -217
- package/src/compression/config.py +0 -72
- package/src/compression/orchestrator.py +0 -133
- package/src/compression/tier2_compressor.py +0 -228
- package/src/compression/tier3_compressor.py +0 -153
- package/src/compression/tier_classifier.py +0 -148
- package/src/db_connection_manager.py +0 -536
- package/src/embedding_engine.py +0 -63
- package/src/embeddings/__init__.py +0 -47
- package/src/embeddings/cache.py +0 -70
- package/src/embeddings/cli.py +0 -113
- package/src/embeddings/constants.py +0 -47
- package/src/embeddings/database.py +0 -91
- package/src/embeddings/engine.py +0 -247
- package/src/embeddings/model_loader.py +0 -145
- package/src/event_bus.py +0 -562
- package/src/graph/__init__.py +0 -36
- package/src/graph/build_helpers.py +0 -74
- package/src/graph/cli.py +0 -87
- package/src/graph/cluster_builder.py +0 -188
- package/src/graph/cluster_summary.py +0 -148
- package/src/graph/constants.py +0 -47
- package/src/graph/edge_builder.py +0 -162
- package/src/graph/entity_extractor.py +0 -95
- package/src/graph/graph_core.py +0 -226
- package/src/graph/graph_search.py +0 -231
- package/src/graph/hierarchical.py +0 -207
- package/src/graph/schema.py +0 -99
- package/src/graph_engine.py +0 -52
- package/src/hnsw_index.py +0 -628
- package/src/hybrid_search.py +0 -46
- package/src/learning/__init__.py +0 -217
- package/src/learning/adaptive_ranker.py +0 -682
- package/src/learning/bootstrap/__init__.py +0 -69
- package/src/learning/bootstrap/constants.py +0 -93
- package/src/learning/bootstrap/db_queries.py +0 -316
- package/src/learning/bootstrap/sampling.py +0 -82
- package/src/learning/bootstrap/text_utils.py +0 -71
- package/src/learning/cross_project_aggregator.py +0 -857
- package/src/learning/db/__init__.py +0 -40
- package/src/learning/db/constants.py +0 -44
- package/src/learning/db/schema.py +0 -279
- package/src/learning/engagement_tracker.py +0 -628
- package/src/learning/feature_extractor.py +0 -708
- package/src/learning/feedback_collector.py +0 -806
- package/src/learning/learning_db.py +0 -915
- package/src/learning/project_context_manager.py +0 -572
- package/src/learning/ranking/__init__.py +0 -33
- package/src/learning/ranking/constants.py +0 -84
- package/src/learning/ranking/helpers.py +0 -278
- package/src/learning/source_quality_scorer.py +0 -676
- package/src/learning/synthetic_bootstrap.py +0 -755
- package/src/learning/tests/test_adaptive_ranker.py +0 -325
- package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
- package/src/learning/tests/test_aggregator.py +0 -306
- package/src/learning/tests/test_auto_retrain_v28.py +0 -35
- package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
- package/src/learning/tests/test_feature_extractor_v28.py +0 -93
- package/src/learning/tests/test_feedback_collector.py +0 -294
- package/src/learning/tests/test_learning_db.py +0 -602
- package/src/learning/tests/test_learning_db_v28.py +0 -110
- package/src/learning/tests/test_learning_init_v28.py +0 -48
- package/src/learning/tests/test_outcome_signals.py +0 -48
- package/src/learning/tests/test_project_context.py +0 -292
- package/src/learning/tests/test_schema_migration.py +0 -319
- package/src/learning/tests/test_signal_inference.py +0 -397
- package/src/learning/tests/test_source_quality.py +0 -351
- package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
- package/src/learning/tests/test_workflow_miner.py +0 -318
- package/src/learning/workflow_pattern_miner.py +0 -655
- package/src/lifecycle/__init__.py +0 -54
- package/src/lifecycle/bounded_growth.py +0 -239
- package/src/lifecycle/compaction_engine.py +0 -226
- package/src/lifecycle/lifecycle_engine.py +0 -355
- package/src/lifecycle/lifecycle_evaluator.py +0 -257
- package/src/lifecycle/lifecycle_scheduler.py +0 -130
- package/src/lifecycle/retention_policy.py +0 -285
- package/src/lifecycle/tests/test_bounded_growth.py +0 -193
- package/src/lifecycle/tests/test_compaction.py +0 -179
- package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
- package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
- package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
- package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
- package/src/lifecycle/tests/test_mcp_compact.py +0 -149
- package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
- package/src/lifecycle/tests/test_retention_policy.py +0 -162
- package/src/mcp_tools_v28.py +0 -281
- package/src/memory/__init__.py +0 -36
- package/src/memory/cli.py +0 -205
- package/src/memory/constants.py +0 -39
- package/src/memory/helpers.py +0 -28
- package/src/memory/schema.py +0 -166
- package/src/memory-profiles.py +0 -595
- package/src/memory-reset.py +0 -491
- package/src/memory_compression.py +0 -989
- package/src/memory_store_v2.py +0 -1155
- package/src/migrate_v1_to_v2.py +0 -629
- package/src/pattern_learner.py +0 -34
- package/src/patterns/__init__.py +0 -24
- package/src/patterns/analyzers.py +0 -251
- package/src/patterns/learner.py +0 -271
- package/src/patterns/scoring.py +0 -171
- package/src/patterns/store.py +0 -225
- package/src/patterns/terminology.py +0 -140
- package/src/provenance_tracker.py +0 -312
- package/src/qualixar_attribution.py +0 -139
- package/src/qualixar_watermark.py +0 -78
- package/src/query_optimizer.py +0 -511
- package/src/rate_limiter.py +0 -83
- package/src/search/__init__.py +0 -20
- package/src/search/cli.py +0 -77
- package/src/search/constants.py +0 -26
- package/src/search/engine.py +0 -241
- package/src/search/fusion.py +0 -122
- package/src/search/index_loader.py +0 -114
- package/src/search/methods.py +0 -162
- package/src/search_engine_v2.py +0 -401
- package/src/setup_validator.py +0 -482
- package/src/subscription_manager.py +0 -391
- package/src/tree/__init__.py +0 -59
- package/src/tree/builder.py +0 -185
- package/src/tree/nodes.py +0 -202
- package/src/tree/queries.py +0 -257
- package/src/tree/schema.py +0 -80
- package/src/tree_manager.py +0 -19
- package/src/trust/__init__.py +0 -45
- package/src/trust/constants.py +0 -66
- package/src/trust/queries.py +0 -157
- package/src/trust/schema.py +0 -95
- package/src/trust/scorer.py +0 -299
- package/src/trust/signals.py +0 -95
- package/src/trust_scorer.py +0 -44
- package/ui/app.js +0 -1588
- package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
- package/ui/js/graph-cytoscape.js +0 -1168
- package/ui/js/graph-d3-backup.js +0 -32
- package/ui/js/graph.js +0 -32
- package/ui_server.py +0 -286
- /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
- /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
- /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
- /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
- /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
- /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
- /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
- /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
- /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
- /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
- /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
- /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
- /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
- /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
- /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
- /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
- /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
- /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
- /package/{completions → ide/completions}/slm.bash +0 -0
- /package/{completions → ide/completions}/slm.zsh +0 -0
- /package/{configs → ide/configs}/cody-commands.json +0 -0
- /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
- /package/{install.ps1 → scripts/install.ps1} +0 -0
- /package/{install.sh → scripts/install.sh} +0 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""SuperLocalMemory V3 — Bayesian Trust Scorer (Beta Distribution).
|
|
6
|
+
|
|
7
|
+
Per-agent and per-fact trust using conjugate Beta(alpha, beta) priors.
|
|
8
|
+
Trust = alpha / (alpha + beta). Prior decay toward uniform when idle.
|
|
9
|
+
|
|
10
|
+
Encoding into existing schema (no ALTER TABLE):
|
|
11
|
+
trust_score = alpha / (alpha + beta)
|
|
12
|
+
evidence_count = round(alpha + beta - 2) (subtract the default prior)
|
|
13
|
+
Reconstruct: alpha = trust_score * (evidence_count + 2)
|
|
14
|
+
beta = (1 - trust_score) * (evidence_count + 2)
|
|
15
|
+
|
|
16
|
+
Backward-compatible: update_on_confirmation / contradiction / access
|
|
17
|
+
delegate to record_signal internally.
|
|
18
|
+
|
|
19
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
import math
|
|
26
|
+
from datetime import UTC, datetime
|
|
27
|
+
|
|
28
|
+
from superlocalmemory.storage.models import TrustScore
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
# Default Beta(1,1) = uniform prior => trust 0.5
|
|
33
|
+
_DEFAULT_ALPHA = 1.0
|
|
34
|
+
_DEFAULT_BETA = 1.0
|
|
35
|
+
_DEFAULT_TRUST = 0.5
|
|
36
|
+
|
|
37
|
+
# Signal strengths (how much each event shifts alpha/beta)
|
|
38
|
+
_SIGNAL_WEIGHTS: dict[str, tuple[float, float]] = {
|
|
39
|
+
# (delta_alpha, delta_beta)
|
|
40
|
+
"store_success": (1.0, 0.0),
|
|
41
|
+
"store_rejected": (0.0, 2.0),
|
|
42
|
+
"recall_hit": (0.5, 0.0),
|
|
43
|
+
"contradiction": (0.0, 3.0),
|
|
44
|
+
"deletion": (0.0, 1.5),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Prior decay: days of inactivity before decay starts, and rate per day
|
|
48
|
+
_DECAY_IDLE_DAYS = 30
|
|
49
|
+
_DECAY_RATE = 0.05 # 5% per day toward prior
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TrustScorer:
|
|
53
|
+
"""Bayesian trust scoring with Beta distribution.
|
|
54
|
+
|
|
55
|
+
Trust(agent) = alpha / (alpha + beta).
|
|
56
|
+
- Positive evidence increments alpha.
|
|
57
|
+
- Negative evidence increments beta.
|
|
58
|
+
- Idle decay shrinks both toward 1.0 (uniform prior).
|
|
59
|
+
|
|
60
|
+
Facts inherit their source agent's trust, penalized by contradictions.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, db) -> None:
|
|
64
|
+
self._db = db
|
|
65
|
+
|
|
66
|
+
# ------------------------------------------------------------------
|
|
67
|
+
# Public API: per-agent trust
|
|
68
|
+
# ------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
def get_agent_trust(self, agent_id: str, profile_id: str) -> float:
|
|
71
|
+
"""Get trust score for an agent. Returns 0.5 if unknown."""
|
|
72
|
+
alpha, beta = self._get_beta_params("agent", agent_id, profile_id)
|
|
73
|
+
return self._compute_trust(alpha, beta)
|
|
74
|
+
|
|
75
|
+
def get_fact_trust(self, fact_id: str, profile_id: str) -> float:
|
|
76
|
+
"""Get trust for a fact. Inherits source agent trust, modified by
|
|
77
|
+
any contradiction evidence recorded directly against the fact."""
|
|
78
|
+
alpha, beta = self._get_beta_params("fact", fact_id, profile_id)
|
|
79
|
+
return self._compute_trust(alpha, beta)
|
|
80
|
+
|
|
81
|
+
def get_entity_trust(self, entity_id: str, profile_id: str) -> float:
|
|
82
|
+
"""Convenience: get trust for an entity."""
|
|
83
|
+
return self.get_trust("entity", entity_id, profile_id)
|
|
84
|
+
|
|
85
|
+
def get_trust(
|
|
86
|
+
self, target_type: str, target_id: str, profile_id: str
|
|
87
|
+
) -> float:
|
|
88
|
+
"""Generic trust lookup — backward compatible with V3 Task 3."""
|
|
89
|
+
alpha, beta = self._get_beta_params(target_type, target_id, profile_id)
|
|
90
|
+
return self._compute_trust(alpha, beta)
|
|
91
|
+
|
|
92
|
+
# ------------------------------------------------------------------
|
|
93
|
+
# Public API: record signals
|
|
94
|
+
# ------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
def record_signal(
|
|
97
|
+
self, agent_id: str, profile_id: str, signal_type: str
|
|
98
|
+
) -> float:
|
|
99
|
+
"""Record a trust signal and return updated trust score.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
agent_id: The agent whose trust is being updated.
|
|
103
|
+
profile_id: Active profile scope.
|
|
104
|
+
signal_type: One of store_success, store_rejected, recall_hit,
|
|
105
|
+
contradiction, deletion.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Updated trust score for the agent.
|
|
109
|
+
"""
|
|
110
|
+
weights = _SIGNAL_WEIGHTS.get(signal_type, (0.0, 0.0))
|
|
111
|
+
delta_alpha, delta_beta = weights
|
|
112
|
+
|
|
113
|
+
alpha, beta = self._get_beta_params("agent", agent_id, profile_id)
|
|
114
|
+
new_alpha = alpha + delta_alpha
|
|
115
|
+
new_beta = beta + delta_beta
|
|
116
|
+
|
|
117
|
+
self._persist_beta("agent", agent_id, profile_id, new_alpha, new_beta)
|
|
118
|
+
score = self._compute_trust(new_alpha, new_beta)
|
|
119
|
+
logger.debug(
|
|
120
|
+
"trust signal: agent=%s signal=%s trust=%.3f (a=%.1f b=%.1f)",
|
|
121
|
+
agent_id, signal_type, score, new_alpha, new_beta,
|
|
122
|
+
)
|
|
123
|
+
return score
|
|
124
|
+
|
|
125
|
+
# ------------------------------------------------------------------
|
|
126
|
+
# Public API: propagation
|
|
127
|
+
# ------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
def propagate_recall_trust(
|
|
130
|
+
self, agent_id: str, profile_id: str
|
|
131
|
+
) -> float:
|
|
132
|
+
"""Boost trust for agents whose memories are recalled.
|
|
133
|
+
|
|
134
|
+
A recall-hit means the system found the agent's data useful.
|
|
135
|
+
"""
|
|
136
|
+
return self.record_signal(agent_id, profile_id, "recall_hit")
|
|
137
|
+
|
|
138
|
+
# ------------------------------------------------------------------
|
|
139
|
+
# Public API: direct set (testing & migration)
|
|
140
|
+
# ------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
def set_trust(
|
|
143
|
+
self,
|
|
144
|
+
agent_id: str,
|
|
145
|
+
profile_id: str,
|
|
146
|
+
alpha: float = _DEFAULT_ALPHA,
|
|
147
|
+
beta_param: float = _DEFAULT_BETA,
|
|
148
|
+
) -> float:
|
|
149
|
+
"""Set trust directly via Beta params. Primarily for testing."""
|
|
150
|
+
alpha = max(0.01, alpha)
|
|
151
|
+
beta_param = max(0.01, beta_param)
|
|
152
|
+
self._persist_beta("agent", agent_id, profile_id, alpha, beta_param)
|
|
153
|
+
return self._compute_trust(alpha, beta_param)
|
|
154
|
+
|
|
155
|
+
# ------------------------------------------------------------------
|
|
156
|
+
# Public API: bulk query
|
|
157
|
+
# ------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
def get_all_scores(self, profile_id: str) -> list[dict]:
|
|
160
|
+
"""Get all trust scores for a profile as dicts.
|
|
161
|
+
|
|
162
|
+
Returns list of {target_type, target_id, trust_score,
|
|
163
|
+
alpha, beta_param, evidence_count, last_updated}.
|
|
164
|
+
"""
|
|
165
|
+
rows = self._db.execute(
|
|
166
|
+
"SELECT * FROM trust_scores WHERE profile_id = ?",
|
|
167
|
+
(profile_id,),
|
|
168
|
+
)
|
|
169
|
+
results: list[dict] = []
|
|
170
|
+
for r in rows:
|
|
171
|
+
d = dict(r)
|
|
172
|
+
alpha, beta = self._decode_beta(
|
|
173
|
+
d["trust_score"], d["evidence_count"]
|
|
174
|
+
)
|
|
175
|
+
results.append({
|
|
176
|
+
"trust_id": d["trust_id"],
|
|
177
|
+
"target_type": d["target_type"],
|
|
178
|
+
"target_id": d["target_id"],
|
|
179
|
+
"trust_score": d["trust_score"],
|
|
180
|
+
"alpha": alpha,
|
|
181
|
+
"beta_param": beta,
|
|
182
|
+
"evidence_count": d["evidence_count"],
|
|
183
|
+
"last_updated": d["last_updated"],
|
|
184
|
+
})
|
|
185
|
+
return results
|
|
186
|
+
|
|
187
|
+
# ------------------------------------------------------------------
|
|
188
|
+
# Backward-compatible delegators
|
|
189
|
+
# ------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
def update_on_confirmation(
|
|
192
|
+
self, target_type: str, target_id: str, profile_id: str
|
|
193
|
+
) -> float:
|
|
194
|
+
"""V3-compat: confirmation -> store_success signal."""
|
|
195
|
+
alpha, beta = self._get_beta_params(target_type, target_id, profile_id)
|
|
196
|
+
new_alpha = alpha + 1.0
|
|
197
|
+
self._persist_beta(target_type, target_id, profile_id, new_alpha, beta)
|
|
198
|
+
return self._compute_trust(new_alpha, beta)
|
|
199
|
+
|
|
200
|
+
def update_on_contradiction(
|
|
201
|
+
self, target_type: str, target_id: str, profile_id: str
|
|
202
|
+
) -> float:
|
|
203
|
+
"""V3-compat: contradiction -> contradiction signal."""
|
|
204
|
+
alpha, beta = self._get_beta_params(target_type, target_id, profile_id)
|
|
205
|
+
new_beta = beta + 3.0
|
|
206
|
+
self._persist_beta(target_type, target_id, profile_id, alpha, new_beta)
|
|
207
|
+
return self._compute_trust(alpha, new_beta)
|
|
208
|
+
|
|
209
|
+
def update_on_access(
|
|
210
|
+
self, target_type: str, target_id: str, profile_id: str
|
|
211
|
+
) -> float:
|
|
212
|
+
"""V3-compat: access -> recall_hit signal (small boost)."""
|
|
213
|
+
alpha, beta = self._get_beta_params(target_type, target_id, profile_id)
|
|
214
|
+
new_alpha = alpha + 0.5
|
|
215
|
+
self._persist_beta(
|
|
216
|
+
target_type, target_id, profile_id, new_alpha, beta
|
|
217
|
+
)
|
|
218
|
+
return self._compute_trust(new_alpha, beta)
|
|
219
|
+
|
|
220
|
+
# ------------------------------------------------------------------
|
|
221
|
+
# Prior decay
|
|
222
|
+
# ------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
def apply_prior_decay(self, profile_id: str) -> int:
|
|
225
|
+
"""Decay idle trust scores toward uniform prior Beta(1,1).
|
|
226
|
+
|
|
227
|
+
Called periodically (e.g., daily maintenance).
|
|
228
|
+
Returns number of scores decayed.
|
|
229
|
+
"""
|
|
230
|
+
cutoff = datetime.now(UTC).isoformat()
|
|
231
|
+
rows = self._db.execute(
|
|
232
|
+
"SELECT * FROM trust_scores WHERE profile_id = ?",
|
|
233
|
+
(profile_id,),
|
|
234
|
+
)
|
|
235
|
+
decayed = 0
|
|
236
|
+
for r in rows:
|
|
237
|
+
d = dict(r)
|
|
238
|
+
last = d.get("last_updated", "")
|
|
239
|
+
if not last:
|
|
240
|
+
continue
|
|
241
|
+
try:
|
|
242
|
+
last_dt = datetime.fromisoformat(last)
|
|
243
|
+
idle_days = (
|
|
244
|
+
datetime.now(UTC) - last_dt.replace(tzinfo=UTC)
|
|
245
|
+
).days
|
|
246
|
+
except (ValueError, TypeError):
|
|
247
|
+
continue
|
|
248
|
+
|
|
249
|
+
if idle_days < _DECAY_IDLE_DAYS:
|
|
250
|
+
continue
|
|
251
|
+
|
|
252
|
+
alpha, beta = self._decode_beta(
|
|
253
|
+
d["trust_score"], d["evidence_count"]
|
|
254
|
+
)
|
|
255
|
+
excess_days = idle_days - _DECAY_IDLE_DAYS
|
|
256
|
+
decay_factor = max(0.0, 1.0 - _DECAY_RATE * excess_days)
|
|
257
|
+
|
|
258
|
+
new_alpha = _DEFAULT_ALPHA + (alpha - _DEFAULT_ALPHA) * decay_factor
|
|
259
|
+
new_beta = _DEFAULT_BETA + (beta - _DEFAULT_BETA) * decay_factor
|
|
260
|
+
|
|
261
|
+
new_alpha = max(0.01, new_alpha)
|
|
262
|
+
new_beta = max(0.01, new_beta)
|
|
263
|
+
|
|
264
|
+
score = self._compute_trust(new_alpha, new_beta)
|
|
265
|
+
ev = max(0, round(new_alpha + new_beta - 2))
|
|
266
|
+
self._db.execute(
|
|
267
|
+
"UPDATE trust_scores SET trust_score = ?, evidence_count = ?, "
|
|
268
|
+
"last_updated = ? WHERE trust_id = ?",
|
|
269
|
+
(score, ev, cutoff, d["trust_id"]),
|
|
270
|
+
)
|
|
271
|
+
decayed += 1
|
|
272
|
+
return decayed
|
|
273
|
+
|
|
274
|
+
# ------------------------------------------------------------------
|
|
275
|
+
# Internal helpers
|
|
276
|
+
# ------------------------------------------------------------------
|
|
277
|
+
|
|
278
|
+
@staticmethod
|
|
279
|
+
def _compute_trust(alpha: float, beta: float) -> float:
|
|
280
|
+
"""Trust = alpha / (alpha + beta), clamped to [0, 1]."""
|
|
281
|
+
total = alpha + beta
|
|
282
|
+
if total <= 0:
|
|
283
|
+
return _DEFAULT_TRUST
|
|
284
|
+
return max(0.0, min(1.0, alpha / total))
|
|
285
|
+
|
|
286
|
+
@staticmethod
|
|
287
|
+
def _decode_beta(
|
|
288
|
+
trust_score: float, evidence_count: int
|
|
289
|
+
) -> tuple[float, float]:
|
|
290
|
+
"""Reconstruct Beta params from encoded trust_score + evidence_count.
|
|
291
|
+
|
|
292
|
+
Encoding: trust_score = a/(a+b), evidence_count = round(a+b-2).
|
|
293
|
+
"""
|
|
294
|
+
total = float(evidence_count) + 2.0
|
|
295
|
+
alpha = trust_score * total
|
|
296
|
+
beta = (1.0 - trust_score) * total
|
|
297
|
+
return max(0.01, alpha), max(0.01, beta)
|
|
298
|
+
|
|
299
|
+
def _get_beta_params(
|
|
300
|
+
self, target_type: str, target_id: str, profile_id: str
|
|
301
|
+
) -> tuple[float, float]:
|
|
302
|
+
"""Load Beta(alpha, beta) from DB, or return default prior."""
|
|
303
|
+
rows = self._db.execute(
|
|
304
|
+
"SELECT trust_score, evidence_count FROM trust_scores "
|
|
305
|
+
"WHERE target_type = ? AND target_id = ? AND profile_id = ?",
|
|
306
|
+
(target_type, target_id, profile_id),
|
|
307
|
+
)
|
|
308
|
+
if rows:
|
|
309
|
+
d = dict(rows[0])
|
|
310
|
+
return self._decode_beta(d["trust_score"], d["evidence_count"])
|
|
311
|
+
return _DEFAULT_ALPHA, _DEFAULT_BETA
|
|
312
|
+
|
|
313
|
+
def _persist_beta(
|
|
314
|
+
self,
|
|
315
|
+
target_type: str,
|
|
316
|
+
target_id: str,
|
|
317
|
+
profile_id: str,
|
|
318
|
+
alpha: float,
|
|
319
|
+
beta: float,
|
|
320
|
+
) -> None:
|
|
321
|
+
"""Encode and persist Beta params into existing schema."""
|
|
322
|
+
score = self._compute_trust(alpha, beta)
|
|
323
|
+
ev = max(0, round(alpha + beta - 2))
|
|
324
|
+
now = datetime.now(UTC).isoformat()
|
|
325
|
+
|
|
326
|
+
existing = self._db.execute(
|
|
327
|
+
"SELECT trust_id FROM trust_scores "
|
|
328
|
+
"WHERE target_type = ? AND target_id = ? AND profile_id = ?",
|
|
329
|
+
(target_type, target_id, profile_id),
|
|
330
|
+
)
|
|
331
|
+
if existing:
|
|
332
|
+
tid = dict(existing[0])["trust_id"]
|
|
333
|
+
self._db.execute(
|
|
334
|
+
"UPDATE trust_scores SET trust_score = ?, evidence_count = ?, "
|
|
335
|
+
"last_updated = ? WHERE trust_id = ?",
|
|
336
|
+
(score, ev, now, tid),
|
|
337
|
+
)
|
|
338
|
+
else:
|
|
339
|
+
from superlocalmemory.storage.models import _new_id
|
|
340
|
+
|
|
341
|
+
tid = _new_id()
|
|
342
|
+
self._db.execute(
|
|
343
|
+
"INSERT INTO trust_scores "
|
|
344
|
+
"(trust_id, profile_id, target_type, target_id, trust_score, "
|
|
345
|
+
"evidence_count, last_updated) VALUES (?,?,?,?,?,?,?)",
|
|
346
|
+
(tid, profile_id, target_type, target_id, score, ev, now),
|
|
347
|
+
)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""SuperLocalMemory V3 — Trust Signal Recorder with Burst Detection.
|
|
6
|
+
|
|
7
|
+
Records trust signals and detects anomalous write patterns.
|
|
8
|
+
Burst detection uses an in-memory sliding window (collections.deque)
|
|
9
|
+
per agent. Signals are published via the event bus rather than a
|
|
10
|
+
separate table, keeping the storage footprint minimal.
|
|
11
|
+
|
|
12
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
import time
|
|
19
|
+
from collections import defaultdict, deque
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
# Valid signal types (must match scorer._SIGNAL_WEIGHTS keys)
|
|
25
|
+
VALID_SIGNAL_TYPES = frozenset({
|
|
26
|
+
"store_success",
|
|
27
|
+
"store_rejected",
|
|
28
|
+
"recall_hit",
|
|
29
|
+
"contradiction",
|
|
30
|
+
"deletion",
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SignalRecorder:
|
|
35
|
+
"""Records trust signals and detects anomalous write patterns.
|
|
36
|
+
|
|
37
|
+
Burst detection: tracks write timestamps in a per-agent deque.
|
|
38
|
+
If > threshold writes occur within the window, burst is flagged.
|
|
39
|
+
Burst-flagged signals are still recorded but logged as anomalous.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
db: Any,
|
|
45
|
+
burst_window_seconds: int = 60,
|
|
46
|
+
burst_threshold: int = 20,
|
|
47
|
+
) -> None:
|
|
48
|
+
self._db = db
|
|
49
|
+
self._burst_window = burst_window_seconds
|
|
50
|
+
self._burst_threshold = burst_threshold
|
|
51
|
+
|
|
52
|
+
# Per-agent sliding window: key = (agent_id, profile_id)
|
|
53
|
+
# Value = deque of timestamps (float, time.monotonic)
|
|
54
|
+
self._windows: dict[tuple[str, str], deque[float]] = defaultdict(
|
|
55
|
+
lambda: deque(maxlen=max(1, burst_threshold * 2))
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# In-memory signal log for get_recent_signals
|
|
59
|
+
self._recent: dict[tuple[str, str], deque[dict]] = defaultdict(
|
|
60
|
+
lambda: deque(maxlen=200)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def record(
|
|
64
|
+
self,
|
|
65
|
+
agent_id: str,
|
|
66
|
+
profile_id: str,
|
|
67
|
+
signal_type: str,
|
|
68
|
+
context: dict[str, Any] | None = None,
|
|
69
|
+
) -> bool:
|
|
70
|
+
"""Record a trust signal.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
agent_id: The agent generating the signal.
|
|
74
|
+
profile_id: Active profile scope.
|
|
75
|
+
signal_type: One of VALID_SIGNAL_TYPES.
|
|
76
|
+
context: Optional metadata about the signal.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
True if signal was accepted (even during burst).
|
|
80
|
+
False if signal_type is invalid.
|
|
81
|
+
"""
|
|
82
|
+
if signal_type not in VALID_SIGNAL_TYPES:
|
|
83
|
+
logger.warning("invalid signal type: %s", signal_type)
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
now = time.monotonic()
|
|
87
|
+
key = (agent_id, profile_id)
|
|
88
|
+
|
|
89
|
+
# Update sliding window
|
|
90
|
+
window = self._windows[key]
|
|
91
|
+
window.append(now)
|
|
92
|
+
self._prune_window(window, now)
|
|
93
|
+
|
|
94
|
+
# Check burst before recording
|
|
95
|
+
is_burst = len(window) >= self._burst_threshold
|
|
96
|
+
if is_burst:
|
|
97
|
+
logger.warning(
|
|
98
|
+
"burst detected: agent=%s profile=%s count=%d in %ds",
|
|
99
|
+
agent_id, profile_id, len(window), self._burst_window,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Record signal in memory log
|
|
103
|
+
entry = {
|
|
104
|
+
"agent_id": agent_id,
|
|
105
|
+
"profile_id": profile_id,
|
|
106
|
+
"signal_type": signal_type,
|
|
107
|
+
"timestamp": now,
|
|
108
|
+
"burst_flagged": is_burst,
|
|
109
|
+
"context": context or {},
|
|
110
|
+
}
|
|
111
|
+
self._recent[key].append(entry)
|
|
112
|
+
|
|
113
|
+
return True
|
|
114
|
+
|
|
115
|
+
def is_burst_detected(self, agent_id: str, profile_id: str) -> bool:
|
|
116
|
+
"""True if agent has exceeded burst_threshold writes in window."""
|
|
117
|
+
key = (agent_id, profile_id)
|
|
118
|
+
window = self._windows.get(key)
|
|
119
|
+
if window is None:
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
now = time.monotonic()
|
|
123
|
+
self._prune_window(window, now)
|
|
124
|
+
return len(window) >= self._burst_threshold
|
|
125
|
+
|
|
126
|
+
def get_recent_signals(
|
|
127
|
+
self, agent_id: str, profile_id: str, limit: int = 50
|
|
128
|
+
) -> list[dict]:
|
|
129
|
+
"""Get recent signals for an agent, most recent first."""
|
|
130
|
+
key = (agent_id, profile_id)
|
|
131
|
+
signals = list(self._recent.get(key, []))
|
|
132
|
+
signals.reverse()
|
|
133
|
+
return signals[:limit]
|
|
134
|
+
|
|
135
|
+
def get_burst_status(self, profile_id: str) -> dict[str, bool]:
|
|
136
|
+
"""Get burst status for all agents in a profile.
|
|
137
|
+
|
|
138
|
+
Returns dict mapping agent_id -> is_bursting.
|
|
139
|
+
"""
|
|
140
|
+
result: dict[str, bool] = {}
|
|
141
|
+
now = time.monotonic()
|
|
142
|
+
for (aid, pid), window in self._windows.items():
|
|
143
|
+
if pid != profile_id:
|
|
144
|
+
continue
|
|
145
|
+
self._prune_window(window, now)
|
|
146
|
+
result[aid] = len(window) >= self._burst_threshold
|
|
147
|
+
return result
|
|
148
|
+
|
|
149
|
+
def _prune_window(self, window: deque[float], now: float) -> None:
|
|
150
|
+
"""Remove timestamps older than the burst window."""
|
|
151
|
+
cutoff = now - self._burst_window
|
|
152
|
+
while window and window[0] < cutoff:
|
|
153
|
+
window.popleft()
|