attune-ai 2.0.0__py3-none-any.whl
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.
- attune/__init__.py +358 -0
- attune/adaptive/__init__.py +13 -0
- attune/adaptive/task_complexity.py +127 -0
- attune/agent_monitoring.py +414 -0
- attune/cache/__init__.py +117 -0
- attune/cache/base.py +166 -0
- attune/cache/dependency_manager.py +256 -0
- attune/cache/hash_only.py +251 -0
- attune/cache/hybrid.py +457 -0
- attune/cache/storage.py +285 -0
- attune/cache_monitor.py +356 -0
- attune/cache_stats.py +298 -0
- attune/cli/__init__.py +152 -0
- attune/cli/__main__.py +12 -0
- attune/cli/commands/__init__.py +1 -0
- attune/cli/commands/batch.py +264 -0
- attune/cli/commands/cache.py +248 -0
- attune/cli/commands/help.py +331 -0
- attune/cli/commands/info.py +140 -0
- attune/cli/commands/inspect.py +436 -0
- attune/cli/commands/inspection.py +57 -0
- attune/cli/commands/memory.py +48 -0
- attune/cli/commands/metrics.py +92 -0
- attune/cli/commands/orchestrate.py +184 -0
- attune/cli/commands/patterns.py +207 -0
- attune/cli/commands/profiling.py +202 -0
- attune/cli/commands/provider.py +98 -0
- attune/cli/commands/routing.py +285 -0
- attune/cli/commands/setup.py +96 -0
- attune/cli/commands/status.py +235 -0
- attune/cli/commands/sync.py +166 -0
- attune/cli/commands/tier.py +121 -0
- attune/cli/commands/utilities.py +114 -0
- attune/cli/commands/workflow.py +579 -0
- attune/cli/core.py +32 -0
- attune/cli/parsers/__init__.py +68 -0
- attune/cli/parsers/batch.py +118 -0
- attune/cli/parsers/cache.py +65 -0
- attune/cli/parsers/help.py +41 -0
- attune/cli/parsers/info.py +26 -0
- attune/cli/parsers/inspect.py +66 -0
- attune/cli/parsers/metrics.py +42 -0
- attune/cli/parsers/orchestrate.py +61 -0
- attune/cli/parsers/patterns.py +54 -0
- attune/cli/parsers/provider.py +40 -0
- attune/cli/parsers/routing.py +110 -0
- attune/cli/parsers/setup.py +42 -0
- attune/cli/parsers/status.py +47 -0
- attune/cli/parsers/sync.py +31 -0
- attune/cli/parsers/tier.py +33 -0
- attune/cli/parsers/workflow.py +77 -0
- attune/cli/utils/__init__.py +1 -0
- attune/cli/utils/data.py +242 -0
- attune/cli/utils/helpers.py +68 -0
- attune/cli_legacy.py +3957 -0
- attune/cli_minimal.py +1159 -0
- attune/cli_router.py +437 -0
- attune/cli_unified.py +814 -0
- attune/config/__init__.py +66 -0
- attune/config/xml_config.py +286 -0
- attune/config.py +545 -0
- attune/coordination.py +870 -0
- attune/core.py +1511 -0
- attune/core_modules/__init__.py +15 -0
- attune/cost_tracker.py +626 -0
- attune/dashboard/__init__.py +41 -0
- attune/dashboard/app.py +512 -0
- attune/dashboard/simple_server.py +435 -0
- attune/dashboard/standalone_server.py +547 -0
- attune/discovery.py +306 -0
- attune/emergence.py +306 -0
- attune/exceptions.py +123 -0
- attune/feedback_loops.py +373 -0
- attune/hot_reload/README.md +473 -0
- attune/hot_reload/__init__.py +62 -0
- attune/hot_reload/config.py +83 -0
- attune/hot_reload/integration.py +229 -0
- attune/hot_reload/reloader.py +298 -0
- attune/hot_reload/watcher.py +183 -0
- attune/hot_reload/websocket.py +177 -0
- attune/levels.py +577 -0
- attune/leverage_points.py +441 -0
- attune/logging_config.py +261 -0
- attune/mcp/__init__.py +10 -0
- attune/mcp/server.py +506 -0
- attune/memory/__init__.py +237 -0
- attune/memory/claude_memory.py +469 -0
- attune/memory/config.py +224 -0
- attune/memory/control_panel.py +1290 -0
- attune/memory/control_panel_support.py +145 -0
- attune/memory/cross_session.py +845 -0
- attune/memory/edges.py +179 -0
- attune/memory/encryption.py +159 -0
- attune/memory/file_session.py +770 -0
- attune/memory/graph.py +570 -0
- attune/memory/long_term.py +913 -0
- attune/memory/long_term_types.py +99 -0
- attune/memory/mixins/__init__.py +25 -0
- attune/memory/mixins/backend_init_mixin.py +249 -0
- attune/memory/mixins/capabilities_mixin.py +208 -0
- attune/memory/mixins/handoff_mixin.py +208 -0
- attune/memory/mixins/lifecycle_mixin.py +49 -0
- attune/memory/mixins/long_term_mixin.py +352 -0
- attune/memory/mixins/promotion_mixin.py +109 -0
- attune/memory/mixins/short_term_mixin.py +182 -0
- attune/memory/nodes.py +179 -0
- attune/memory/redis_bootstrap.py +540 -0
- attune/memory/security/__init__.py +31 -0
- attune/memory/security/audit_logger.py +932 -0
- attune/memory/security/pii_scrubber.py +640 -0
- attune/memory/security/secrets_detector.py +678 -0
- attune/memory/short_term.py +2192 -0
- attune/memory/simple_storage.py +302 -0
- attune/memory/storage/__init__.py +15 -0
- attune/memory/storage_backend.py +167 -0
- attune/memory/summary_index.py +583 -0
- attune/memory/types.py +446 -0
- attune/memory/unified.py +182 -0
- attune/meta_workflows/__init__.py +74 -0
- attune/meta_workflows/agent_creator.py +248 -0
- attune/meta_workflows/builtin_templates.py +567 -0
- attune/meta_workflows/cli_commands/__init__.py +56 -0
- attune/meta_workflows/cli_commands/agent_commands.py +321 -0
- attune/meta_workflows/cli_commands/analytics_commands.py +442 -0
- attune/meta_workflows/cli_commands/config_commands.py +232 -0
- attune/meta_workflows/cli_commands/memory_commands.py +182 -0
- attune/meta_workflows/cli_commands/template_commands.py +354 -0
- attune/meta_workflows/cli_commands/workflow_commands.py +382 -0
- attune/meta_workflows/cli_meta_workflows.py +59 -0
- attune/meta_workflows/form_engine.py +292 -0
- attune/meta_workflows/intent_detector.py +409 -0
- attune/meta_workflows/models.py +569 -0
- attune/meta_workflows/pattern_learner.py +738 -0
- attune/meta_workflows/plan_generator.py +384 -0
- attune/meta_workflows/session_context.py +397 -0
- attune/meta_workflows/template_registry.py +229 -0
- attune/meta_workflows/workflow.py +984 -0
- attune/metrics/__init__.py +12 -0
- attune/metrics/collector.py +31 -0
- attune/metrics/prompt_metrics.py +194 -0
- attune/models/__init__.py +172 -0
- attune/models/__main__.py +13 -0
- attune/models/adaptive_routing.py +437 -0
- attune/models/auth_cli.py +444 -0
- attune/models/auth_strategy.py +450 -0
- attune/models/cli.py +655 -0
- attune/models/empathy_executor.py +354 -0
- attune/models/executor.py +257 -0
- attune/models/fallback.py +762 -0
- attune/models/provider_config.py +282 -0
- attune/models/registry.py +472 -0
- attune/models/tasks.py +359 -0
- attune/models/telemetry/__init__.py +71 -0
- attune/models/telemetry/analytics.py +594 -0
- attune/models/telemetry/backend.py +196 -0
- attune/models/telemetry/data_models.py +431 -0
- attune/models/telemetry/storage.py +489 -0
- attune/models/token_estimator.py +420 -0
- attune/models/validation.py +280 -0
- attune/monitoring/__init__.py +52 -0
- attune/monitoring/alerts.py +946 -0
- attune/monitoring/alerts_cli.py +448 -0
- attune/monitoring/multi_backend.py +271 -0
- attune/monitoring/otel_backend.py +362 -0
- attune/optimization/__init__.py +19 -0
- attune/optimization/context_optimizer.py +272 -0
- attune/orchestration/__init__.py +67 -0
- attune/orchestration/agent_templates.py +707 -0
- attune/orchestration/config_store.py +499 -0
- attune/orchestration/execution_strategies.py +2111 -0
- attune/orchestration/meta_orchestrator.py +1168 -0
- attune/orchestration/pattern_learner.py +696 -0
- attune/orchestration/real_tools.py +931 -0
- attune/pattern_cache.py +187 -0
- attune/pattern_library.py +542 -0
- attune/patterns/debugging/all_patterns.json +81 -0
- attune/patterns/debugging/workflow_20260107_1770825e.json +77 -0
- attune/patterns/refactoring_memory.json +89 -0
- attune/persistence.py +564 -0
- attune/platform_utils.py +265 -0
- attune/plugins/__init__.py +28 -0
- attune/plugins/base.py +361 -0
- attune/plugins/registry.py +268 -0
- attune/project_index/__init__.py +32 -0
- attune/project_index/cli.py +335 -0
- attune/project_index/index.py +667 -0
- attune/project_index/models.py +504 -0
- attune/project_index/reports.py +474 -0
- attune/project_index/scanner.py +777 -0
- attune/project_index/scanner_parallel.py +291 -0
- attune/prompts/__init__.py +61 -0
- attune/prompts/config.py +77 -0
- attune/prompts/context.py +177 -0
- attune/prompts/parser.py +285 -0
- attune/prompts/registry.py +313 -0
- attune/prompts/templates.py +208 -0
- attune/redis_config.py +302 -0
- attune/redis_memory.py +799 -0
- attune/resilience/__init__.py +56 -0
- attune/resilience/circuit_breaker.py +256 -0
- attune/resilience/fallback.py +179 -0
- attune/resilience/health.py +300 -0
- attune/resilience/retry.py +209 -0
- attune/resilience/timeout.py +135 -0
- attune/routing/__init__.py +43 -0
- attune/routing/chain_executor.py +433 -0
- attune/routing/classifier.py +217 -0
- attune/routing/smart_router.py +234 -0
- attune/routing/workflow_registry.py +343 -0
- attune/scaffolding/README.md +589 -0
- attune/scaffolding/__init__.py +35 -0
- attune/scaffolding/__main__.py +14 -0
- attune/scaffolding/cli.py +240 -0
- attune/scaffolding/templates/base_wizard.py.jinja2 +121 -0
- attune/scaffolding/templates/coach_wizard.py.jinja2 +321 -0
- attune/scaffolding/templates/domain_wizard.py.jinja2 +408 -0
- attune/scaffolding/templates/linear_flow_wizard.py.jinja2 +203 -0
- attune/socratic/__init__.py +256 -0
- attune/socratic/ab_testing.py +958 -0
- attune/socratic/blueprint.py +533 -0
- attune/socratic/cli.py +703 -0
- attune/socratic/collaboration.py +1114 -0
- attune/socratic/domain_templates.py +924 -0
- attune/socratic/embeddings.py +738 -0
- attune/socratic/engine.py +794 -0
- attune/socratic/explainer.py +682 -0
- attune/socratic/feedback.py +772 -0
- attune/socratic/forms.py +629 -0
- attune/socratic/generator.py +732 -0
- attune/socratic/llm_analyzer.py +637 -0
- attune/socratic/mcp_server.py +702 -0
- attune/socratic/session.py +312 -0
- attune/socratic/storage.py +667 -0
- attune/socratic/success.py +730 -0
- attune/socratic/visual_editor.py +860 -0
- attune/socratic/web_ui.py +958 -0
- attune/telemetry/__init__.py +39 -0
- attune/telemetry/agent_coordination.py +475 -0
- attune/telemetry/agent_tracking.py +367 -0
- attune/telemetry/approval_gates.py +545 -0
- attune/telemetry/cli.py +1231 -0
- attune/telemetry/commands/__init__.py +14 -0
- attune/telemetry/commands/dashboard_commands.py +696 -0
- attune/telemetry/event_streaming.py +409 -0
- attune/telemetry/feedback_loop.py +567 -0
- attune/telemetry/usage_tracker.py +591 -0
- attune/templates.py +754 -0
- attune/test_generator/__init__.py +38 -0
- attune/test_generator/__main__.py +14 -0
- attune/test_generator/cli.py +234 -0
- attune/test_generator/generator.py +355 -0
- attune/test_generator/risk_analyzer.py +216 -0
- attune/test_generator/templates/unit_test.py.jinja2 +272 -0
- attune/tier_recommender.py +384 -0
- attune/tools.py +183 -0
- attune/trust/__init__.py +28 -0
- attune/trust/circuit_breaker.py +579 -0
- attune/trust_building.py +527 -0
- attune/validation/__init__.py +19 -0
- attune/validation/xml_validator.py +281 -0
- attune/vscode_bridge.py +173 -0
- attune/workflow_commands.py +780 -0
- attune/workflow_patterns/__init__.py +33 -0
- attune/workflow_patterns/behavior.py +249 -0
- attune/workflow_patterns/core.py +76 -0
- attune/workflow_patterns/output.py +99 -0
- attune/workflow_patterns/registry.py +255 -0
- attune/workflow_patterns/structural.py +288 -0
- attune/workflows/__init__.py +539 -0
- attune/workflows/autonomous_test_gen.py +1268 -0
- attune/workflows/base.py +2667 -0
- attune/workflows/batch_processing.py +342 -0
- attune/workflows/bug_predict.py +1084 -0
- attune/workflows/builder.py +273 -0
- attune/workflows/caching.py +253 -0
- attune/workflows/code_review.py +1048 -0
- attune/workflows/code_review_adapters.py +312 -0
- attune/workflows/code_review_pipeline.py +722 -0
- attune/workflows/config.py +645 -0
- attune/workflows/dependency_check.py +644 -0
- attune/workflows/document_gen/__init__.py +25 -0
- attune/workflows/document_gen/config.py +30 -0
- attune/workflows/document_gen/report_formatter.py +162 -0
- attune/workflows/document_gen/workflow.py +1426 -0
- attune/workflows/document_manager.py +216 -0
- attune/workflows/document_manager_README.md +134 -0
- attune/workflows/documentation_orchestrator.py +1205 -0
- attune/workflows/history.py +510 -0
- attune/workflows/keyboard_shortcuts/__init__.py +39 -0
- attune/workflows/keyboard_shortcuts/generators.py +391 -0
- attune/workflows/keyboard_shortcuts/parsers.py +416 -0
- attune/workflows/keyboard_shortcuts/prompts.py +295 -0
- attune/workflows/keyboard_shortcuts/schema.py +193 -0
- attune/workflows/keyboard_shortcuts/workflow.py +509 -0
- attune/workflows/llm_base.py +363 -0
- attune/workflows/manage_docs.py +87 -0
- attune/workflows/manage_docs_README.md +134 -0
- attune/workflows/manage_documentation.py +821 -0
- attune/workflows/new_sample_workflow1.py +149 -0
- attune/workflows/new_sample_workflow1_README.md +150 -0
- attune/workflows/orchestrated_health_check.py +849 -0
- attune/workflows/orchestrated_release_prep.py +600 -0
- attune/workflows/output.py +413 -0
- attune/workflows/perf_audit.py +863 -0
- attune/workflows/pr_review.py +762 -0
- attune/workflows/progress.py +785 -0
- attune/workflows/progress_server.py +322 -0
- attune/workflows/progressive/README 2.md +454 -0
- attune/workflows/progressive/README.md +454 -0
- attune/workflows/progressive/__init__.py +82 -0
- attune/workflows/progressive/cli.py +219 -0
- attune/workflows/progressive/core.py +488 -0
- attune/workflows/progressive/orchestrator.py +723 -0
- attune/workflows/progressive/reports.py +520 -0
- attune/workflows/progressive/telemetry.py +274 -0
- attune/workflows/progressive/test_gen.py +495 -0
- attune/workflows/progressive/workflow.py +589 -0
- attune/workflows/refactor_plan.py +694 -0
- attune/workflows/release_prep.py +895 -0
- attune/workflows/release_prep_crew.py +969 -0
- attune/workflows/research_synthesis.py +404 -0
- attune/workflows/routing.py +168 -0
- attune/workflows/secure_release.py +593 -0
- attune/workflows/security_adapters.py +297 -0
- attune/workflows/security_audit.py +1329 -0
- attune/workflows/security_audit_phase3.py +355 -0
- attune/workflows/seo_optimization.py +633 -0
- attune/workflows/step_config.py +234 -0
- attune/workflows/telemetry_mixin.py +269 -0
- attune/workflows/test5.py +125 -0
- attune/workflows/test5_README.md +158 -0
- attune/workflows/test_coverage_boost_crew.py +849 -0
- attune/workflows/test_gen/__init__.py +52 -0
- attune/workflows/test_gen/ast_analyzer.py +249 -0
- attune/workflows/test_gen/config.py +88 -0
- attune/workflows/test_gen/data_models.py +38 -0
- attune/workflows/test_gen/report_formatter.py +289 -0
- attune/workflows/test_gen/test_templates.py +381 -0
- attune/workflows/test_gen/workflow.py +655 -0
- attune/workflows/test_gen.py +54 -0
- attune/workflows/test_gen_behavioral.py +477 -0
- attune/workflows/test_gen_parallel.py +341 -0
- attune/workflows/test_lifecycle.py +526 -0
- attune/workflows/test_maintenance.py +627 -0
- attune/workflows/test_maintenance_cli.py +590 -0
- attune/workflows/test_maintenance_crew.py +840 -0
- attune/workflows/test_runner.py +622 -0
- attune/workflows/tier_tracking.py +531 -0
- attune/workflows/xml_enhanced_crew.py +285 -0
- attune_ai-2.0.0.dist-info/METADATA +1026 -0
- attune_ai-2.0.0.dist-info/RECORD +457 -0
- attune_ai-2.0.0.dist-info/WHEEL +5 -0
- attune_ai-2.0.0.dist-info/entry_points.txt +26 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE +201 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
- attune_ai-2.0.0.dist-info/top_level.txt +5 -0
- attune_healthcare/__init__.py +13 -0
- attune_healthcare/monitors/__init__.py +9 -0
- attune_healthcare/monitors/clinical_protocol_monitor.py +315 -0
- attune_healthcare/monitors/monitoring/__init__.py +44 -0
- attune_healthcare/monitors/monitoring/protocol_checker.py +300 -0
- attune_healthcare/monitors/monitoring/protocol_loader.py +214 -0
- attune_healthcare/monitors/monitoring/sensor_parsers.py +306 -0
- attune_healthcare/monitors/monitoring/trajectory_analyzer.py +389 -0
- attune_llm/README.md +553 -0
- attune_llm/__init__.py +28 -0
- attune_llm/agent_factory/__init__.py +53 -0
- attune_llm/agent_factory/adapters/__init__.py +85 -0
- attune_llm/agent_factory/adapters/autogen_adapter.py +312 -0
- attune_llm/agent_factory/adapters/crewai_adapter.py +483 -0
- attune_llm/agent_factory/adapters/haystack_adapter.py +298 -0
- attune_llm/agent_factory/adapters/langchain_adapter.py +362 -0
- attune_llm/agent_factory/adapters/langgraph_adapter.py +333 -0
- attune_llm/agent_factory/adapters/native.py +228 -0
- attune_llm/agent_factory/adapters/wizard_adapter.py +423 -0
- attune_llm/agent_factory/base.py +305 -0
- attune_llm/agent_factory/crews/__init__.py +67 -0
- attune_llm/agent_factory/crews/code_review.py +1113 -0
- attune_llm/agent_factory/crews/health_check.py +1262 -0
- attune_llm/agent_factory/crews/refactoring.py +1128 -0
- attune_llm/agent_factory/crews/security_audit.py +1018 -0
- attune_llm/agent_factory/decorators.py +287 -0
- attune_llm/agent_factory/factory.py +558 -0
- attune_llm/agent_factory/framework.py +193 -0
- attune_llm/agent_factory/memory_integration.py +328 -0
- attune_llm/agent_factory/resilient.py +320 -0
- attune_llm/agents_md/__init__.py +22 -0
- attune_llm/agents_md/loader.py +218 -0
- attune_llm/agents_md/parser.py +271 -0
- attune_llm/agents_md/registry.py +307 -0
- attune_llm/claude_memory.py +466 -0
- attune_llm/cli/__init__.py +8 -0
- attune_llm/cli/sync_claude.py +487 -0
- attune_llm/code_health.py +1313 -0
- attune_llm/commands/__init__.py +51 -0
- attune_llm/commands/context.py +375 -0
- attune_llm/commands/loader.py +301 -0
- attune_llm/commands/models.py +231 -0
- attune_llm/commands/parser.py +371 -0
- attune_llm/commands/registry.py +429 -0
- attune_llm/config/__init__.py +29 -0
- attune_llm/config/unified.py +291 -0
- attune_llm/context/__init__.py +22 -0
- attune_llm/context/compaction.py +455 -0
- attune_llm/context/manager.py +434 -0
- attune_llm/contextual_patterns.py +361 -0
- attune_llm/core.py +907 -0
- attune_llm/git_pattern_extractor.py +435 -0
- attune_llm/hooks/__init__.py +24 -0
- attune_llm/hooks/config.py +306 -0
- attune_llm/hooks/executor.py +289 -0
- attune_llm/hooks/registry.py +302 -0
- attune_llm/hooks/scripts/__init__.py +39 -0
- attune_llm/hooks/scripts/evaluate_session.py +201 -0
- attune_llm/hooks/scripts/first_time_init.py +285 -0
- attune_llm/hooks/scripts/pre_compact.py +207 -0
- attune_llm/hooks/scripts/session_end.py +183 -0
- attune_llm/hooks/scripts/session_start.py +163 -0
- attune_llm/hooks/scripts/suggest_compact.py +225 -0
- attune_llm/learning/__init__.py +30 -0
- attune_llm/learning/evaluator.py +438 -0
- attune_llm/learning/extractor.py +514 -0
- attune_llm/learning/storage.py +560 -0
- attune_llm/levels.py +227 -0
- attune_llm/pattern_confidence.py +414 -0
- attune_llm/pattern_resolver.py +272 -0
- attune_llm/pattern_summary.py +350 -0
- attune_llm/providers.py +967 -0
- attune_llm/routing/__init__.py +32 -0
- attune_llm/routing/model_router.py +362 -0
- attune_llm/security/IMPLEMENTATION_SUMMARY.md +413 -0
- attune_llm/security/PHASE2_COMPLETE.md +384 -0
- attune_llm/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
- attune_llm/security/QUICK_REFERENCE.md +316 -0
- attune_llm/security/README.md +262 -0
- attune_llm/security/__init__.py +62 -0
- attune_llm/security/audit_logger.py +929 -0
- attune_llm/security/audit_logger_example.py +152 -0
- attune_llm/security/pii_scrubber.py +640 -0
- attune_llm/security/secrets_detector.py +678 -0
- attune_llm/security/secrets_detector_example.py +304 -0
- attune_llm/security/secure_memdocs.py +1192 -0
- attune_llm/security/secure_memdocs_example.py +278 -0
- attune_llm/session_status.py +745 -0
- attune_llm/state.py +246 -0
- attune_llm/utils/__init__.py +5 -0
- attune_llm/utils/tokens.py +349 -0
- attune_software/SOFTWARE_PLUGIN_README.md +57 -0
- attune_software/__init__.py +13 -0
- attune_software/cli/__init__.py +120 -0
- attune_software/cli/inspect.py +362 -0
- attune_software/cli.py +574 -0
- attune_software/plugin.py +188 -0
- workflow_scaffolding/__init__.py +11 -0
- workflow_scaffolding/__main__.py +12 -0
- workflow_scaffolding/cli.py +206 -0
- workflow_scaffolding/generator.py +265 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Telemetry tracking for Empathy Framework.
|
|
2
|
+
|
|
3
|
+
Privacy-first, local-only usage tracking to measure actual cost savings.
|
|
4
|
+
|
|
5
|
+
Includes:
|
|
6
|
+
- UsageTracker: Track LLM usage and costs
|
|
7
|
+
- HeartbeatCoordinator: Monitor agent liveness via TTL heartbeats
|
|
8
|
+
- CoordinationSignals: Inter-agent communication via TTL signals
|
|
9
|
+
- EventStreamer: Real-time event streaming via Redis Streams
|
|
10
|
+
- ApprovalGate: Human approval gates for workflow control
|
|
11
|
+
- FeedbackLoop: Agent-to-LLM quality feedback for adaptive routing
|
|
12
|
+
|
|
13
|
+
Copyright 2025 Smart-AI-Memory
|
|
14
|
+
Licensed under Fair Source License 0.9
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from .agent_coordination import CoordinationSignal, CoordinationSignals
|
|
18
|
+
from .agent_tracking import AgentHeartbeat, HeartbeatCoordinator
|
|
19
|
+
from .approval_gates import ApprovalGate, ApprovalRequest, ApprovalResponse
|
|
20
|
+
from .event_streaming import EventStreamer, StreamEvent
|
|
21
|
+
from .feedback_loop import FeedbackEntry, FeedbackLoop, QualityStats, TierRecommendation
|
|
22
|
+
from .usage_tracker import UsageTracker
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"UsageTracker",
|
|
26
|
+
"HeartbeatCoordinator",
|
|
27
|
+
"AgentHeartbeat",
|
|
28
|
+
"CoordinationSignals",
|
|
29
|
+
"CoordinationSignal",
|
|
30
|
+
"EventStreamer",
|
|
31
|
+
"StreamEvent",
|
|
32
|
+
"ApprovalGate",
|
|
33
|
+
"ApprovalRequest",
|
|
34
|
+
"ApprovalResponse",
|
|
35
|
+
"FeedbackLoop",
|
|
36
|
+
"FeedbackEntry",
|
|
37
|
+
"QualityStats",
|
|
38
|
+
"TierRecommendation",
|
|
39
|
+
]
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
"""Agent Coordination via TTL Signals.
|
|
2
|
+
|
|
3
|
+
Pattern 2 from Agent Coordination Architecture - TTL-based inter-agent
|
|
4
|
+
communication for orchestration, synchronization, and coordination.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
# Agent A signals completion
|
|
8
|
+
coordinator = CoordinationSignals()
|
|
9
|
+
coordinator.signal(
|
|
10
|
+
signal_type="task_complete",
|
|
11
|
+
source_agent="agent-a",
|
|
12
|
+
target_agent="agent-b",
|
|
13
|
+
payload={"result": "success", "data": {...}}
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Agent B waits for signal
|
|
17
|
+
signal = coordinator.wait_for_signal(
|
|
18
|
+
signal_type="task_complete",
|
|
19
|
+
source_agent="agent-a",
|
|
20
|
+
timeout=30.0
|
|
21
|
+
)
|
|
22
|
+
if signal:
|
|
23
|
+
process(signal.payload)
|
|
24
|
+
|
|
25
|
+
# Orchestrator broadcasts to all agents
|
|
26
|
+
coordinator.broadcast(
|
|
27
|
+
signal_type="abort",
|
|
28
|
+
source_agent="orchestrator",
|
|
29
|
+
payload={"reason": "user_cancelled"}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
Copyright 2025 Smart-AI-Memory
|
|
33
|
+
Licensed under Fair Source License 0.9
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import logging
|
|
39
|
+
import time
|
|
40
|
+
from dataclasses import dataclass
|
|
41
|
+
from datetime import datetime
|
|
42
|
+
from typing import TYPE_CHECKING, Any
|
|
43
|
+
from uuid import uuid4
|
|
44
|
+
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from attune.memory.types import AgentCredentials
|
|
47
|
+
|
|
48
|
+
logger = logging.getLogger(__name__)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class CoordinationSignal:
|
|
53
|
+
"""Coordination signal between agents.
|
|
54
|
+
|
|
55
|
+
Ephemeral message with TTL, used for agent-to-agent communication.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
signal_id: str
|
|
59
|
+
signal_type: str # "task_complete", "abort", "ready", "checkpoint", etc.
|
|
60
|
+
source_agent: str
|
|
61
|
+
target_agent: str | None # None for broadcast
|
|
62
|
+
payload: dict[str, Any]
|
|
63
|
+
timestamp: datetime
|
|
64
|
+
ttl_seconds: int = 60
|
|
65
|
+
|
|
66
|
+
def to_dict(self) -> dict[str, Any]:
|
|
67
|
+
"""Convert to dictionary for serialization."""
|
|
68
|
+
return {
|
|
69
|
+
"signal_id": self.signal_id,
|
|
70
|
+
"signal_type": self.signal_type,
|
|
71
|
+
"source_agent": self.source_agent,
|
|
72
|
+
"target_agent": self.target_agent,
|
|
73
|
+
"payload": self.payload,
|
|
74
|
+
"timestamp": self.timestamp.isoformat() if isinstance(self.timestamp, datetime) else self.timestamp,
|
|
75
|
+
"ttl_seconds": self.ttl_seconds,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_dict(cls, data: dict[str, Any]) -> CoordinationSignal:
|
|
80
|
+
"""Create from dictionary."""
|
|
81
|
+
timestamp = data.get("timestamp")
|
|
82
|
+
if isinstance(timestamp, str):
|
|
83
|
+
timestamp = datetime.fromisoformat(timestamp)
|
|
84
|
+
elif not isinstance(timestamp, datetime):
|
|
85
|
+
timestamp = datetime.utcnow()
|
|
86
|
+
|
|
87
|
+
return cls(
|
|
88
|
+
signal_id=data["signal_id"],
|
|
89
|
+
signal_type=data["signal_type"],
|
|
90
|
+
source_agent=data["source_agent"],
|
|
91
|
+
target_agent=data.get("target_agent"),
|
|
92
|
+
payload=data.get("payload", {}),
|
|
93
|
+
timestamp=timestamp,
|
|
94
|
+
ttl_seconds=data.get("ttl_seconds", 60),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class CoordinationSignals:
|
|
99
|
+
"""TTL-based inter-agent coordination signals.
|
|
100
|
+
|
|
101
|
+
Agents can:
|
|
102
|
+
- Send signals to specific agents
|
|
103
|
+
- Broadcast signals to all agents
|
|
104
|
+
- Wait for specific signals with timeout
|
|
105
|
+
- Check for pending signals without blocking
|
|
106
|
+
|
|
107
|
+
Signals expire automatically via TTL, preventing stale coordination.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
DEFAULT_TTL = 60 # Default signal TTL: 60 seconds
|
|
111
|
+
BROADCAST_TARGET = "*" # Special target for broadcast signals
|
|
112
|
+
KEY_PREFIX = "empathy:signal:" # Redis key prefix (consistent with framework)
|
|
113
|
+
|
|
114
|
+
def __init__(self, memory=None, agent_id: str | None = None, enable_streaming: bool = False):
|
|
115
|
+
"""Initialize coordination signals.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
memory: Memory instance for storing signals
|
|
119
|
+
agent_id: This agent's ID (for receiving targeted signals)
|
|
120
|
+
enable_streaming: If True, publish signal events to Redis Streams
|
|
121
|
+
for real-time monitoring (Pattern 4).
|
|
122
|
+
"""
|
|
123
|
+
self.memory = memory
|
|
124
|
+
self.agent_id = agent_id
|
|
125
|
+
self._enable_streaming = enable_streaming
|
|
126
|
+
self._event_streamer = None
|
|
127
|
+
|
|
128
|
+
if self.memory is None:
|
|
129
|
+
try:
|
|
130
|
+
from attune.telemetry import UsageTracker
|
|
131
|
+
|
|
132
|
+
tracker = UsageTracker.get_instance()
|
|
133
|
+
if hasattr(tracker, "_memory"):
|
|
134
|
+
self.memory = tracker._memory
|
|
135
|
+
except (ImportError, AttributeError):
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
if self.memory is None:
|
|
139
|
+
logger.warning("No memory backend available for coordination signals")
|
|
140
|
+
|
|
141
|
+
def _get_event_streamer(self):
|
|
142
|
+
"""Get or create EventStreamer instance (lazy initialization)."""
|
|
143
|
+
if not self._enable_streaming:
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
if self._event_streamer is None:
|
|
147
|
+
try:
|
|
148
|
+
from attune.telemetry.event_streaming import EventStreamer
|
|
149
|
+
|
|
150
|
+
self._event_streamer = EventStreamer(memory=self.memory)
|
|
151
|
+
except Exception as e:
|
|
152
|
+
logger.warning(f"Failed to initialize EventStreamer: {e}")
|
|
153
|
+
self._enable_streaming = False
|
|
154
|
+
|
|
155
|
+
return self._event_streamer
|
|
156
|
+
|
|
157
|
+
def signal(
|
|
158
|
+
self,
|
|
159
|
+
signal_type: str,
|
|
160
|
+
source_agent: str | None = None,
|
|
161
|
+
target_agent: str | None = None,
|
|
162
|
+
payload: dict[str, Any] | None = None,
|
|
163
|
+
ttl_seconds: int | None = None,
|
|
164
|
+
credentials: AgentCredentials | None = None,
|
|
165
|
+
) -> str:
|
|
166
|
+
"""Send a coordination signal.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
signal_type: Type of signal (e.g., "task_complete", "abort", "ready")
|
|
170
|
+
source_agent: Source agent ID (defaults to self.agent_id)
|
|
171
|
+
target_agent: Target agent ID (None for broadcast)
|
|
172
|
+
payload: Signal payload data
|
|
173
|
+
ttl_seconds: TTL for this signal (defaults to DEFAULT_TTL)
|
|
174
|
+
credentials: Agent credentials for permission check (optional but recommended)
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Signal ID
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
PermissionError: If credentials provided but agent lacks CONTRIBUTOR tier
|
|
181
|
+
|
|
182
|
+
Security:
|
|
183
|
+
Coordination signals require CONTRIBUTOR tier or higher. If credentials
|
|
184
|
+
are not provided, a warning is logged but the signal is still sent
|
|
185
|
+
(backward compatibility). For production use, always provide credentials.
|
|
186
|
+
"""
|
|
187
|
+
if not self.memory:
|
|
188
|
+
logger.warning("Cannot send signal: no memory backend")
|
|
189
|
+
return ""
|
|
190
|
+
|
|
191
|
+
# Permission check for coordination signals (requires CONTRIBUTOR tier)
|
|
192
|
+
if credentials is not None:
|
|
193
|
+
if not credentials.can_stage():
|
|
194
|
+
raise PermissionError(
|
|
195
|
+
f"Agent {credentials.agent_id} (Tier {credentials.tier.name}) "
|
|
196
|
+
"cannot send coordination signals. Requires CONTRIBUTOR tier or higher."
|
|
197
|
+
)
|
|
198
|
+
else:
|
|
199
|
+
# Log warning if no credentials provided (security best practice)
|
|
200
|
+
logger.warning(
|
|
201
|
+
"Sending coordination signal without credentials - "
|
|
202
|
+
"permission check bypassed. Provide credentials for secure coordination."
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
source = source_agent or self.agent_id or "unknown"
|
|
206
|
+
signal_id = f"signal_{uuid4().hex[:8]}"
|
|
207
|
+
ttl = ttl_seconds or self.DEFAULT_TTL
|
|
208
|
+
|
|
209
|
+
signal = CoordinationSignal(
|
|
210
|
+
signal_id=signal_id,
|
|
211
|
+
signal_type=signal_type,
|
|
212
|
+
source_agent=source,
|
|
213
|
+
target_agent=target_agent,
|
|
214
|
+
payload=payload or {},
|
|
215
|
+
timestamp=datetime.utcnow(),
|
|
216
|
+
ttl_seconds=ttl,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Store signal with TTL (Pattern 2)
|
|
220
|
+
# Key format: empathy:signal:{target}:{type}:{id}
|
|
221
|
+
target_key = target_agent or self.BROADCAST_TARGET
|
|
222
|
+
key = f"{self.KEY_PREFIX}{target_key}:{signal_type}:{signal_id}"
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
# Use direct Redis access for custom TTL
|
|
226
|
+
if hasattr(self.memory, "_client") and self.memory._client:
|
|
227
|
+
import json
|
|
228
|
+
|
|
229
|
+
self.memory._client.setex(key, ttl, json.dumps(signal.to_dict()))
|
|
230
|
+
else:
|
|
231
|
+
logger.warning("Cannot send signal: no Redis backend available")
|
|
232
|
+
except Exception as e:
|
|
233
|
+
logger.error(f"Failed to send signal {signal_id}: {e}")
|
|
234
|
+
|
|
235
|
+
# Publish to event stream (Pattern 4 - optional)
|
|
236
|
+
streamer = self._get_event_streamer()
|
|
237
|
+
if streamer:
|
|
238
|
+
try:
|
|
239
|
+
streamer.publish_event(
|
|
240
|
+
event_type="coordination_signal",
|
|
241
|
+
data=signal.to_dict(),
|
|
242
|
+
source="attune",
|
|
243
|
+
)
|
|
244
|
+
except Exception as e:
|
|
245
|
+
logger.debug(f"Failed to publish coordination signal event to stream: {e}")
|
|
246
|
+
|
|
247
|
+
return signal_id
|
|
248
|
+
|
|
249
|
+
def broadcast(
|
|
250
|
+
self,
|
|
251
|
+
signal_type: str,
|
|
252
|
+
source_agent: str | None = None,
|
|
253
|
+
payload: dict[str, Any] | None = None,
|
|
254
|
+
ttl_seconds: int | None = None,
|
|
255
|
+
credentials: AgentCredentials | None = None,
|
|
256
|
+
) -> str:
|
|
257
|
+
"""Broadcast signal to all agents.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
signal_type: Type of signal
|
|
261
|
+
source_agent: Source agent ID
|
|
262
|
+
payload: Signal payload
|
|
263
|
+
ttl_seconds: TTL for signal
|
|
264
|
+
credentials: Agent credentials for permission check
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
Signal ID
|
|
268
|
+
|
|
269
|
+
Raises:
|
|
270
|
+
PermissionError: If credentials provided but agent lacks CONTRIBUTOR tier
|
|
271
|
+
"""
|
|
272
|
+
return self.signal(
|
|
273
|
+
signal_type=signal_type,
|
|
274
|
+
source_agent=source_agent,
|
|
275
|
+
target_agent=None, # Broadcast
|
|
276
|
+
payload=payload,
|
|
277
|
+
ttl_seconds=ttl_seconds,
|
|
278
|
+
credentials=credentials,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
def wait_for_signal(
|
|
282
|
+
self,
|
|
283
|
+
signal_type: str,
|
|
284
|
+
source_agent: str | None = None,
|
|
285
|
+
timeout: float = 30.0,
|
|
286
|
+
poll_interval: float = 0.5,
|
|
287
|
+
) -> CoordinationSignal | None:
|
|
288
|
+
"""Wait for a specific signal (blocking with timeout).
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
signal_type: Type of signal to wait for
|
|
292
|
+
source_agent: Optional source agent filter
|
|
293
|
+
timeout: Maximum wait time in seconds
|
|
294
|
+
poll_interval: Poll interval in seconds
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
CoordinationSignal if received, None if timeout
|
|
298
|
+
"""
|
|
299
|
+
if not self.memory or not self.agent_id:
|
|
300
|
+
return None
|
|
301
|
+
|
|
302
|
+
start_time = time.time()
|
|
303
|
+
|
|
304
|
+
while time.time() - start_time < timeout:
|
|
305
|
+
# Check for signal
|
|
306
|
+
signal = self.check_signal(signal_type=signal_type, source_agent=source_agent, consume=True)
|
|
307
|
+
|
|
308
|
+
if signal:
|
|
309
|
+
return signal
|
|
310
|
+
|
|
311
|
+
# Sleep before next poll
|
|
312
|
+
time.sleep(poll_interval)
|
|
313
|
+
|
|
314
|
+
return None
|
|
315
|
+
|
|
316
|
+
def check_signal(
|
|
317
|
+
self, signal_type: str, source_agent: str | None = None, consume: bool = True
|
|
318
|
+
) -> CoordinationSignal | None:
|
|
319
|
+
"""Check for a signal without blocking.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
signal_type: Type of signal to check
|
|
323
|
+
source_agent: Optional source agent filter
|
|
324
|
+
consume: If True, remove signal after reading
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
CoordinationSignal if available, None otherwise
|
|
328
|
+
"""
|
|
329
|
+
if not self.memory or not self.agent_id:
|
|
330
|
+
return None
|
|
331
|
+
|
|
332
|
+
try:
|
|
333
|
+
# Scan for matching signals
|
|
334
|
+
# Check targeted signals: empathy:signal:{agent_id}:{type}:*
|
|
335
|
+
# Check broadcast signals: empathy:signal:*:{type}:*
|
|
336
|
+
patterns = [
|
|
337
|
+
f"{self.KEY_PREFIX}{self.agent_id}:{signal_type}:*",
|
|
338
|
+
f"{self.KEY_PREFIX}{self.BROADCAST_TARGET}:{signal_type}:*"
|
|
339
|
+
]
|
|
340
|
+
|
|
341
|
+
for pattern in patterns:
|
|
342
|
+
if hasattr(self.memory, "_client"):
|
|
343
|
+
keys = self.memory._client.keys(pattern)
|
|
344
|
+
else:
|
|
345
|
+
continue
|
|
346
|
+
|
|
347
|
+
for key in keys:
|
|
348
|
+
if isinstance(key, bytes):
|
|
349
|
+
key = key.decode("utf-8")
|
|
350
|
+
|
|
351
|
+
# Retrieve signal
|
|
352
|
+
data = self._retrieve_signal(key)
|
|
353
|
+
if not data:
|
|
354
|
+
continue
|
|
355
|
+
|
|
356
|
+
signal = CoordinationSignal.from_dict(data)
|
|
357
|
+
|
|
358
|
+
# Filter by source if specified
|
|
359
|
+
if source_agent and signal.source_agent != source_agent:
|
|
360
|
+
continue
|
|
361
|
+
|
|
362
|
+
# Consume signal if requested
|
|
363
|
+
if consume:
|
|
364
|
+
self._delete_signal(key)
|
|
365
|
+
|
|
366
|
+
return signal
|
|
367
|
+
|
|
368
|
+
return None
|
|
369
|
+
except Exception as e:
|
|
370
|
+
logger.error(f"Failed to check signal: {e}")
|
|
371
|
+
return None
|
|
372
|
+
|
|
373
|
+
def get_pending_signals(self, signal_type: str | None = None) -> list[CoordinationSignal]:
|
|
374
|
+
"""Get all pending signals for this agent.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
signal_type: Optional filter by signal type
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
List of pending signals
|
|
381
|
+
"""
|
|
382
|
+
if not self.memory or not self.agent_id:
|
|
383
|
+
return []
|
|
384
|
+
|
|
385
|
+
try:
|
|
386
|
+
# Scan for all signals for this agent
|
|
387
|
+
patterns = [
|
|
388
|
+
f"{self.KEY_PREFIX}{self.agent_id}:*",
|
|
389
|
+
f"{self.KEY_PREFIX}{self.BROADCAST_TARGET}:*",
|
|
390
|
+
]
|
|
391
|
+
|
|
392
|
+
signals = []
|
|
393
|
+
for pattern in patterns:
|
|
394
|
+
if hasattr(self.memory, "_client"):
|
|
395
|
+
keys = self.memory._client.keys(pattern)
|
|
396
|
+
else:
|
|
397
|
+
continue
|
|
398
|
+
|
|
399
|
+
for key in keys:
|
|
400
|
+
if isinstance(key, bytes):
|
|
401
|
+
key = key.decode("utf-8")
|
|
402
|
+
|
|
403
|
+
data = self._retrieve_signal(key)
|
|
404
|
+
if not data:
|
|
405
|
+
continue
|
|
406
|
+
|
|
407
|
+
signal = CoordinationSignal.from_dict(data)
|
|
408
|
+
|
|
409
|
+
# Filter by type if specified
|
|
410
|
+
if signal_type and signal.signal_type != signal_type:
|
|
411
|
+
continue
|
|
412
|
+
|
|
413
|
+
signals.append(signal)
|
|
414
|
+
|
|
415
|
+
return signals
|
|
416
|
+
except Exception as e:
|
|
417
|
+
logger.error(f"Failed to get pending signals: {e}")
|
|
418
|
+
return []
|
|
419
|
+
|
|
420
|
+
def clear_signals(self, signal_type: str | None = None) -> int:
|
|
421
|
+
"""Clear all signals for this agent.
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
signal_type: Optional filter by signal type
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
Number of signals cleared
|
|
428
|
+
"""
|
|
429
|
+
if not self.memory or not self.agent_id:
|
|
430
|
+
return 0
|
|
431
|
+
|
|
432
|
+
signals = self.get_pending_signals(signal_type=signal_type)
|
|
433
|
+
count = 0
|
|
434
|
+
|
|
435
|
+
for signal in signals:
|
|
436
|
+
# Reconstruct key
|
|
437
|
+
target_key = signal.target_agent or self.BROADCAST_TARGET
|
|
438
|
+
key = f"{self.KEY_PREFIX}{target_key}:{signal.signal_type}:{signal.signal_id}"
|
|
439
|
+
if self._delete_signal(key):
|
|
440
|
+
count += 1
|
|
441
|
+
|
|
442
|
+
return count
|
|
443
|
+
|
|
444
|
+
def _retrieve_signal(self, key: str) -> dict[str, Any] | None:
|
|
445
|
+
"""Retrieve signal data from memory."""
|
|
446
|
+
if not self.memory:
|
|
447
|
+
return None
|
|
448
|
+
|
|
449
|
+
try:
|
|
450
|
+
# Use direct Redis access (signal keys are stored without prefix)
|
|
451
|
+
if hasattr(self.memory, "_client"):
|
|
452
|
+
import json
|
|
453
|
+
|
|
454
|
+
data = self.memory._client.get(key)
|
|
455
|
+
if data:
|
|
456
|
+
if isinstance(data, bytes):
|
|
457
|
+
data = data.decode("utf-8")
|
|
458
|
+
return json.loads(data)
|
|
459
|
+
return None
|
|
460
|
+
except Exception as e:
|
|
461
|
+
logger.debug(f"Failed to retrieve signal {key}: {e}")
|
|
462
|
+
return None
|
|
463
|
+
|
|
464
|
+
def _delete_signal(self, key: str) -> bool:
|
|
465
|
+
"""Delete signal from memory."""
|
|
466
|
+
if not self.memory:
|
|
467
|
+
return False
|
|
468
|
+
|
|
469
|
+
try:
|
|
470
|
+
if hasattr(self.memory, "_client"):
|
|
471
|
+
return self.memory._client.delete(key) > 0
|
|
472
|
+
return False
|
|
473
|
+
except Exception as e:
|
|
474
|
+
logger.debug(f"Failed to delete signal {key}: {e}")
|
|
475
|
+
return False
|