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
attune/dashboard/app.py
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
"""Agent Coordination Dashboard - FastAPI Application.
|
|
2
|
+
|
|
3
|
+
Web dashboard for monitoring all 6 Agent Coordination patterns:
|
|
4
|
+
1. Agent Heartbeat Tracking
|
|
5
|
+
2. Coordination Signals
|
|
6
|
+
3. State Synchronization
|
|
7
|
+
4. Real-Time Event Streaming
|
|
8
|
+
5. Human Approval Gates
|
|
9
|
+
6. Agent-to-LLM Feedback Loop
|
|
10
|
+
|
|
11
|
+
Copyright 2025 Smart-AI-Memory
|
|
12
|
+
Licensed under Fair Source License 0.9
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
|
|
23
|
+
from fastapi.responses import HTMLResponse
|
|
24
|
+
from fastapi.staticfiles import StaticFiles
|
|
25
|
+
from pydantic import BaseModel
|
|
26
|
+
|
|
27
|
+
from attune.telemetry import (
|
|
28
|
+
ApprovalGate,
|
|
29
|
+
CoordinationSignals,
|
|
30
|
+
EventStreamer,
|
|
31
|
+
FeedbackLoop,
|
|
32
|
+
HeartbeatCoordinator,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
# Create FastAPI app
|
|
38
|
+
app = FastAPI(
|
|
39
|
+
title="Empathy Agent Dashboard",
|
|
40
|
+
description="Real-time monitoring dashboard for agent coordination patterns",
|
|
41
|
+
version="1.0.0",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Mount static files
|
|
45
|
+
static_dir = Path(__file__).parent / "static"
|
|
46
|
+
app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# ============================================================================
|
|
50
|
+
# Models
|
|
51
|
+
# ============================================================================
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class AgentStatus(BaseModel):
|
|
55
|
+
"""Agent status summary."""
|
|
56
|
+
|
|
57
|
+
agent_id: str
|
|
58
|
+
status: str
|
|
59
|
+
last_seen: str
|
|
60
|
+
progress: float
|
|
61
|
+
current_task: str
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class SignalSummary(BaseModel):
|
|
65
|
+
"""Coordination signal summary."""
|
|
66
|
+
|
|
67
|
+
signal_type: str
|
|
68
|
+
source_agent: str
|
|
69
|
+
target_agent: str
|
|
70
|
+
timestamp: str
|
|
71
|
+
payload: dict[str, Any]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ApprovalRequestSummary(BaseModel):
|
|
75
|
+
"""Approval request summary."""
|
|
76
|
+
|
|
77
|
+
request_id: str
|
|
78
|
+
approval_type: str
|
|
79
|
+
agent_id: str
|
|
80
|
+
context: dict[str, Any]
|
|
81
|
+
timestamp: str
|
|
82
|
+
timeout_seconds: float
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class QualityMetrics(BaseModel):
|
|
86
|
+
"""Quality feedback metrics."""
|
|
87
|
+
|
|
88
|
+
workflow_name: str
|
|
89
|
+
stage_name: str
|
|
90
|
+
tier: str
|
|
91
|
+
avg_quality: float
|
|
92
|
+
sample_count: int
|
|
93
|
+
trend: float
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ============================================================================
|
|
97
|
+
# Root Endpoint
|
|
98
|
+
# ============================================================================
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@app.get("/", response_class=HTMLResponse)
|
|
102
|
+
async def get_dashboard():
|
|
103
|
+
"""Serve dashboard HTML."""
|
|
104
|
+
html_file = static_dir / "index.html"
|
|
105
|
+
if not html_file.exists():
|
|
106
|
+
return """
|
|
107
|
+
<html>
|
|
108
|
+
<head><title>Agent Dashboard</title></head>
|
|
109
|
+
<body>
|
|
110
|
+
<h1>Agent Coordination Dashboard</h1>
|
|
111
|
+
<p>Dashboard UI not found. Please ensure static files are built.</p>
|
|
112
|
+
<p>API Documentation: <a href="/docs">/docs</a></p>
|
|
113
|
+
</body>
|
|
114
|
+
</html>
|
|
115
|
+
"""
|
|
116
|
+
return html_file.read_text()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ============================================================================
|
|
120
|
+
# Pattern 1: Agent Heartbeat Tracking
|
|
121
|
+
# ============================================================================
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@app.get("/api/agents", response_model=list[AgentStatus])
|
|
125
|
+
async def get_active_agents():
|
|
126
|
+
"""Get all active agents with heartbeats."""
|
|
127
|
+
try:
|
|
128
|
+
coordinator = HeartbeatCoordinator()
|
|
129
|
+
active_agents = coordinator.get_active_agents()
|
|
130
|
+
|
|
131
|
+
result = []
|
|
132
|
+
for agent_id in active_agents:
|
|
133
|
+
heartbeat = coordinator.get_heartbeat(agent_id)
|
|
134
|
+
if heartbeat:
|
|
135
|
+
result.append(
|
|
136
|
+
AgentStatus(
|
|
137
|
+
agent_id=agent_id,
|
|
138
|
+
status=heartbeat.status,
|
|
139
|
+
last_seen=heartbeat.timestamp.isoformat(),
|
|
140
|
+
progress=heartbeat.progress,
|
|
141
|
+
current_task=heartbeat.current_task,
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return result
|
|
146
|
+
except Exception as e:
|
|
147
|
+
logger.error(f"Failed to get active agents: {e}")
|
|
148
|
+
return []
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@app.get("/api/agents/{agent_id}")
|
|
152
|
+
async def get_agent_status(agent_id: str):
|
|
153
|
+
"""Get specific agent status."""
|
|
154
|
+
try:
|
|
155
|
+
coordinator = HeartbeatCoordinator()
|
|
156
|
+
heartbeat = coordinator.get_heartbeat(agent_id)
|
|
157
|
+
|
|
158
|
+
if not heartbeat:
|
|
159
|
+
raise HTTPException(status_code=404, detail=f"Agent {agent_id} not found")
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
"agent_id": agent_id,
|
|
163
|
+
"status": heartbeat.status,
|
|
164
|
+
"last_seen": heartbeat.timestamp.isoformat(),
|
|
165
|
+
"progress": heartbeat.progress,
|
|
166
|
+
"current_task": heartbeat.current_task,
|
|
167
|
+
"metadata": heartbeat.metadata,
|
|
168
|
+
}
|
|
169
|
+
except HTTPException:
|
|
170
|
+
raise
|
|
171
|
+
except Exception as e:
|
|
172
|
+
logger.error(f"Failed to get agent status: {e}")
|
|
173
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# ============================================================================
|
|
177
|
+
# Pattern 2: Coordination Signals
|
|
178
|
+
# ============================================================================
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@app.get("/api/signals", response_model=list[SignalSummary])
|
|
182
|
+
async def get_recent_signals(limit: int = 50):
|
|
183
|
+
"""Get recent coordination signals."""
|
|
184
|
+
try:
|
|
185
|
+
signals = CoordinationSignals()
|
|
186
|
+
recent = signals.get_recent_signals(limit=limit)
|
|
187
|
+
|
|
188
|
+
return [
|
|
189
|
+
SignalSummary(
|
|
190
|
+
signal_type=sig.signal_type,
|
|
191
|
+
source_agent=sig.source_agent,
|
|
192
|
+
target_agent=sig.target_agent,
|
|
193
|
+
timestamp=sig.timestamp.isoformat(),
|
|
194
|
+
payload=sig.payload,
|
|
195
|
+
)
|
|
196
|
+
for sig in recent
|
|
197
|
+
]
|
|
198
|
+
except Exception as e:
|
|
199
|
+
logger.error(f"Failed to get signals: {e}")
|
|
200
|
+
return []
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@app.get("/api/signals/{agent_id}")
|
|
204
|
+
async def get_agent_signals(agent_id: str, limit: int = 20):
|
|
205
|
+
"""Get signals for specific agent."""
|
|
206
|
+
try:
|
|
207
|
+
signals = CoordinationSignals()
|
|
208
|
+
agent_signals = signals.get_signals_for_agent(agent_id, limit=limit)
|
|
209
|
+
|
|
210
|
+
return [
|
|
211
|
+
{
|
|
212
|
+
"signal_type": sig.signal_type,
|
|
213
|
+
"source_agent": sig.source_agent,
|
|
214
|
+
"target_agent": sig.target_agent,
|
|
215
|
+
"timestamp": sig.timestamp.isoformat(),
|
|
216
|
+
"payload": sig.payload,
|
|
217
|
+
}
|
|
218
|
+
for sig in agent_signals
|
|
219
|
+
]
|
|
220
|
+
except Exception as e:
|
|
221
|
+
logger.error(f"Failed to get agent signals: {e}")
|
|
222
|
+
return []
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# ============================================================================
|
|
226
|
+
# Pattern 4: Real-Time Event Streaming
|
|
227
|
+
# ============================================================================
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@app.get("/api/events")
|
|
231
|
+
async def get_recent_events(event_type: str | None = None, limit: int = 100):
|
|
232
|
+
"""Get recent events from streams."""
|
|
233
|
+
try:
|
|
234
|
+
streamer = EventStreamer()
|
|
235
|
+
|
|
236
|
+
if event_type:
|
|
237
|
+
events = list(streamer.get_recent_events(event_type, limit=limit))
|
|
238
|
+
else:
|
|
239
|
+
# Get events from multiple common streams
|
|
240
|
+
all_events = []
|
|
241
|
+
for evt_type in ["agent_heartbeat", "coordination_signal", "workflow_progress"]:
|
|
242
|
+
events = list(streamer.get_recent_events(evt_type, limit=20))
|
|
243
|
+
all_events.extend(events)
|
|
244
|
+
|
|
245
|
+
# Sort by timestamp and limit
|
|
246
|
+
all_events.sort(key=lambda e: e.timestamp, reverse=True)
|
|
247
|
+
events = all_events[:limit]
|
|
248
|
+
|
|
249
|
+
return [
|
|
250
|
+
{
|
|
251
|
+
"event_id": evt.event_id,
|
|
252
|
+
"event_type": evt.event_type,
|
|
253
|
+
"timestamp": evt.timestamp.isoformat(),
|
|
254
|
+
"data": evt.data,
|
|
255
|
+
"source": evt.source,
|
|
256
|
+
}
|
|
257
|
+
for evt in events
|
|
258
|
+
]
|
|
259
|
+
except Exception as e:
|
|
260
|
+
logger.error(f"Failed to get events: {e}")
|
|
261
|
+
return []
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
# ============================================================================
|
|
265
|
+
# Pattern 5: Human Approval Gates
|
|
266
|
+
# ============================================================================
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@app.get("/api/approvals", response_model=list[ApprovalRequestSummary])
|
|
270
|
+
async def get_pending_approvals():
|
|
271
|
+
"""Get pending approval requests."""
|
|
272
|
+
try:
|
|
273
|
+
gate = ApprovalGate()
|
|
274
|
+
pending = gate.get_pending_approvals()
|
|
275
|
+
|
|
276
|
+
return [
|
|
277
|
+
ApprovalRequestSummary(
|
|
278
|
+
request_id=req.request_id,
|
|
279
|
+
approval_type=req.approval_type,
|
|
280
|
+
agent_id=req.agent_id,
|
|
281
|
+
context=req.context,
|
|
282
|
+
timestamp=req.timestamp.isoformat(),
|
|
283
|
+
timeout_seconds=req.timeout_seconds,
|
|
284
|
+
)
|
|
285
|
+
for req in pending
|
|
286
|
+
]
|
|
287
|
+
except Exception as e:
|
|
288
|
+
logger.error(f"Failed to get pending approvals: {e}")
|
|
289
|
+
return []
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@app.post("/api/approvals/{request_id}/approve")
|
|
293
|
+
async def approve_request(request_id: str, reason: str = "Approved via dashboard"):
|
|
294
|
+
"""Approve a pending request."""
|
|
295
|
+
try:
|
|
296
|
+
gate = ApprovalGate()
|
|
297
|
+
success = gate.respond_to_approval(
|
|
298
|
+
request_id=request_id, approved=True, responder="dashboard", reason=reason
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
if not success:
|
|
302
|
+
raise HTTPException(status_code=500, detail="Failed to record approval")
|
|
303
|
+
|
|
304
|
+
return {"status": "approved", "request_id": request_id}
|
|
305
|
+
except HTTPException:
|
|
306
|
+
raise
|
|
307
|
+
except Exception as e:
|
|
308
|
+
logger.error(f"Failed to approve request: {e}")
|
|
309
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@app.post("/api/approvals/{request_id}/reject")
|
|
313
|
+
async def reject_request(request_id: str, reason: str = "Rejected via dashboard"):
|
|
314
|
+
"""Reject a pending request."""
|
|
315
|
+
try:
|
|
316
|
+
gate = ApprovalGate()
|
|
317
|
+
success = gate.respond_to_approval(
|
|
318
|
+
request_id=request_id, approved=False, responder="dashboard", reason=reason
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
if not success:
|
|
322
|
+
raise HTTPException(status_code=500, detail="Failed to record rejection")
|
|
323
|
+
|
|
324
|
+
return {"status": "rejected", "request_id": request_id}
|
|
325
|
+
except HTTPException:
|
|
326
|
+
raise
|
|
327
|
+
except Exception as e:
|
|
328
|
+
logger.error(f"Failed to reject request: {e}")
|
|
329
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
# ============================================================================
|
|
333
|
+
# Pattern 6: Agent-to-LLM Feedback Loop
|
|
334
|
+
# ============================================================================
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
@app.get("/api/feedback/workflows")
|
|
338
|
+
async def get_workflow_quality():
|
|
339
|
+
"""Get quality metrics for workflows."""
|
|
340
|
+
try:
|
|
341
|
+
feedback = FeedbackLoop()
|
|
342
|
+
|
|
343
|
+
# Get stats for known workflows (in production, this would query all workflows)
|
|
344
|
+
workflows = ["code-review", "test-generation", "refactoring"]
|
|
345
|
+
results = []
|
|
346
|
+
|
|
347
|
+
for workflow in workflows:
|
|
348
|
+
for stage in ["analysis", "generation", "validation"]:
|
|
349
|
+
for tier in ["cheap", "capable", "premium"]:
|
|
350
|
+
stats = feedback.get_quality_stats(workflow, stage, tier=tier)
|
|
351
|
+
if stats and stats.sample_count > 0:
|
|
352
|
+
results.append(
|
|
353
|
+
QualityMetrics(
|
|
354
|
+
workflow_name=workflow,
|
|
355
|
+
stage_name=stage,
|
|
356
|
+
tier=tier,
|
|
357
|
+
avg_quality=stats.avg_quality,
|
|
358
|
+
sample_count=stats.sample_count,
|
|
359
|
+
trend=stats.recent_trend,
|
|
360
|
+
)
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
return results
|
|
364
|
+
except Exception as e:
|
|
365
|
+
logger.error(f"Failed to get workflow quality: {e}")
|
|
366
|
+
return []
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
@app.get("/api/feedback/underperforming")
|
|
370
|
+
async def get_underperforming_stages(threshold: float = 0.7):
|
|
371
|
+
"""Get underperforming workflow stages."""
|
|
372
|
+
try:
|
|
373
|
+
feedback = FeedbackLoop()
|
|
374
|
+
|
|
375
|
+
# Check known workflows
|
|
376
|
+
workflows = ["code-review", "test-generation", "refactoring"]
|
|
377
|
+
all_underperforming = []
|
|
378
|
+
|
|
379
|
+
for workflow in workflows:
|
|
380
|
+
underperforming = feedback.get_underperforming_stages(workflow, quality_threshold=threshold)
|
|
381
|
+
for stage_name, stats in underperforming:
|
|
382
|
+
all_underperforming.append(
|
|
383
|
+
{
|
|
384
|
+
"workflow_name": workflow,
|
|
385
|
+
"stage_name": stage_name,
|
|
386
|
+
"avg_quality": stats.avg_quality,
|
|
387
|
+
"sample_count": stats.sample_count,
|
|
388
|
+
"min_quality": stats.min_quality,
|
|
389
|
+
"max_quality": stats.max_quality,
|
|
390
|
+
"trend": stats.recent_trend,
|
|
391
|
+
}
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
# Sort by worst quality
|
|
395
|
+
all_underperforming.sort(key=lambda x: x["avg_quality"])
|
|
396
|
+
|
|
397
|
+
return all_underperforming
|
|
398
|
+
except Exception as e:
|
|
399
|
+
logger.error(f"Failed to get underperforming stages: {e}")
|
|
400
|
+
return []
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
# ============================================================================
|
|
404
|
+
# System Health
|
|
405
|
+
# ============================================================================
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
@app.get("/api/health")
|
|
409
|
+
async def get_system_health():
|
|
410
|
+
"""Get overall system health status."""
|
|
411
|
+
try:
|
|
412
|
+
# Check if Redis is available
|
|
413
|
+
coordinator = HeartbeatCoordinator()
|
|
414
|
+
has_redis = coordinator.memory is not None
|
|
415
|
+
|
|
416
|
+
# Get counts
|
|
417
|
+
active_agents = len(coordinator.get_active_agents()) if has_redis else 0
|
|
418
|
+
|
|
419
|
+
gate = ApprovalGate()
|
|
420
|
+
pending_approvals = len(gate.get_pending_approvals()) if has_redis else 0
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
"status": "healthy" if has_redis else "degraded",
|
|
424
|
+
"redis_available": has_redis,
|
|
425
|
+
"active_agents": active_agents,
|
|
426
|
+
"pending_approvals": pending_approvals,
|
|
427
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
428
|
+
}
|
|
429
|
+
except Exception as e:
|
|
430
|
+
logger.error(f"Health check failed: {e}")
|
|
431
|
+
return {
|
|
432
|
+
"status": "unhealthy",
|
|
433
|
+
"redis_available": False,
|
|
434
|
+
"error": str(e),
|
|
435
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
# ============================================================================
|
|
440
|
+
# WebSocket for Real-Time Updates
|
|
441
|
+
# ============================================================================
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
class ConnectionManager:
|
|
445
|
+
"""Manage WebSocket connections."""
|
|
446
|
+
|
|
447
|
+
def __init__(self):
|
|
448
|
+
self.active_connections: list[WebSocket] = []
|
|
449
|
+
|
|
450
|
+
async def connect(self, websocket: WebSocket):
|
|
451
|
+
await websocket.accept()
|
|
452
|
+
self.active_connections.append(websocket)
|
|
453
|
+
|
|
454
|
+
def disconnect(self, websocket: WebSocket):
|
|
455
|
+
self.active_connections.remove(websocket)
|
|
456
|
+
|
|
457
|
+
async def broadcast(self, message: dict):
|
|
458
|
+
for connection in self.active_connections:
|
|
459
|
+
try:
|
|
460
|
+
await connection.send_json(message)
|
|
461
|
+
except Exception:
|
|
462
|
+
pass # Client disconnected
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
manager = ConnectionManager()
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
@app.websocket("/ws")
|
|
469
|
+
async def websocket_endpoint(websocket: WebSocket):
|
|
470
|
+
"""WebSocket endpoint for real-time updates."""
|
|
471
|
+
await manager.connect(websocket)
|
|
472
|
+
try:
|
|
473
|
+
while True:
|
|
474
|
+
# Receive ping to keep connection alive
|
|
475
|
+
data = await websocket.receive_text()
|
|
476
|
+
|
|
477
|
+
# Send updates (in production, this would stream from Redis)
|
|
478
|
+
coordinator = HeartbeatCoordinator()
|
|
479
|
+
active_agents = coordinator.get_active_agents()
|
|
480
|
+
|
|
481
|
+
await websocket.send_json(
|
|
482
|
+
{"type": "agent_update", "agents": [{"agent_id": aid} for aid in active_agents]}
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
except WebSocketDisconnect:
|
|
486
|
+
manager.disconnect(websocket)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
# ============================================================================
|
|
490
|
+
# Run Server
|
|
491
|
+
# ============================================================================
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def run_dashboard(host: str = "127.0.0.1", port: int = 8000):
|
|
495
|
+
"""Run the dashboard server.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
host: Host to bind to
|
|
499
|
+
port: Port to bind to
|
|
500
|
+
|
|
501
|
+
Example:
|
|
502
|
+
>>> from attune.dashboard import run_dashboard
|
|
503
|
+
>>> run_dashboard(host="0.0.0.0", port=8080)
|
|
504
|
+
"""
|
|
505
|
+
import uvicorn
|
|
506
|
+
|
|
507
|
+
logger.info(f"Starting dashboard at http://{host}:{port}")
|
|
508
|
+
uvicorn.run(app, host=host, port=port)
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
if __name__ == "__main__":
|
|
512
|
+
run_dashboard()
|