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,696 @@
|
|
|
1
|
+
"""Pattern Learning System - Grammar that evolves from experience.
|
|
2
|
+
|
|
3
|
+
This module implements the learning grammar that tracks pattern success
|
|
4
|
+
and recommends optimal compositions based on historical data.
|
|
5
|
+
|
|
6
|
+
Features:
|
|
7
|
+
- Track success metrics for each pattern execution
|
|
8
|
+
- Memory + file storage for fast access and persistence
|
|
9
|
+
- Hybrid recommendation: similarity matching → statistical fallback
|
|
10
|
+
|
|
11
|
+
Security:
|
|
12
|
+
- No eval() or exec() usage
|
|
13
|
+
- File paths validated before writing
|
|
14
|
+
- JSON serialization only (no pickle)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
from collections import defaultdict
|
|
20
|
+
from collections.abc import Iterator
|
|
21
|
+
from dataclasses import asdict, dataclass, field
|
|
22
|
+
from datetime import datetime
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
from attune.config import _validate_file_path
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# =============================================================================
|
|
32
|
+
# Data Models
|
|
33
|
+
# =============================================================================
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ExecutionRecord:
|
|
38
|
+
"""Record of a single pattern execution.
|
|
39
|
+
|
|
40
|
+
Captures the essential metrics for learning.
|
|
41
|
+
|
|
42
|
+
Attributes:
|
|
43
|
+
pattern: Pattern/strategy name used
|
|
44
|
+
success: Whether execution succeeded
|
|
45
|
+
duration_seconds: Execution time
|
|
46
|
+
cost: Estimated cost (tokens * rate)
|
|
47
|
+
confidence: Aggregate confidence score
|
|
48
|
+
context_features: Key features of the execution context
|
|
49
|
+
timestamp: When the execution occurred
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
pattern: str
|
|
53
|
+
success: bool
|
|
54
|
+
duration_seconds: float
|
|
55
|
+
cost: float = 0.0
|
|
56
|
+
confidence: float = 0.0
|
|
57
|
+
context_features: dict[str, Any] = field(default_factory=dict)
|
|
58
|
+
timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
59
|
+
|
|
60
|
+
def to_dict(self) -> dict[str, Any]:
|
|
61
|
+
"""Convert to dictionary for serialization."""
|
|
62
|
+
return asdict(self)
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def from_dict(cls, data: dict[str, Any]) -> "ExecutionRecord":
|
|
66
|
+
"""Create from dictionary."""
|
|
67
|
+
return cls(**data)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class PatternStats:
|
|
72
|
+
"""Aggregated statistics for a pattern.
|
|
73
|
+
|
|
74
|
+
Attributes:
|
|
75
|
+
pattern: Pattern/strategy name
|
|
76
|
+
total_executions: Number of times executed
|
|
77
|
+
success_count: Number of successful executions
|
|
78
|
+
total_duration: Sum of all execution durations
|
|
79
|
+
total_cost: Sum of all execution costs
|
|
80
|
+
avg_confidence: Average confidence across executions
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
pattern: str
|
|
84
|
+
total_executions: int = 0
|
|
85
|
+
success_count: int = 0
|
|
86
|
+
total_duration: float = 0.0
|
|
87
|
+
total_cost: float = 0.0
|
|
88
|
+
avg_confidence: float = 0.0
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def success_rate(self) -> float:
|
|
92
|
+
"""Calculate success rate (0.0 - 1.0)."""
|
|
93
|
+
if self.total_executions == 0:
|
|
94
|
+
return 0.0
|
|
95
|
+
return self.success_count / self.total_executions
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def avg_duration(self) -> float:
|
|
99
|
+
"""Calculate average execution duration."""
|
|
100
|
+
if self.total_executions == 0:
|
|
101
|
+
return 0.0
|
|
102
|
+
return self.total_duration / self.total_executions
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def avg_cost(self) -> float:
|
|
106
|
+
"""Calculate average execution cost."""
|
|
107
|
+
if self.total_executions == 0:
|
|
108
|
+
return 0.0
|
|
109
|
+
return self.total_cost / self.total_executions
|
|
110
|
+
|
|
111
|
+
def update(self, record: ExecutionRecord) -> None:
|
|
112
|
+
"""Update stats with a new execution record.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
record: Execution record to incorporate
|
|
116
|
+
"""
|
|
117
|
+
self.total_executions += 1
|
|
118
|
+
if record.success:
|
|
119
|
+
self.success_count += 1
|
|
120
|
+
self.total_duration += record.duration_seconds
|
|
121
|
+
self.total_cost += record.cost
|
|
122
|
+
|
|
123
|
+
# Running average for confidence
|
|
124
|
+
n = self.total_executions
|
|
125
|
+
self.avg_confidence = (self.avg_confidence * (n - 1) + record.confidence) / n
|
|
126
|
+
|
|
127
|
+
def to_dict(self) -> dict[str, Any]:
|
|
128
|
+
"""Convert to dictionary for serialization."""
|
|
129
|
+
return {
|
|
130
|
+
"pattern": self.pattern,
|
|
131
|
+
"total_executions": self.total_executions,
|
|
132
|
+
"success_count": self.success_count,
|
|
133
|
+
"total_duration": self.total_duration,
|
|
134
|
+
"total_cost": self.total_cost,
|
|
135
|
+
"avg_confidence": self.avg_confidence,
|
|
136
|
+
# Computed properties
|
|
137
|
+
"success_rate": self.success_rate,
|
|
138
|
+
"avg_duration": self.avg_duration,
|
|
139
|
+
"avg_cost": self.avg_cost,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def from_dict(cls, data: dict[str, Any]) -> "PatternStats":
|
|
144
|
+
"""Create from dictionary."""
|
|
145
|
+
return cls(
|
|
146
|
+
pattern=data["pattern"],
|
|
147
|
+
total_executions=data.get("total_executions", 0),
|
|
148
|
+
success_count=data.get("success_count", 0),
|
|
149
|
+
total_duration=data.get("total_duration", 0.0),
|
|
150
|
+
total_cost=data.get("total_cost", 0.0),
|
|
151
|
+
avg_confidence=data.get("avg_confidence", 0.0),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@dataclass
|
|
156
|
+
class ContextSignature:
|
|
157
|
+
"""Signature of a context for similarity matching.
|
|
158
|
+
|
|
159
|
+
Extracts key features from execution context for comparison.
|
|
160
|
+
|
|
161
|
+
Attributes:
|
|
162
|
+
task_type: Type of task (e.g., "code_review", "test_gen")
|
|
163
|
+
agent_count: Number of agents involved
|
|
164
|
+
has_conditions: Whether conditionals were used
|
|
165
|
+
has_nesting: Whether nested workflows were used
|
|
166
|
+
priority: Task priority level
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
task_type: str = ""
|
|
170
|
+
agent_count: int = 0
|
|
171
|
+
has_conditions: bool = False
|
|
172
|
+
has_nesting: bool = False
|
|
173
|
+
priority: str = "normal"
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def from_context(cls, context: dict[str, Any]) -> "ContextSignature":
|
|
177
|
+
"""Extract signature from execution context.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
context: Execution context dictionary
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
ContextSignature with extracted features
|
|
184
|
+
"""
|
|
185
|
+
return cls(
|
|
186
|
+
task_type=context.get("task_type", context.get("_task_type", "")),
|
|
187
|
+
agent_count=len(context.get("agents", [])),
|
|
188
|
+
has_conditions="_conditional" in context,
|
|
189
|
+
has_nesting="_nesting" in context,
|
|
190
|
+
priority=context.get("priority", "normal"),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def similarity(self, other: "ContextSignature") -> float:
|
|
194
|
+
"""Calculate similarity score with another signature.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
other: Signature to compare with
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Similarity score (0.0 - 1.0)
|
|
201
|
+
"""
|
|
202
|
+
score = 0.0
|
|
203
|
+
max_score = 0.0
|
|
204
|
+
|
|
205
|
+
# Task type match (highest weight)
|
|
206
|
+
max_score += 3.0
|
|
207
|
+
if self.task_type and other.task_type:
|
|
208
|
+
if self.task_type == other.task_type:
|
|
209
|
+
score += 3.0
|
|
210
|
+
elif self.task_type.split("_")[0] == other.task_type.split("_")[0]:
|
|
211
|
+
score += 1.5 # Partial match
|
|
212
|
+
|
|
213
|
+
# Agent count similarity
|
|
214
|
+
max_score += 1.0
|
|
215
|
+
if self.agent_count > 0 and other.agent_count > 0:
|
|
216
|
+
ratio = min(self.agent_count, other.agent_count) / max(
|
|
217
|
+
self.agent_count, other.agent_count
|
|
218
|
+
)
|
|
219
|
+
score += ratio
|
|
220
|
+
|
|
221
|
+
# Boolean features
|
|
222
|
+
max_score += 2.0
|
|
223
|
+
if self.has_conditions == other.has_conditions:
|
|
224
|
+
score += 1.0
|
|
225
|
+
if self.has_nesting == other.has_nesting:
|
|
226
|
+
score += 1.0
|
|
227
|
+
|
|
228
|
+
# Priority match
|
|
229
|
+
max_score += 1.0
|
|
230
|
+
if self.priority == other.priority:
|
|
231
|
+
score += 1.0
|
|
232
|
+
|
|
233
|
+
return score / max_score if max_score > 0 else 0.0
|
|
234
|
+
|
|
235
|
+
def to_dict(self) -> dict[str, Any]:
|
|
236
|
+
"""Convert to dictionary."""
|
|
237
|
+
return asdict(self)
|
|
238
|
+
|
|
239
|
+
@classmethod
|
|
240
|
+
def from_dict(cls, data: dict[str, Any]) -> "ContextSignature":
|
|
241
|
+
"""Create from dictionary."""
|
|
242
|
+
return cls(**data)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# =============================================================================
|
|
246
|
+
# Storage Layer
|
|
247
|
+
# =============================================================================
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class LearningStore:
|
|
251
|
+
"""Memory + file storage for learning data.
|
|
252
|
+
|
|
253
|
+
Maintains an in-memory cache for fast access with
|
|
254
|
+
periodic persistence to a JSON file.
|
|
255
|
+
|
|
256
|
+
Attributes:
|
|
257
|
+
file_path: Path to persistence file
|
|
258
|
+
_records: In-memory execution records
|
|
259
|
+
_stats: In-memory pattern statistics
|
|
260
|
+
_dirty: Whether in-memory data needs saving
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
DEFAULT_FILE = "patterns/learning_memory.json"
|
|
264
|
+
|
|
265
|
+
def __init__(self, file_path: str | None = None):
|
|
266
|
+
"""Initialize learning store.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
file_path: Path to persistence file (default: patterns/learning_memory.json)
|
|
270
|
+
"""
|
|
271
|
+
self.file_path = Path(file_path or self.DEFAULT_FILE)
|
|
272
|
+
self._records: list[ExecutionRecord] = []
|
|
273
|
+
self._stats: dict[str, PatternStats] = {}
|
|
274
|
+
self._context_index: dict[str, list[int]] = defaultdict(list)
|
|
275
|
+
self._dirty = False
|
|
276
|
+
|
|
277
|
+
# Load existing data if available
|
|
278
|
+
self._load()
|
|
279
|
+
|
|
280
|
+
def _load(self) -> None:
|
|
281
|
+
"""Load data from file if it exists."""
|
|
282
|
+
if not self.file_path.exists():
|
|
283
|
+
logger.info(f"No existing learning data at {self.file_path}")
|
|
284
|
+
return
|
|
285
|
+
|
|
286
|
+
try:
|
|
287
|
+
with self.file_path.open("r") as f:
|
|
288
|
+
data = json.load(f)
|
|
289
|
+
|
|
290
|
+
# Load records
|
|
291
|
+
self._records = [ExecutionRecord.from_dict(r) for r in data.get("records", [])]
|
|
292
|
+
|
|
293
|
+
# Load stats
|
|
294
|
+
self._stats = {s["pattern"]: PatternStats.from_dict(s) for s in data.get("stats", [])}
|
|
295
|
+
|
|
296
|
+
# Rebuild context index
|
|
297
|
+
for i, record in enumerate(self._records):
|
|
298
|
+
sig = ContextSignature(task_type=record.context_features.get("task_type", ""))
|
|
299
|
+
self._context_index[sig.task_type].append(i)
|
|
300
|
+
|
|
301
|
+
logger.info(
|
|
302
|
+
f"Loaded {len(self._records)} records, "
|
|
303
|
+
f"{len(self._stats)} pattern stats from {self.file_path}"
|
|
304
|
+
)
|
|
305
|
+
except json.JSONDecodeError as e:
|
|
306
|
+
logger.error(f"Failed to parse learning data: {e}")
|
|
307
|
+
except Exception as e:
|
|
308
|
+
logger.exception(f"Failed to load learning data: {e}")
|
|
309
|
+
|
|
310
|
+
def save(self) -> None:
|
|
311
|
+
"""Save data to file."""
|
|
312
|
+
if not self._dirty:
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
# Ensure directory exists
|
|
316
|
+
self.file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
317
|
+
|
|
318
|
+
data = {
|
|
319
|
+
"records": [r.to_dict() for r in self._records],
|
|
320
|
+
"stats": [s.to_dict() for s in self._stats.values()],
|
|
321
|
+
"metadata": {
|
|
322
|
+
"saved_at": datetime.now().isoformat(),
|
|
323
|
+
"total_records": len(self._records),
|
|
324
|
+
"patterns_tracked": len(self._stats),
|
|
325
|
+
},
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
validated_path = _validate_file_path(str(self.file_path))
|
|
330
|
+
with validated_path.open("w") as f:
|
|
331
|
+
json.dump(data, f, indent=2)
|
|
332
|
+
self._dirty = False
|
|
333
|
+
logger.info(f"Saved learning data to {validated_path}")
|
|
334
|
+
except (OSError, ValueError) as e:
|
|
335
|
+
logger.exception(f"Failed to save learning data: {e}")
|
|
336
|
+
|
|
337
|
+
def add_record(self, record: ExecutionRecord) -> None:
|
|
338
|
+
"""Add an execution record.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
record: Record to add
|
|
342
|
+
"""
|
|
343
|
+
self._records.append(record)
|
|
344
|
+
|
|
345
|
+
# Update stats
|
|
346
|
+
if record.pattern not in self._stats:
|
|
347
|
+
self._stats[record.pattern] = PatternStats(pattern=record.pattern)
|
|
348
|
+
self._stats[record.pattern].update(record)
|
|
349
|
+
|
|
350
|
+
# Update context index
|
|
351
|
+
task_type = record.context_features.get("task_type", "")
|
|
352
|
+
self._context_index[task_type].append(len(self._records) - 1)
|
|
353
|
+
|
|
354
|
+
self._dirty = True
|
|
355
|
+
|
|
356
|
+
# Auto-save periodically
|
|
357
|
+
if len(self._records) % 10 == 0:
|
|
358
|
+
self.save()
|
|
359
|
+
|
|
360
|
+
def get_stats(self, pattern: str) -> PatternStats | None:
|
|
361
|
+
"""Get statistics for a pattern.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
pattern: Pattern name
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
PatternStats or None if not tracked
|
|
368
|
+
"""
|
|
369
|
+
return self._stats.get(pattern)
|
|
370
|
+
|
|
371
|
+
def iter_all_stats(self) -> Iterator[PatternStats]:
|
|
372
|
+
"""Iterate over all pattern statistics (memory-efficient).
|
|
373
|
+
|
|
374
|
+
Yields patterns in arbitrary order. For sorted results,
|
|
375
|
+
use get_all_stats().
|
|
376
|
+
"""
|
|
377
|
+
yield from self._stats.values()
|
|
378
|
+
|
|
379
|
+
def get_all_stats(self) -> list[PatternStats]:
|
|
380
|
+
"""Get all pattern statistics sorted by success rate.
|
|
381
|
+
|
|
382
|
+
Note: For large pattern sets, prefer iter_all_stats() when
|
|
383
|
+
you don't need sorted results.
|
|
384
|
+
"""
|
|
385
|
+
return sorted(
|
|
386
|
+
self.iter_all_stats(),
|
|
387
|
+
key=lambda s: s.success_rate,
|
|
388
|
+
reverse=True,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
def find_similar_records(
|
|
392
|
+
self, signature: ContextSignature, limit: int = 10
|
|
393
|
+
) -> list[tuple[ExecutionRecord, float]]:
|
|
394
|
+
"""Find records with similar context.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
signature: Context signature to match
|
|
398
|
+
limit: Maximum records to return
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
List of (record, similarity_score) tuples
|
|
402
|
+
"""
|
|
403
|
+
scored: list[tuple[ExecutionRecord, float]] = []
|
|
404
|
+
|
|
405
|
+
for record in self._records:
|
|
406
|
+
record_sig = ContextSignature(
|
|
407
|
+
task_type=record.context_features.get("task_type", ""),
|
|
408
|
+
agent_count=record.context_features.get("agent_count", 0),
|
|
409
|
+
has_conditions=record.context_features.get("has_conditions", False),
|
|
410
|
+
has_nesting=record.context_features.get("has_nesting", False),
|
|
411
|
+
priority=record.context_features.get("priority", "normal"),
|
|
412
|
+
)
|
|
413
|
+
score = signature.similarity(record_sig)
|
|
414
|
+
if score > 0.3: # Minimum threshold
|
|
415
|
+
scored.append((record, score))
|
|
416
|
+
|
|
417
|
+
# Sort by similarity and return top results
|
|
418
|
+
scored.sort(key=lambda x: x[1], reverse=True)
|
|
419
|
+
return scored[:limit]
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
# =============================================================================
|
|
423
|
+
# Recommendation Engine
|
|
424
|
+
# =============================================================================
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
@dataclass
|
|
428
|
+
class PatternRecommendation:
|
|
429
|
+
"""A pattern recommendation.
|
|
430
|
+
|
|
431
|
+
Attributes:
|
|
432
|
+
pattern: Recommended pattern name
|
|
433
|
+
confidence: Confidence in recommendation (0.0 - 1.0)
|
|
434
|
+
reason: Why this pattern was recommended
|
|
435
|
+
expected_success_rate: Predicted success rate
|
|
436
|
+
expected_duration: Predicted duration
|
|
437
|
+
"""
|
|
438
|
+
|
|
439
|
+
pattern: str
|
|
440
|
+
confidence: float
|
|
441
|
+
reason: str
|
|
442
|
+
expected_success_rate: float = 0.0
|
|
443
|
+
expected_duration: float = 0.0
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
class PatternRecommender:
|
|
447
|
+
"""Hybrid recommendation engine for patterns.
|
|
448
|
+
|
|
449
|
+
Uses similarity matching first, falls back to statistical ranking.
|
|
450
|
+
"""
|
|
451
|
+
|
|
452
|
+
def __init__(self, store: LearningStore):
|
|
453
|
+
"""Initialize recommender.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
store: Learning store with historical data
|
|
457
|
+
"""
|
|
458
|
+
self.store = store
|
|
459
|
+
|
|
460
|
+
def recommend(self, context: dict[str, Any], top_k: int = 3) -> list[PatternRecommendation]:
|
|
461
|
+
"""Recommend patterns for a context.
|
|
462
|
+
|
|
463
|
+
Uses hybrid approach:
|
|
464
|
+
1. Find similar past contexts
|
|
465
|
+
2. Recommend patterns that worked for them
|
|
466
|
+
3. Fall back to overall statistics if no matches
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
context: Current execution context
|
|
470
|
+
top_k: Number of recommendations to return
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
List of PatternRecommendation
|
|
474
|
+
"""
|
|
475
|
+
signature = ContextSignature.from_context(context)
|
|
476
|
+
recommendations: list[PatternRecommendation] = []
|
|
477
|
+
|
|
478
|
+
# Phase 1: Similarity matching
|
|
479
|
+
similar = self.store.find_similar_records(signature, limit=20)
|
|
480
|
+
if similar:
|
|
481
|
+
recommendations = self._recommend_from_similar(similar, top_k)
|
|
482
|
+
|
|
483
|
+
# Phase 2: Statistical fallback
|
|
484
|
+
if len(recommendations) < top_k:
|
|
485
|
+
statistical = self._recommend_statistical(top_k - len(recommendations))
|
|
486
|
+
recommendations.extend(statistical)
|
|
487
|
+
|
|
488
|
+
return recommendations[:top_k]
|
|
489
|
+
|
|
490
|
+
def _recommend_from_similar(
|
|
491
|
+
self, similar: list[tuple[ExecutionRecord, float]], top_k: int
|
|
492
|
+
) -> list[PatternRecommendation]:
|
|
493
|
+
"""Generate recommendations from similar records.
|
|
494
|
+
|
|
495
|
+
Args:
|
|
496
|
+
similar: List of (record, similarity) tuples
|
|
497
|
+
top_k: Number of recommendations
|
|
498
|
+
|
|
499
|
+
Returns:
|
|
500
|
+
List of recommendations
|
|
501
|
+
"""
|
|
502
|
+
# Aggregate by pattern
|
|
503
|
+
pattern_scores: dict[str, dict[str, Any]] = defaultdict(
|
|
504
|
+
lambda: {"total_similarity": 0, "success_similarity": 0, "count": 0}
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
for record, similarity in similar:
|
|
508
|
+
pattern_scores[record.pattern]["count"] += 1
|
|
509
|
+
pattern_scores[record.pattern]["total_similarity"] += similarity
|
|
510
|
+
if record.success:
|
|
511
|
+
pattern_scores[record.pattern]["success_similarity"] += similarity
|
|
512
|
+
|
|
513
|
+
# Calculate weighted success rate
|
|
514
|
+
recommendations = []
|
|
515
|
+
for pattern, scores in pattern_scores.items():
|
|
516
|
+
if scores["total_similarity"] > 0:
|
|
517
|
+
weighted_success = scores["success_similarity"] / scores["total_similarity"]
|
|
518
|
+
stats = self.store.get_stats(pattern)
|
|
519
|
+
|
|
520
|
+
recommendations.append(
|
|
521
|
+
PatternRecommendation(
|
|
522
|
+
pattern=pattern,
|
|
523
|
+
confidence=min(weighted_success, 0.95),
|
|
524
|
+
reason=f"Worked in {scores['count']} similar contexts",
|
|
525
|
+
expected_success_rate=stats.success_rate if stats else 0,
|
|
526
|
+
expected_duration=stats.avg_duration if stats else 0,
|
|
527
|
+
)
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
# Sort by confidence
|
|
531
|
+
recommendations.sort(key=lambda r: r.confidence, reverse=True)
|
|
532
|
+
return recommendations[:top_k]
|
|
533
|
+
|
|
534
|
+
def _recommend_statistical(self, top_k: int) -> list[PatternRecommendation]:
|
|
535
|
+
"""Generate recommendations from overall statistics.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
top_k: Number of recommendations
|
|
539
|
+
|
|
540
|
+
Returns:
|
|
541
|
+
List of recommendations based on global stats
|
|
542
|
+
"""
|
|
543
|
+
all_stats = self.store.get_all_stats()
|
|
544
|
+
recommendations = []
|
|
545
|
+
|
|
546
|
+
for stats in all_stats[:top_k]:
|
|
547
|
+
if stats.total_executions >= 3: # Minimum sample size
|
|
548
|
+
recommendations.append(
|
|
549
|
+
PatternRecommendation(
|
|
550
|
+
pattern=stats.pattern,
|
|
551
|
+
confidence=stats.success_rate * 0.8, # Slight penalty
|
|
552
|
+
reason=f"High overall success rate ({stats.success_rate:.0%})",
|
|
553
|
+
expected_success_rate=stats.success_rate,
|
|
554
|
+
expected_duration=stats.avg_duration,
|
|
555
|
+
)
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
return recommendations
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
# =============================================================================
|
|
562
|
+
# Main Interface
|
|
563
|
+
# =============================================================================
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
class PatternLearner:
|
|
567
|
+
"""Main interface for the learning grammar system.
|
|
568
|
+
|
|
569
|
+
Provides a simple API for recording executions and getting recommendations.
|
|
570
|
+
|
|
571
|
+
Example:
|
|
572
|
+
>>> learner = PatternLearner()
|
|
573
|
+
>>> # Record an execution
|
|
574
|
+
>>> learner.record(
|
|
575
|
+
... pattern="sequential",
|
|
576
|
+
... success=True,
|
|
577
|
+
... duration=2.5,
|
|
578
|
+
... cost=0.05,
|
|
579
|
+
... context={"task_type": "code_review"}
|
|
580
|
+
... )
|
|
581
|
+
>>> # Get recommendations
|
|
582
|
+
>>> recs = learner.recommend({"task_type": "code_review"})
|
|
583
|
+
>>> print(recs[0].pattern, recs[0].confidence)
|
|
584
|
+
"""
|
|
585
|
+
|
|
586
|
+
def __init__(self, storage_path: str | None = None):
|
|
587
|
+
"""Initialize pattern learner.
|
|
588
|
+
|
|
589
|
+
Args:
|
|
590
|
+
storage_path: Path for persistence (default: patterns/learning_memory.json)
|
|
591
|
+
"""
|
|
592
|
+
self.store = LearningStore(storage_path)
|
|
593
|
+
self.recommender = PatternRecommender(self.store)
|
|
594
|
+
|
|
595
|
+
def record(
|
|
596
|
+
self,
|
|
597
|
+
pattern: str,
|
|
598
|
+
success: bool,
|
|
599
|
+
duration: float,
|
|
600
|
+
cost: float = 0.0,
|
|
601
|
+
confidence: float = 0.0,
|
|
602
|
+
context: dict[str, Any] | None = None,
|
|
603
|
+
) -> None:
|
|
604
|
+
"""Record a pattern execution.
|
|
605
|
+
|
|
606
|
+
Args:
|
|
607
|
+
pattern: Pattern/strategy name
|
|
608
|
+
success: Whether execution succeeded
|
|
609
|
+
duration: Execution duration in seconds
|
|
610
|
+
cost: Estimated cost
|
|
611
|
+
confidence: Aggregate confidence score
|
|
612
|
+
context: Execution context (for similarity matching)
|
|
613
|
+
"""
|
|
614
|
+
record = ExecutionRecord(
|
|
615
|
+
pattern=pattern,
|
|
616
|
+
success=success,
|
|
617
|
+
duration_seconds=duration,
|
|
618
|
+
cost=cost,
|
|
619
|
+
confidence=confidence,
|
|
620
|
+
context_features=context or {},
|
|
621
|
+
)
|
|
622
|
+
self.store.add_record(record)
|
|
623
|
+
logger.debug(f"Recorded {pattern} execution: success={success}")
|
|
624
|
+
|
|
625
|
+
def recommend(self, context: dict[str, Any], top_k: int = 3) -> list[PatternRecommendation]:
|
|
626
|
+
"""Get pattern recommendations for a context.
|
|
627
|
+
|
|
628
|
+
Args:
|
|
629
|
+
context: Execution context
|
|
630
|
+
top_k: Number of recommendations
|
|
631
|
+
|
|
632
|
+
Returns:
|
|
633
|
+
List of PatternRecommendation
|
|
634
|
+
"""
|
|
635
|
+
return self.recommender.recommend(context, top_k)
|
|
636
|
+
|
|
637
|
+
def get_stats(self, pattern: str) -> PatternStats | None:
|
|
638
|
+
"""Get statistics for a specific pattern.
|
|
639
|
+
|
|
640
|
+
Args:
|
|
641
|
+
pattern: Pattern name
|
|
642
|
+
|
|
643
|
+
Returns:
|
|
644
|
+
PatternStats or None
|
|
645
|
+
"""
|
|
646
|
+
return self.store.get_stats(pattern)
|
|
647
|
+
|
|
648
|
+
def get_all_stats(self) -> list[PatternStats]:
|
|
649
|
+
"""Get statistics for all patterns.
|
|
650
|
+
|
|
651
|
+
Returns:
|
|
652
|
+
List of PatternStats sorted by success rate
|
|
653
|
+
"""
|
|
654
|
+
return self.store.get_all_stats()
|
|
655
|
+
|
|
656
|
+
def save(self) -> None:
|
|
657
|
+
"""Force save to disk."""
|
|
658
|
+
self.store.save()
|
|
659
|
+
|
|
660
|
+
def report(self) -> str:
|
|
661
|
+
"""Generate a human-readable report of learning data.
|
|
662
|
+
|
|
663
|
+
Returns:
|
|
664
|
+
Formatted report string
|
|
665
|
+
"""
|
|
666
|
+
stats = self.get_all_stats()
|
|
667
|
+
if not stats:
|
|
668
|
+
return "No learning data recorded yet."
|
|
669
|
+
|
|
670
|
+
lines = ["Pattern Learning Report", "=" * 50, ""]
|
|
671
|
+
|
|
672
|
+
for s in stats:
|
|
673
|
+
lines.append(f"Pattern: {s.pattern}")
|
|
674
|
+
lines.append(f" Executions: {s.total_executions}")
|
|
675
|
+
lines.append(f" Success Rate: {s.success_rate:.1%}")
|
|
676
|
+
lines.append(f" Avg Duration: {s.avg_duration:.2f}s")
|
|
677
|
+
lines.append(f" Avg Cost: ${s.avg_cost:.4f}")
|
|
678
|
+
lines.append("")
|
|
679
|
+
|
|
680
|
+
return "\n".join(lines)
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
# Module-level singleton for convenience
|
|
684
|
+
_default_learner: PatternLearner | None = None
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
def get_learner() -> PatternLearner:
|
|
688
|
+
"""Get the default pattern learner instance.
|
|
689
|
+
|
|
690
|
+
Returns:
|
|
691
|
+
PatternLearner singleton
|
|
692
|
+
"""
|
|
693
|
+
global _default_learner
|
|
694
|
+
if _default_learner is None:
|
|
695
|
+
_default_learner = PatternLearner()
|
|
696
|
+
return _default_learner
|