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,772 @@
|
|
|
1
|
+
"""Feedback Loop for Continuous Improvement
|
|
2
|
+
|
|
3
|
+
Analyzes success metrics from workflow executions to improve
|
|
4
|
+
future agent generation. This creates a learning system that:
|
|
5
|
+
|
|
6
|
+
1. Tracks which agent configurations succeed
|
|
7
|
+
2. Identifies patterns in successful workflows
|
|
8
|
+
3. Adjusts agent recommendations based on historical data
|
|
9
|
+
4. Provides insights for manual tuning
|
|
10
|
+
|
|
11
|
+
Copyright 2026 Smart-AI-Memory
|
|
12
|
+
Licensed under Fair Source License 0.9
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from .blueprint import AgentBlueprint, WorkflowBlueprint
|
|
25
|
+
from .success import SuccessEvaluation
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# =============================================================================
|
|
31
|
+
# FEEDBACK DATA STRUCTURES
|
|
32
|
+
# =============================================================================
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class AgentPerformance:
|
|
37
|
+
"""Performance statistics for an agent template."""
|
|
38
|
+
|
|
39
|
+
template_id: str
|
|
40
|
+
total_uses: int = 0
|
|
41
|
+
successful_uses: int = 0
|
|
42
|
+
average_score: float = 0.0
|
|
43
|
+
scores: list[float] = field(default_factory=list)
|
|
44
|
+
|
|
45
|
+
# Context-specific performance
|
|
46
|
+
by_domain: dict[str, dict[str, float]] = field(default_factory=dict)
|
|
47
|
+
by_language: dict[str, dict[str, float]] = field(default_factory=dict)
|
|
48
|
+
by_quality_focus: dict[str, dict[str, float]] = field(default_factory=dict)
|
|
49
|
+
|
|
50
|
+
# Trend data
|
|
51
|
+
recent_scores: list[tuple[str, float]] = field(default_factory=list) # (timestamp, score)
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def success_rate(self) -> float:
|
|
55
|
+
"""Calculate success rate."""
|
|
56
|
+
if self.total_uses == 0:
|
|
57
|
+
return 0.0
|
|
58
|
+
return self.successful_uses / self.total_uses
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def trend(self) -> str:
|
|
62
|
+
"""Determine performance trend."""
|
|
63
|
+
if len(self.recent_scores) < 5:
|
|
64
|
+
return "insufficient_data"
|
|
65
|
+
|
|
66
|
+
recent_5 = [s for _, s in self.recent_scores[-5:]]
|
|
67
|
+
older_5 = (
|
|
68
|
+
[s for _, s in self.recent_scores[-10:-5]] if len(self.recent_scores) >= 10 else []
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if not older_5:
|
|
72
|
+
return "stable"
|
|
73
|
+
|
|
74
|
+
recent_avg = sum(recent_5) / len(recent_5)
|
|
75
|
+
older_avg = sum(older_5) / len(older_5)
|
|
76
|
+
|
|
77
|
+
if recent_avg > older_avg * 1.1:
|
|
78
|
+
return "improving"
|
|
79
|
+
elif recent_avg < older_avg * 0.9:
|
|
80
|
+
return "declining"
|
|
81
|
+
else:
|
|
82
|
+
return "stable"
|
|
83
|
+
|
|
84
|
+
def record_use(
|
|
85
|
+
self,
|
|
86
|
+
success: bool,
|
|
87
|
+
score: float,
|
|
88
|
+
domain: str | None = None,
|
|
89
|
+
languages: list[str] | None = None,
|
|
90
|
+
quality_focus: list[str] | None = None,
|
|
91
|
+
) -> None:
|
|
92
|
+
"""Record a use of this agent."""
|
|
93
|
+
self.total_uses += 1
|
|
94
|
+
if success:
|
|
95
|
+
self.successful_uses += 1
|
|
96
|
+
|
|
97
|
+
self.scores.append(score)
|
|
98
|
+
self.average_score = sum(self.scores) / len(self.scores)
|
|
99
|
+
|
|
100
|
+
# Record with timestamp for trend analysis
|
|
101
|
+
self.recent_scores.append((datetime.now().isoformat(), score))
|
|
102
|
+
# Keep last 100 scores
|
|
103
|
+
if len(self.recent_scores) > 100:
|
|
104
|
+
self.recent_scores = self.recent_scores[-100:]
|
|
105
|
+
|
|
106
|
+
# Record by context
|
|
107
|
+
if domain:
|
|
108
|
+
if domain not in self.by_domain:
|
|
109
|
+
self.by_domain[domain] = {"uses": 0, "successes": 0, "total_score": 0}
|
|
110
|
+
self.by_domain[domain]["uses"] += 1
|
|
111
|
+
self.by_domain[domain]["successes"] += 1 if success else 0
|
|
112
|
+
self.by_domain[domain]["total_score"] += score
|
|
113
|
+
|
|
114
|
+
if languages:
|
|
115
|
+
for lang in languages:
|
|
116
|
+
if lang not in self.by_language:
|
|
117
|
+
self.by_language[lang] = {"uses": 0, "successes": 0, "total_score": 0}
|
|
118
|
+
self.by_language[lang]["uses"] += 1
|
|
119
|
+
self.by_language[lang]["successes"] += 1 if success else 0
|
|
120
|
+
self.by_language[lang]["total_score"] += score
|
|
121
|
+
|
|
122
|
+
if quality_focus:
|
|
123
|
+
for qf in quality_focus:
|
|
124
|
+
if qf not in self.by_quality_focus:
|
|
125
|
+
self.by_quality_focus[qf] = {"uses": 0, "successes": 0, "total_score": 0}
|
|
126
|
+
self.by_quality_focus[qf]["uses"] += 1
|
|
127
|
+
self.by_quality_focus[qf]["successes"] += 1 if success else 0
|
|
128
|
+
self.by_quality_focus[qf]["total_score"] += score
|
|
129
|
+
|
|
130
|
+
def get_score_for_context(
|
|
131
|
+
self,
|
|
132
|
+
domain: str | None = None,
|
|
133
|
+
languages: list[str] | None = None,
|
|
134
|
+
quality_focus: list[str] | None = None,
|
|
135
|
+
) -> float:
|
|
136
|
+
"""Get a weighted score for a specific context."""
|
|
137
|
+
scores = []
|
|
138
|
+
weights = []
|
|
139
|
+
|
|
140
|
+
# Base score
|
|
141
|
+
if self.total_uses > 0:
|
|
142
|
+
scores.append(self.average_score)
|
|
143
|
+
weights.append(1.0)
|
|
144
|
+
|
|
145
|
+
# Domain-specific score
|
|
146
|
+
if domain and domain in self.by_domain:
|
|
147
|
+
d = self.by_domain[domain]
|
|
148
|
+
if d["uses"] > 0:
|
|
149
|
+
scores.append(d["total_score"] / d["uses"])
|
|
150
|
+
weights.append(2.0) # Higher weight for domain match
|
|
151
|
+
|
|
152
|
+
# Language-specific score
|
|
153
|
+
if languages:
|
|
154
|
+
for lang in languages:
|
|
155
|
+
if lang in self.by_language:
|
|
156
|
+
lang_stats = self.by_language[lang]
|
|
157
|
+
if lang_stats["uses"] > 0:
|
|
158
|
+
scores.append(lang_stats["total_score"] / lang_stats["uses"])
|
|
159
|
+
weights.append(1.5)
|
|
160
|
+
|
|
161
|
+
# Quality focus score
|
|
162
|
+
if quality_focus:
|
|
163
|
+
for qf in quality_focus:
|
|
164
|
+
if qf in self.by_quality_focus:
|
|
165
|
+
q = self.by_quality_focus[qf]
|
|
166
|
+
if q["uses"] > 0:
|
|
167
|
+
scores.append(q["total_score"] / q["uses"])
|
|
168
|
+
weights.append(1.5)
|
|
169
|
+
|
|
170
|
+
if not scores:
|
|
171
|
+
return 0.5 # Default neutral score
|
|
172
|
+
|
|
173
|
+
# Weighted average
|
|
174
|
+
return sum(s * w for s, w in zip(scores, weights, strict=False)) / sum(weights)
|
|
175
|
+
|
|
176
|
+
def to_dict(self) -> dict[str, Any]:
|
|
177
|
+
"""Serialize to dictionary."""
|
|
178
|
+
return {
|
|
179
|
+
"template_id": self.template_id,
|
|
180
|
+
"total_uses": self.total_uses,
|
|
181
|
+
"successful_uses": self.successful_uses,
|
|
182
|
+
"average_score": self.average_score,
|
|
183
|
+
"success_rate": self.success_rate,
|
|
184
|
+
"trend": self.trend,
|
|
185
|
+
"by_domain": self.by_domain,
|
|
186
|
+
"by_language": self.by_language,
|
|
187
|
+
"by_quality_focus": self.by_quality_focus,
|
|
188
|
+
"recent_scores": self.recent_scores[-20:], # Last 20 for display
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@classmethod
|
|
192
|
+
def from_dict(cls, data: dict[str, Any]) -> AgentPerformance:
|
|
193
|
+
"""Deserialize from dictionary."""
|
|
194
|
+
perf = cls(template_id=data.get("template_id", ""))
|
|
195
|
+
perf.total_uses = data.get("total_uses", 0)
|
|
196
|
+
perf.successful_uses = data.get("successful_uses", 0)
|
|
197
|
+
perf.average_score = data.get("average_score", 0.0)
|
|
198
|
+
perf.by_domain = data.get("by_domain", {})
|
|
199
|
+
perf.by_language = data.get("by_language", {})
|
|
200
|
+
perf.by_quality_focus = data.get("by_quality_focus", {})
|
|
201
|
+
perf.recent_scores = data.get("recent_scores", [])
|
|
202
|
+
return perf
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@dataclass
|
|
206
|
+
class WorkflowPattern:
|
|
207
|
+
"""Pattern of successful workflow configurations."""
|
|
208
|
+
|
|
209
|
+
pattern_id: str
|
|
210
|
+
domain: str
|
|
211
|
+
agent_combination: list[str] # List of template IDs
|
|
212
|
+
stage_configuration: list[dict[str, Any]]
|
|
213
|
+
uses: int = 0
|
|
214
|
+
successes: int = 0
|
|
215
|
+
average_score: float = 0.0
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def success_rate(self) -> float:
|
|
219
|
+
"""Calculate success rate."""
|
|
220
|
+
if self.uses == 0:
|
|
221
|
+
return 0.0
|
|
222
|
+
return self.successes / self.uses
|
|
223
|
+
|
|
224
|
+
def to_dict(self) -> dict[str, Any]:
|
|
225
|
+
"""Serialize to dictionary."""
|
|
226
|
+
return {
|
|
227
|
+
"pattern_id": self.pattern_id,
|
|
228
|
+
"domain": self.domain,
|
|
229
|
+
"agent_combination": self.agent_combination,
|
|
230
|
+
"stage_configuration": self.stage_configuration,
|
|
231
|
+
"uses": self.uses,
|
|
232
|
+
"successes": self.successes,
|
|
233
|
+
"average_score": self.average_score,
|
|
234
|
+
"success_rate": self.success_rate,
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# =============================================================================
|
|
239
|
+
# FEEDBACK COLLECTOR
|
|
240
|
+
# =============================================================================
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class FeedbackCollector:
|
|
244
|
+
"""Collects and stores feedback from workflow executions.
|
|
245
|
+
|
|
246
|
+
Example:
|
|
247
|
+
>>> collector = FeedbackCollector()
|
|
248
|
+
>>> collector.record_execution(blueprint, evaluation)
|
|
249
|
+
>>> performance = collector.get_agent_performance("security_reviewer")
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
def __init__(self, storage_path: str = ".attune/socratic/feedback"):
|
|
253
|
+
"""Initialize the collector.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
storage_path: Path for feedback data storage
|
|
257
|
+
"""
|
|
258
|
+
self.storage_path = Path(storage_path)
|
|
259
|
+
self.storage_path.mkdir(parents=True, exist_ok=True)
|
|
260
|
+
|
|
261
|
+
self._agent_performance: dict[str, AgentPerformance] = {}
|
|
262
|
+
self._workflow_patterns: dict[str, WorkflowPattern] = {}
|
|
263
|
+
|
|
264
|
+
self._load_data()
|
|
265
|
+
|
|
266
|
+
def _load_data(self) -> None:
|
|
267
|
+
"""Load existing feedback data."""
|
|
268
|
+
# Load agent performance
|
|
269
|
+
perf_file = self.storage_path / "agent_performance.json"
|
|
270
|
+
if perf_file.exists():
|
|
271
|
+
try:
|
|
272
|
+
with perf_file.open() as f:
|
|
273
|
+
data = json.load(f)
|
|
274
|
+
for template_id, perf_data in data.items():
|
|
275
|
+
self._agent_performance[template_id] = AgentPerformance.from_dict(perf_data)
|
|
276
|
+
except (json.JSONDecodeError, KeyError) as e:
|
|
277
|
+
logger.warning(f"Failed to load agent performance: {e}")
|
|
278
|
+
|
|
279
|
+
# Load workflow patterns
|
|
280
|
+
patterns_file = self.storage_path / "workflow_patterns.json"
|
|
281
|
+
if patterns_file.exists():
|
|
282
|
+
try:
|
|
283
|
+
with patterns_file.open() as f:
|
|
284
|
+
data = json.load(f)
|
|
285
|
+
for pattern_id, pattern_data in data.items():
|
|
286
|
+
self._workflow_patterns[pattern_id] = WorkflowPattern(**pattern_data)
|
|
287
|
+
except (json.JSONDecodeError, KeyError) as e:
|
|
288
|
+
logger.warning(f"Failed to load workflow patterns: {e}")
|
|
289
|
+
|
|
290
|
+
def _save_data(self) -> None:
|
|
291
|
+
"""Save feedback data to disk."""
|
|
292
|
+
# Save agent performance
|
|
293
|
+
perf_file = self.storage_path / "agent_performance.json"
|
|
294
|
+
perf_data = {k: v.to_dict() for k, v in self._agent_performance.items()}
|
|
295
|
+
with perf_file.open("w") as f:
|
|
296
|
+
json.dump(perf_data, f, indent=2)
|
|
297
|
+
|
|
298
|
+
# Save workflow patterns
|
|
299
|
+
patterns_file = self.storage_path / "workflow_patterns.json"
|
|
300
|
+
patterns_data = {k: v.to_dict() for k, v in self._workflow_patterns.items()}
|
|
301
|
+
with patterns_file.open("w") as f:
|
|
302
|
+
json.dump(patterns_data, f, indent=2)
|
|
303
|
+
|
|
304
|
+
def record_execution(
|
|
305
|
+
self,
|
|
306
|
+
blueprint: WorkflowBlueprint,
|
|
307
|
+
evaluation: SuccessEvaluation,
|
|
308
|
+
) -> None:
|
|
309
|
+
"""Record feedback from a workflow execution.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
blueprint: The executed workflow blueprint
|
|
313
|
+
evaluation: The success evaluation results
|
|
314
|
+
"""
|
|
315
|
+
success = evaluation.overall_success
|
|
316
|
+
score = evaluation.overall_score
|
|
317
|
+
|
|
318
|
+
# Record for each agent
|
|
319
|
+
for agent in blueprint.agents:
|
|
320
|
+
template_id = agent.template_id or agent.spec.id
|
|
321
|
+
|
|
322
|
+
if template_id not in self._agent_performance:
|
|
323
|
+
self._agent_performance[template_id] = AgentPerformance(template_id=template_id)
|
|
324
|
+
|
|
325
|
+
self._agent_performance[template_id].record_use(
|
|
326
|
+
success=success,
|
|
327
|
+
score=score,
|
|
328
|
+
domain=blueprint.domain,
|
|
329
|
+
languages=blueprint.supported_languages,
|
|
330
|
+
quality_focus=blueprint.quality_focus,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
# Record workflow pattern
|
|
334
|
+
pattern_id = self._generate_pattern_id(blueprint)
|
|
335
|
+
if pattern_id not in self._workflow_patterns:
|
|
336
|
+
self._workflow_patterns[pattern_id] = WorkflowPattern(
|
|
337
|
+
pattern_id=pattern_id,
|
|
338
|
+
domain=blueprint.domain,
|
|
339
|
+
agent_combination=[a.template_id or a.spec.id for a in blueprint.agents],
|
|
340
|
+
stage_configuration=[s.to_dict() for s in blueprint.stages],
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
pattern = self._workflow_patterns[pattern_id]
|
|
344
|
+
pattern.uses += 1
|
|
345
|
+
if success:
|
|
346
|
+
pattern.successes += 1
|
|
347
|
+
# Rolling average
|
|
348
|
+
pattern.average_score = (pattern.average_score * (pattern.uses - 1) + score) / pattern.uses
|
|
349
|
+
|
|
350
|
+
self._save_data()
|
|
351
|
+
logger.info(
|
|
352
|
+
f"Recorded feedback for blueprint {blueprint.id[:8]}: success={success}, score={score:.2f}"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
def _generate_pattern_id(self, blueprint: WorkflowBlueprint) -> str:
|
|
356
|
+
"""Generate a unique ID for a workflow pattern."""
|
|
357
|
+
agents = sorted(a.template_id or a.spec.id for a in blueprint.agents)
|
|
358
|
+
return f"{blueprint.domain}:{':'.join(agents)}"
|
|
359
|
+
|
|
360
|
+
def get_agent_performance(self, template_id: str) -> AgentPerformance | None:
|
|
361
|
+
"""Get performance data for an agent template."""
|
|
362
|
+
return self._agent_performance.get(template_id)
|
|
363
|
+
|
|
364
|
+
def get_all_performance(self) -> dict[str, AgentPerformance]:
|
|
365
|
+
"""Get all agent performance data."""
|
|
366
|
+
return self._agent_performance.copy()
|
|
367
|
+
|
|
368
|
+
def get_best_agents_for_context(
|
|
369
|
+
self,
|
|
370
|
+
domain: str,
|
|
371
|
+
languages: list[str] | None = None,
|
|
372
|
+
quality_focus: list[str] | None = None,
|
|
373
|
+
limit: int = 5,
|
|
374
|
+
) -> list[tuple[str, float]]:
|
|
375
|
+
"""Get the best performing agents for a context.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
domain: Target domain
|
|
379
|
+
languages: Target languages
|
|
380
|
+
quality_focus: Quality attributes
|
|
381
|
+
limit: Maximum number of results
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
List of (template_id, score) tuples sorted by score
|
|
385
|
+
"""
|
|
386
|
+
scored_agents = []
|
|
387
|
+
|
|
388
|
+
for template_id, perf in self._agent_performance.items():
|
|
389
|
+
score = perf.get_score_for_context(domain, languages, quality_focus)
|
|
390
|
+
# Apply confidence penalty for low sample sizes
|
|
391
|
+
confidence = min(perf.total_uses / 10, 1.0) # Full confidence at 10+ uses
|
|
392
|
+
adjusted_score = score * confidence + 0.5 * (1 - confidence) # Blend with neutral
|
|
393
|
+
scored_agents.append((template_id, adjusted_score))
|
|
394
|
+
|
|
395
|
+
# Sort by score descending
|
|
396
|
+
scored_agents.sort(key=lambda x: x[1], reverse=True)
|
|
397
|
+
|
|
398
|
+
return scored_agents[:limit]
|
|
399
|
+
|
|
400
|
+
def get_successful_patterns(
|
|
401
|
+
self,
|
|
402
|
+
domain: str | None = None,
|
|
403
|
+
min_success_rate: float = 0.7,
|
|
404
|
+
min_uses: int = 3,
|
|
405
|
+
) -> list[WorkflowPattern]:
|
|
406
|
+
"""Get successful workflow patterns.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
domain: Filter by domain
|
|
410
|
+
min_success_rate: Minimum success rate threshold
|
|
411
|
+
min_uses: Minimum number of uses to be considered
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
List of successful patterns
|
|
415
|
+
"""
|
|
416
|
+
patterns = []
|
|
417
|
+
|
|
418
|
+
for pattern in self._workflow_patterns.values():
|
|
419
|
+
if domain and pattern.domain != domain:
|
|
420
|
+
continue
|
|
421
|
+
if pattern.uses < min_uses:
|
|
422
|
+
continue
|
|
423
|
+
if pattern.success_rate < min_success_rate:
|
|
424
|
+
continue
|
|
425
|
+
patterns.append(pattern)
|
|
426
|
+
|
|
427
|
+
# Sort by success rate then by uses
|
|
428
|
+
patterns.sort(key=lambda p: (p.success_rate, p.uses), reverse=True)
|
|
429
|
+
|
|
430
|
+
return patterns
|
|
431
|
+
|
|
432
|
+
def get_insights(self) -> dict[str, Any]:
|
|
433
|
+
"""Get aggregated insights from feedback data.
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
Dictionary with various insights
|
|
437
|
+
"""
|
|
438
|
+
insights: dict[str, Any] = {
|
|
439
|
+
"total_agents_tracked": len(self._agent_performance),
|
|
440
|
+
"total_patterns_tracked": len(self._workflow_patterns),
|
|
441
|
+
"top_performing_agents": [],
|
|
442
|
+
"declining_agents": [],
|
|
443
|
+
"domain_insights": {},
|
|
444
|
+
"recommendations": [],
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
# Top performing agents
|
|
448
|
+
all_agents = [
|
|
449
|
+
(tid, perf) for tid, perf in self._agent_performance.items() if perf.total_uses >= 5
|
|
450
|
+
]
|
|
451
|
+
all_agents.sort(key=lambda x: x[1].average_score, reverse=True)
|
|
452
|
+
insights["top_performing_agents"] = [
|
|
453
|
+
{"template_id": tid, "score": perf.average_score, "uses": perf.total_uses}
|
|
454
|
+
for tid, perf in all_agents[:5]
|
|
455
|
+
]
|
|
456
|
+
|
|
457
|
+
# Declining agents
|
|
458
|
+
for tid, perf in self._agent_performance.items():
|
|
459
|
+
if perf.trend == "declining" and perf.total_uses >= 5:
|
|
460
|
+
insights["declining_agents"].append(
|
|
461
|
+
{
|
|
462
|
+
"template_id": tid,
|
|
463
|
+
"current_score": perf.average_score,
|
|
464
|
+
"uses": perf.total_uses,
|
|
465
|
+
}
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
# Domain insights
|
|
469
|
+
domains: dict[str, dict[str, Any]] = {}
|
|
470
|
+
for perf in self._agent_performance.values():
|
|
471
|
+
for domain, stats in perf.by_domain.items():
|
|
472
|
+
if domain not in domains:
|
|
473
|
+
domains[domain] = {"total_uses": 0, "total_score": 0, "agents": set()}
|
|
474
|
+
domains[domain]["total_uses"] += stats["uses"]
|
|
475
|
+
domains[domain]["total_score"] += stats["total_score"]
|
|
476
|
+
domains[domain]["agents"].add(perf.template_id)
|
|
477
|
+
|
|
478
|
+
for domain, stats in domains.items():
|
|
479
|
+
if stats["total_uses"] > 0:
|
|
480
|
+
insights["domain_insights"][domain] = {
|
|
481
|
+
"average_score": stats["total_score"] / stats["total_uses"],
|
|
482
|
+
"total_uses": stats["total_uses"],
|
|
483
|
+
"agents_used": len(stats["agents"]),
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
# Generate recommendations
|
|
487
|
+
insights["recommendations"] = self._generate_recommendations()
|
|
488
|
+
|
|
489
|
+
return insights
|
|
490
|
+
|
|
491
|
+
def _generate_recommendations(self) -> list[str]:
|
|
492
|
+
"""Generate improvement recommendations based on feedback."""
|
|
493
|
+
recommendations = []
|
|
494
|
+
|
|
495
|
+
# Check for underperforming agents
|
|
496
|
+
for tid, perf in self._agent_performance.items():
|
|
497
|
+
if perf.total_uses >= 10 and perf.success_rate < 0.5:
|
|
498
|
+
recommendations.append(
|
|
499
|
+
f"Consider reviewing '{tid}' configuration - success rate is {perf.success_rate:.0%}"
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
# Check for agents that work well together
|
|
503
|
+
successful_patterns = self.get_successful_patterns(min_success_rate=0.8, min_uses=5)
|
|
504
|
+
for pattern in successful_patterns[:3]:
|
|
505
|
+
agents = ", ".join(pattern.agent_combination)
|
|
506
|
+
recommendations.append(
|
|
507
|
+
f"Successful pattern for '{pattern.domain}': [{agents}] - {pattern.success_rate:.0%} success rate"
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
# Check for domains needing more data
|
|
511
|
+
for domain, stats in self.get_insights().get("domain_insights", {}).items():
|
|
512
|
+
if stats["total_uses"] < 5:
|
|
513
|
+
recommendations.append(
|
|
514
|
+
f"More data needed for '{domain}' domain - only {stats['total_uses']} executions recorded"
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
return recommendations
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
# =============================================================================
|
|
521
|
+
# ADAPTIVE AGENT GENERATOR
|
|
522
|
+
# =============================================================================
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
class AdaptiveAgentGenerator:
|
|
526
|
+
"""Agent generator that uses feedback to improve recommendations.
|
|
527
|
+
|
|
528
|
+
Wraps the standard AgentGenerator and adjusts recommendations
|
|
529
|
+
based on historical performance data.
|
|
530
|
+
|
|
531
|
+
Example:
|
|
532
|
+
>>> generator = AdaptiveAgentGenerator()
|
|
533
|
+
>>> agents = generator.generate_agents_for_requirements(requirements)
|
|
534
|
+
>>> # Returns agents weighted by historical success
|
|
535
|
+
"""
|
|
536
|
+
|
|
537
|
+
def __init__(self, feedback_collector: FeedbackCollector | None = None):
|
|
538
|
+
"""Initialize the adaptive generator.
|
|
539
|
+
|
|
540
|
+
Args:
|
|
541
|
+
feedback_collector: Feedback collector instance
|
|
542
|
+
"""
|
|
543
|
+
from .generator import AgentGenerator
|
|
544
|
+
|
|
545
|
+
self.base_generator = AgentGenerator()
|
|
546
|
+
self.feedback = feedback_collector or FeedbackCollector()
|
|
547
|
+
|
|
548
|
+
def generate_agents_for_requirements(
|
|
549
|
+
self,
|
|
550
|
+
requirements: dict[str, Any],
|
|
551
|
+
use_feedback: bool = True,
|
|
552
|
+
) -> list[AgentBlueprint]:
|
|
553
|
+
"""Generate agents using feedback-informed recommendations.
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
requirements: Requirements from Socratic session
|
|
557
|
+
use_feedback: Whether to use feedback data
|
|
558
|
+
|
|
559
|
+
Returns:
|
|
560
|
+
List of AgentBlueprints optimized based on feedback
|
|
561
|
+
"""
|
|
562
|
+
# Get base recommendations
|
|
563
|
+
base_agents = self.base_generator.generate_agents_for_requirements(requirements)
|
|
564
|
+
|
|
565
|
+
if not use_feedback:
|
|
566
|
+
return base_agents
|
|
567
|
+
|
|
568
|
+
# Get context
|
|
569
|
+
domain = requirements.get("domain", "general")
|
|
570
|
+
languages = requirements.get("languages", [])
|
|
571
|
+
quality_focus = requirements.get("quality_focus", [])
|
|
572
|
+
|
|
573
|
+
# Get best agents for this context
|
|
574
|
+
best_agents = self.feedback.get_best_agents_for_context(
|
|
575
|
+
domain=domain,
|
|
576
|
+
languages=languages,
|
|
577
|
+
quality_focus=quality_focus,
|
|
578
|
+
limit=10,
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
if not best_agents:
|
|
582
|
+
return base_agents
|
|
583
|
+
|
|
584
|
+
# Reorder and potentially add agents based on feedback
|
|
585
|
+
agent_scores = dict(best_agents)
|
|
586
|
+
|
|
587
|
+
# Score base agents
|
|
588
|
+
scored_base = []
|
|
589
|
+
for agent in base_agents:
|
|
590
|
+
tid = agent.template_id or agent.spec.id
|
|
591
|
+
feedback_score = agent_scores.get(tid, 0.5)
|
|
592
|
+
scored_base.append((agent, feedback_score))
|
|
593
|
+
|
|
594
|
+
# Sort by feedback score
|
|
595
|
+
scored_base.sort(key=lambda x: x[1], reverse=True)
|
|
596
|
+
|
|
597
|
+
# Check if any high-performing agents are missing
|
|
598
|
+
base_ids = {a.template_id or a.spec.id for a in base_agents}
|
|
599
|
+
for tid, score in best_agents:
|
|
600
|
+
if tid not in base_ids and score > 0.7:
|
|
601
|
+
# Add this high-performing agent
|
|
602
|
+
try:
|
|
603
|
+
new_agent = self.base_generator.generate_agent_from_template(
|
|
604
|
+
tid,
|
|
605
|
+
customizations={
|
|
606
|
+
"languages": languages,
|
|
607
|
+
"quality_focus": quality_focus,
|
|
608
|
+
},
|
|
609
|
+
)
|
|
610
|
+
scored_base.append((new_agent, score))
|
|
611
|
+
logger.info(f"Added high-performing agent '{tid}' based on feedback")
|
|
612
|
+
except ValueError:
|
|
613
|
+
pass # Template not found
|
|
614
|
+
|
|
615
|
+
# Return sorted agents
|
|
616
|
+
return [agent for agent, _ in scored_base]
|
|
617
|
+
|
|
618
|
+
def get_recommendation_explanation(
|
|
619
|
+
self,
|
|
620
|
+
requirements: dict[str, Any],
|
|
621
|
+
) -> dict[str, Any]:
|
|
622
|
+
"""Get explanation for agent recommendations.
|
|
623
|
+
|
|
624
|
+
Args:
|
|
625
|
+
requirements: Requirements dict
|
|
626
|
+
|
|
627
|
+
Returns:
|
|
628
|
+
Explanation of why agents were recommended
|
|
629
|
+
"""
|
|
630
|
+
domain = requirements.get("domain", "general")
|
|
631
|
+
languages = requirements.get("languages", [])
|
|
632
|
+
quality_focus = requirements.get("quality_focus", [])
|
|
633
|
+
|
|
634
|
+
best_agents = self.feedback.get_best_agents_for_context(
|
|
635
|
+
domain=domain,
|
|
636
|
+
languages=languages,
|
|
637
|
+
quality_focus=quality_focus,
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
successful_patterns = self.feedback.get_successful_patterns(
|
|
641
|
+
domain=domain,
|
|
642
|
+
min_success_rate=0.7,
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
return {
|
|
646
|
+
"context": {
|
|
647
|
+
"domain": domain,
|
|
648
|
+
"languages": languages,
|
|
649
|
+
"quality_focus": quality_focus,
|
|
650
|
+
},
|
|
651
|
+
"recommended_agents": [
|
|
652
|
+
{
|
|
653
|
+
"template_id": tid,
|
|
654
|
+
"score": score,
|
|
655
|
+
"performance": (
|
|
656
|
+
self.feedback.get_agent_performance(tid).to_dict()
|
|
657
|
+
if self.feedback.get_agent_performance(tid)
|
|
658
|
+
else None
|
|
659
|
+
),
|
|
660
|
+
}
|
|
661
|
+
for tid, score in best_agents
|
|
662
|
+
],
|
|
663
|
+
"successful_patterns": [p.to_dict() for p in successful_patterns[:3]],
|
|
664
|
+
"data_quality": {
|
|
665
|
+
"total_executions": sum(
|
|
666
|
+
p.total_uses for p in self.feedback.get_all_performance().values()
|
|
667
|
+
),
|
|
668
|
+
"agents_with_data": len(
|
|
669
|
+
[p for p in self.feedback.get_all_performance().values() if p.total_uses >= 5]
|
|
670
|
+
),
|
|
671
|
+
},
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
# =============================================================================
|
|
676
|
+
# FEEDBACK LOOP INTEGRATION
|
|
677
|
+
# =============================================================================
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
class FeedbackLoop:
|
|
681
|
+
"""High-level integration for the feedback loop.
|
|
682
|
+
|
|
683
|
+
Provides a simple interface to:
|
|
684
|
+
1. Record execution results
|
|
685
|
+
2. Get improved recommendations
|
|
686
|
+
3. View insights
|
|
687
|
+
|
|
688
|
+
Example:
|
|
689
|
+
>>> loop = FeedbackLoop()
|
|
690
|
+
>>>
|
|
691
|
+
>>> # After workflow execution
|
|
692
|
+
>>> loop.record(blueprint, evaluation)
|
|
693
|
+
>>>
|
|
694
|
+
>>> # For next generation
|
|
695
|
+
>>> agents = loop.get_recommended_agents(requirements)
|
|
696
|
+
>>>
|
|
697
|
+
>>> # View insights
|
|
698
|
+
>>> insights = loop.get_insights()
|
|
699
|
+
"""
|
|
700
|
+
|
|
701
|
+
def __init__(
|
|
702
|
+
self,
|
|
703
|
+
storage_path: str = ".attune/socratic/feedback",
|
|
704
|
+
):
|
|
705
|
+
"""Initialize the feedback loop.
|
|
706
|
+
|
|
707
|
+
Args:
|
|
708
|
+
storage_path: Path for feedback storage
|
|
709
|
+
"""
|
|
710
|
+
self.collector = FeedbackCollector(storage_path)
|
|
711
|
+
self.adaptive_generator = AdaptiveAgentGenerator(self.collector)
|
|
712
|
+
|
|
713
|
+
def record(
|
|
714
|
+
self,
|
|
715
|
+
blueprint: WorkflowBlueprint,
|
|
716
|
+
evaluation: SuccessEvaluation,
|
|
717
|
+
) -> None:
|
|
718
|
+
"""Record execution results for learning.
|
|
719
|
+
|
|
720
|
+
Args:
|
|
721
|
+
blueprint: The executed blueprint
|
|
722
|
+
evaluation: The success evaluation
|
|
723
|
+
"""
|
|
724
|
+
self.collector.record_execution(blueprint, evaluation)
|
|
725
|
+
|
|
726
|
+
def get_recommended_agents(
|
|
727
|
+
self,
|
|
728
|
+
requirements: dict[str, Any],
|
|
729
|
+
) -> list[AgentBlueprint]:
|
|
730
|
+
"""Get recommended agents using feedback data.
|
|
731
|
+
|
|
732
|
+
Args:
|
|
733
|
+
requirements: Requirements from Socratic session
|
|
734
|
+
|
|
735
|
+
Returns:
|
|
736
|
+
List of recommended agents
|
|
737
|
+
"""
|
|
738
|
+
return self.adaptive_generator.generate_agents_for_requirements(requirements)
|
|
739
|
+
|
|
740
|
+
def get_insights(self) -> dict[str, Any]:
|
|
741
|
+
"""Get aggregated insights.
|
|
742
|
+
|
|
743
|
+
Returns:
|
|
744
|
+
Dictionary with insights and recommendations
|
|
745
|
+
"""
|
|
746
|
+
return self.collector.get_insights()
|
|
747
|
+
|
|
748
|
+
def get_agent_stats(self, template_id: str) -> dict[str, Any] | None:
|
|
749
|
+
"""Get performance stats for a specific agent.
|
|
750
|
+
|
|
751
|
+
Args:
|
|
752
|
+
template_id: Agent template ID
|
|
753
|
+
|
|
754
|
+
Returns:
|
|
755
|
+
Performance statistics or None
|
|
756
|
+
"""
|
|
757
|
+
perf = self.collector.get_agent_performance(template_id)
|
|
758
|
+
return perf.to_dict() if perf else None
|
|
759
|
+
|
|
760
|
+
def explain_recommendations(
|
|
761
|
+
self,
|
|
762
|
+
requirements: dict[str, Any],
|
|
763
|
+
) -> dict[str, Any]:
|
|
764
|
+
"""Explain why certain agents are recommended.
|
|
765
|
+
|
|
766
|
+
Args:
|
|
767
|
+
requirements: Requirements dict
|
|
768
|
+
|
|
769
|
+
Returns:
|
|
770
|
+
Explanation dictionary
|
|
771
|
+
"""
|
|
772
|
+
return self.adaptive_generator.get_recommendation_explanation(requirements)
|