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,320 @@
|
|
|
1
|
+
"""Resilient Agent Wrapper
|
|
2
|
+
|
|
3
|
+
Applies production-ready resilience patterns (circuit breaker, retry, timeout,
|
|
4
|
+
fallback) to any agent created by the Agent Factory.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from attune_llm.agent_factory import AgentFactory
|
|
8
|
+
from attune_llm.agent_factory.resilient import ResilientAgent, ResilienceConfig
|
|
9
|
+
|
|
10
|
+
factory = AgentFactory()
|
|
11
|
+
agent = factory.create_agent(name="researcher", role="researcher")
|
|
12
|
+
|
|
13
|
+
# Wrap with resilience
|
|
14
|
+
resilient_agent = ResilientAgent(agent, ResilienceConfig(
|
|
15
|
+
enable_circuit_breaker=True,
|
|
16
|
+
failure_threshold=3,
|
|
17
|
+
enable_retry=True,
|
|
18
|
+
max_attempts=3
|
|
19
|
+
))
|
|
20
|
+
|
|
21
|
+
result = await resilient_agent.invoke("Research AI trends")
|
|
22
|
+
|
|
23
|
+
Copyright 2025 Smart-AI-Memory
|
|
24
|
+
Licensed under Fair Source License 0.9
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import asyncio
|
|
28
|
+
import functools
|
|
29
|
+
import logging
|
|
30
|
+
from dataclasses import dataclass, field
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
from attune_llm.agent_factory.base import AgentConfig, BaseAgent
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class ResilienceConfig:
|
|
40
|
+
"""Configuration for resilience patterns."""
|
|
41
|
+
|
|
42
|
+
# Circuit Breaker
|
|
43
|
+
enable_circuit_breaker: bool = True
|
|
44
|
+
failure_threshold: int = 3
|
|
45
|
+
reset_timeout: float = 60.0
|
|
46
|
+
half_open_max_calls: int = 3
|
|
47
|
+
|
|
48
|
+
# Retry
|
|
49
|
+
enable_retry: bool = True
|
|
50
|
+
max_attempts: int = 2
|
|
51
|
+
initial_delay: float = 1.0
|
|
52
|
+
backoff_factor: float = 2.0
|
|
53
|
+
max_delay: float = 30.0
|
|
54
|
+
jitter: bool = True
|
|
55
|
+
|
|
56
|
+
# Timeout
|
|
57
|
+
enable_timeout: bool = True
|
|
58
|
+
timeout_seconds: float = 30.0
|
|
59
|
+
|
|
60
|
+
# Fallback
|
|
61
|
+
enable_fallback: bool = False
|
|
62
|
+
fallback_value: Any = field(
|
|
63
|
+
default_factory=lambda: {
|
|
64
|
+
"output": "Service temporarily unavailable",
|
|
65
|
+
"metadata": {"fallback": True},
|
|
66
|
+
},
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def from_agent_config(cls, config: AgentConfig) -> "ResilienceConfig":
|
|
71
|
+
"""Create ResilienceConfig from AgentConfig resilience fields."""
|
|
72
|
+
return cls(
|
|
73
|
+
enable_circuit_breaker=getattr(config, "resilience_enabled", False),
|
|
74
|
+
failure_threshold=getattr(config, "circuit_breaker_threshold", 3),
|
|
75
|
+
enable_retry=getattr(config, "resilience_enabled", False),
|
|
76
|
+
max_attempts=getattr(config, "retry_max_attempts", 2),
|
|
77
|
+
enable_timeout=getattr(config, "resilience_enabled", False),
|
|
78
|
+
timeout_seconds=getattr(config, "timeout_seconds", 30.0),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class ResilientAgent(BaseAgent):
|
|
83
|
+
"""Agent wrapper that applies resilience patterns.
|
|
84
|
+
|
|
85
|
+
Wraps any BaseAgent implementation with:
|
|
86
|
+
- Circuit breaker: Prevents cascading failures
|
|
87
|
+
- Retry with backoff: Handles transient errors
|
|
88
|
+
- Timeout: Prevents hanging operations
|
|
89
|
+
- Fallback: Graceful degradation
|
|
90
|
+
|
|
91
|
+
The wrapper preserves the underlying agent's interface while adding
|
|
92
|
+
fault tolerance capabilities.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def __init__(self, agent: BaseAgent, config: ResilienceConfig | None = None):
|
|
96
|
+
"""Initialize resilient agent wrapper.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
agent: The underlying agent to wrap
|
|
100
|
+
config: Resilience configuration (uses defaults if not provided)
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
# Initialize with wrapped agent's config
|
|
104
|
+
super().__init__(agent.config)
|
|
105
|
+
self._wrapped = agent
|
|
106
|
+
self._resilience_config = config or ResilienceConfig()
|
|
107
|
+
self._circuit_breaker: Any = None
|
|
108
|
+
self._setup_resilience()
|
|
109
|
+
|
|
110
|
+
def _setup_resilience(self) -> None:
|
|
111
|
+
"""Set up resilience decorators based on config."""
|
|
112
|
+
config = self._resilience_config
|
|
113
|
+
|
|
114
|
+
# Set up circuit breaker if enabled
|
|
115
|
+
if config.enable_circuit_breaker:
|
|
116
|
+
try:
|
|
117
|
+
from attune.resilience import CircuitBreaker
|
|
118
|
+
|
|
119
|
+
self._circuit_breaker = CircuitBreaker(
|
|
120
|
+
name=f"agent_{self.name}",
|
|
121
|
+
failure_threshold=config.failure_threshold,
|
|
122
|
+
reset_timeout=config.reset_timeout,
|
|
123
|
+
half_open_max_calls=config.half_open_max_calls,
|
|
124
|
+
)
|
|
125
|
+
except ImportError:
|
|
126
|
+
logger.warning("attune.resilience not available, circuit breaker disabled")
|
|
127
|
+
|
|
128
|
+
async def invoke(self, input_data: str | dict, context: dict | None = None) -> dict:
|
|
129
|
+
"""Invoke the agent with resilience patterns applied.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
input_data: User input or structured data
|
|
133
|
+
context: Optional context (previous results, shared state)
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Dict with at least {"output": str, "metadata": dict}
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
CircuitOpenError: If circuit breaker is open
|
|
140
|
+
asyncio.TimeoutError: If operation times out and no fallback
|
|
141
|
+
Exception: If all retries exhausted and no fallback
|
|
142
|
+
|
|
143
|
+
"""
|
|
144
|
+
config = self._resilience_config
|
|
145
|
+
|
|
146
|
+
# Build the resilient call chain
|
|
147
|
+
async def _call():
|
|
148
|
+
return await self._wrapped.invoke(input_data, context)
|
|
149
|
+
|
|
150
|
+
# Apply timeout
|
|
151
|
+
if config.enable_timeout:
|
|
152
|
+
_call = self._with_timeout(_call, config.timeout_seconds)
|
|
153
|
+
|
|
154
|
+
# Apply retry
|
|
155
|
+
if config.enable_retry:
|
|
156
|
+
_call = self._with_retry(
|
|
157
|
+
_call,
|
|
158
|
+
config.max_attempts,
|
|
159
|
+
config.initial_delay,
|
|
160
|
+
config.backoff_factor,
|
|
161
|
+
config.max_delay,
|
|
162
|
+
config.jitter,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Apply circuit breaker
|
|
166
|
+
if config.enable_circuit_breaker and self._circuit_breaker:
|
|
167
|
+
_call = self._with_circuit_breaker(_call)
|
|
168
|
+
|
|
169
|
+
# Execute with optional fallback
|
|
170
|
+
try:
|
|
171
|
+
result = await _call()
|
|
172
|
+
# Add resilience metadata
|
|
173
|
+
if "metadata" in result:
|
|
174
|
+
result["metadata"]["resilience"] = {
|
|
175
|
+
"circuit_breaker_enabled": config.enable_circuit_breaker,
|
|
176
|
+
"retry_enabled": config.enable_retry,
|
|
177
|
+
"timeout_enabled": config.enable_timeout,
|
|
178
|
+
}
|
|
179
|
+
return dict(result)
|
|
180
|
+
except Exception as e:
|
|
181
|
+
if config.enable_fallback:
|
|
182
|
+
logger.warning(f"Agent {self.name} failed, using fallback: {e}")
|
|
183
|
+
fallback = config.fallback_value
|
|
184
|
+
if callable(fallback):
|
|
185
|
+
return dict(fallback(input_data, context, e))
|
|
186
|
+
return dict(fallback) if isinstance(fallback, dict) else {"output": fallback}
|
|
187
|
+
raise
|
|
188
|
+
|
|
189
|
+
async def stream(self, input_data: str | dict, context: dict | None = None):
|
|
190
|
+
"""Stream agent response with resilience patterns.
|
|
191
|
+
|
|
192
|
+
Note: Streaming has limited resilience support (timeout only).
|
|
193
|
+
Circuit breaker and retry work at the full response level.
|
|
194
|
+
"""
|
|
195
|
+
config = self._resilience_config
|
|
196
|
+
|
|
197
|
+
async def _stream():
|
|
198
|
+
async for chunk in self._wrapped.stream(input_data, context):
|
|
199
|
+
yield chunk
|
|
200
|
+
|
|
201
|
+
# Apply timeout to entire stream
|
|
202
|
+
if config.enable_timeout:
|
|
203
|
+
try:
|
|
204
|
+
async with asyncio.timeout(config.timeout_seconds): # type: ignore[attr-defined]
|
|
205
|
+
async for chunk in _stream():
|
|
206
|
+
yield chunk
|
|
207
|
+
except asyncio.TimeoutError:
|
|
208
|
+
if config.enable_fallback:
|
|
209
|
+
yield {"output": "Stream timed out", "metadata": {"fallback": True}}
|
|
210
|
+
else:
|
|
211
|
+
raise
|
|
212
|
+
else:
|
|
213
|
+
async for chunk in _stream():
|
|
214
|
+
yield chunk
|
|
215
|
+
|
|
216
|
+
def _with_timeout(self, func, timeout_seconds: float):
|
|
217
|
+
"""Wrap function with timeout."""
|
|
218
|
+
|
|
219
|
+
@functools.wraps(func)
|
|
220
|
+
async def wrapper():
|
|
221
|
+
return await asyncio.wait_for(func(), timeout=timeout_seconds)
|
|
222
|
+
|
|
223
|
+
return wrapper
|
|
224
|
+
|
|
225
|
+
def _with_retry(
|
|
226
|
+
self,
|
|
227
|
+
func,
|
|
228
|
+
max_attempts: int,
|
|
229
|
+
initial_delay: float,
|
|
230
|
+
backoff_factor: float,
|
|
231
|
+
max_delay: float,
|
|
232
|
+
jitter: bool,
|
|
233
|
+
):
|
|
234
|
+
"""Wrap function with retry logic."""
|
|
235
|
+
import random
|
|
236
|
+
|
|
237
|
+
@functools.wraps(func)
|
|
238
|
+
async def wrapper():
|
|
239
|
+
last_exception = None
|
|
240
|
+
delay = initial_delay
|
|
241
|
+
|
|
242
|
+
for attempt in range(max_attempts):
|
|
243
|
+
try:
|
|
244
|
+
return await func()
|
|
245
|
+
except asyncio.TimeoutError:
|
|
246
|
+
# Don't retry timeouts by default
|
|
247
|
+
raise
|
|
248
|
+
except Exception as e:
|
|
249
|
+
last_exception = e
|
|
250
|
+
if attempt < max_attempts - 1:
|
|
251
|
+
actual_delay = delay
|
|
252
|
+
if jitter:
|
|
253
|
+
actual_delay = delay * (0.5 + random.random())
|
|
254
|
+
actual_delay = min(actual_delay, max_delay)
|
|
255
|
+
logger.debug(
|
|
256
|
+
f"Agent {self.name} attempt {attempt + 1} failed, "
|
|
257
|
+
f"retrying in {actual_delay:.2f}s: {e}",
|
|
258
|
+
)
|
|
259
|
+
await asyncio.sleep(actual_delay)
|
|
260
|
+
delay = min(delay * backoff_factor, max_delay)
|
|
261
|
+
|
|
262
|
+
raise last_exception
|
|
263
|
+
|
|
264
|
+
return wrapper
|
|
265
|
+
|
|
266
|
+
def _with_circuit_breaker(self, func):
|
|
267
|
+
"""Wrap function with circuit breaker."""
|
|
268
|
+
|
|
269
|
+
@functools.wraps(func)
|
|
270
|
+
async def wrapper():
|
|
271
|
+
from attune.resilience import CircuitOpenError
|
|
272
|
+
|
|
273
|
+
# Check if circuit is open (failing fast)
|
|
274
|
+
if self._circuit_breaker.is_open:
|
|
275
|
+
reset_time = self._circuit_breaker.get_time_until_reset()
|
|
276
|
+
raise CircuitOpenError(
|
|
277
|
+
name=f"agent_{self.name}",
|
|
278
|
+
reset_time=reset_time,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
try:
|
|
282
|
+
result = await func()
|
|
283
|
+
self._circuit_breaker.record_success()
|
|
284
|
+
return result
|
|
285
|
+
except Exception as e:
|
|
286
|
+
self._circuit_breaker.record_failure(e)
|
|
287
|
+
raise
|
|
288
|
+
|
|
289
|
+
return wrapper
|
|
290
|
+
|
|
291
|
+
# Delegate other methods to wrapped agent
|
|
292
|
+
def add_tool(self, tool: Any) -> None:
|
|
293
|
+
"""Add a tool to the wrapped agent."""
|
|
294
|
+
self._wrapped.add_tool(tool)
|
|
295
|
+
|
|
296
|
+
def get_conversation_history(self) -> list[dict]:
|
|
297
|
+
"""Get conversation history from wrapped agent."""
|
|
298
|
+
return self._wrapped.get_conversation_history()
|
|
299
|
+
|
|
300
|
+
def clear_history(self) -> None:
|
|
301
|
+
"""Clear conversation history in wrapped agent."""
|
|
302
|
+
self._wrapped.clear_history()
|
|
303
|
+
|
|
304
|
+
@property
|
|
305
|
+
def model(self) -> str:
|
|
306
|
+
"""Get the model being used by wrapped agent."""
|
|
307
|
+
return self._wrapped.model
|
|
308
|
+
|
|
309
|
+
@property
|
|
310
|
+
def circuit_state(self) -> str | None:
|
|
311
|
+
"""Get current circuit breaker state."""
|
|
312
|
+
if self._circuit_breaker:
|
|
313
|
+
return str(self._circuit_breaker.state.value)
|
|
314
|
+
return None
|
|
315
|
+
|
|
316
|
+
def reset_circuit_breaker(self) -> None:
|
|
317
|
+
"""Manually reset the circuit breaker."""
|
|
318
|
+
if self._circuit_breaker:
|
|
319
|
+
self._circuit_breaker.reset()
|
|
320
|
+
logger.info(f"Circuit breaker reset for agent {self.name}")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Markdown Agent System
|
|
2
|
+
|
|
3
|
+
Define agents in Markdown files with YAML frontmatter for portability.
|
|
4
|
+
Integrates with Empathy Framework's UnifiedAgentConfig and model tier system.
|
|
5
|
+
|
|
6
|
+
Markdown agent format inspired by everything-claude-code by Affaan Mustafa.
|
|
7
|
+
See: https://github.com/affaan-m/everything-claude-code (MIT License)
|
|
8
|
+
See: ACKNOWLEDGMENTS.md for full attribution.
|
|
9
|
+
|
|
10
|
+
Copyright 2025 Smart-AI-Memory
|
|
11
|
+
Licensed under Fair Source License 0.9
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from attune_llm.agents_md.loader import AgentLoader
|
|
15
|
+
from attune_llm.agents_md.parser import MarkdownAgentParser
|
|
16
|
+
from attune_llm.agents_md.registry import AgentRegistry
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"MarkdownAgentParser",
|
|
20
|
+
"AgentLoader",
|
|
21
|
+
"AgentRegistry",
|
|
22
|
+
]
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Agent Loader
|
|
2
|
+
|
|
3
|
+
Loads agents from directory structures.
|
|
4
|
+
|
|
5
|
+
Copyright 2025 Smart-AI-Memory
|
|
6
|
+
Licensed under Fair Source License 0.9
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from collections.abc import Iterator
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from attune_llm.agents_md.parser import MarkdownAgentParser
|
|
14
|
+
from attune_llm.config.unified import UnifiedAgentConfig
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AgentLoader:
|
|
20
|
+
"""Loader for discovering and loading markdown agent files.
|
|
21
|
+
|
|
22
|
+
Scans directories for .md files with agent definitions and loads them
|
|
23
|
+
into UnifiedAgentConfig instances.
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
loader = AgentLoader()
|
|
27
|
+
|
|
28
|
+
# Load a single agent
|
|
29
|
+
config = loader.load("agents/architect.md")
|
|
30
|
+
|
|
31
|
+
# Load all agents from a directory
|
|
32
|
+
agents = loader.load_directory("agents/")
|
|
33
|
+
|
|
34
|
+
# Discover and iterate agents lazily
|
|
35
|
+
for config in loader.discover("agents/"):
|
|
36
|
+
print(config.name)
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, parser: MarkdownAgentParser | None = None):
|
|
40
|
+
"""Initialize the loader.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
parser: Optional custom parser instance
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
self.parser = parser or MarkdownAgentParser()
|
|
47
|
+
|
|
48
|
+
def load(self, file_path: str | Path) -> UnifiedAgentConfig:
|
|
49
|
+
"""Load a single agent file.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
file_path: Path to the agent markdown file
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
UnifiedAgentConfig instance
|
|
56
|
+
|
|
57
|
+
"""
|
|
58
|
+
return self.parser.parse_file(file_path)
|
|
59
|
+
|
|
60
|
+
def load_directory(
|
|
61
|
+
self,
|
|
62
|
+
directory: str | Path,
|
|
63
|
+
recursive: bool = False,
|
|
64
|
+
) -> dict[str, UnifiedAgentConfig]:
|
|
65
|
+
"""Load all agents from a directory.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
directory: Directory to scan for .md files
|
|
69
|
+
recursive: If True, scan subdirectories
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Dictionary mapping agent names to configs
|
|
73
|
+
|
|
74
|
+
"""
|
|
75
|
+
agents = {}
|
|
76
|
+
|
|
77
|
+
for config in self.discover(directory, recursive=recursive):
|
|
78
|
+
if config.name in agents:
|
|
79
|
+
logger.warning(
|
|
80
|
+
"Duplicate agent name '%s' - keeping first occurrence",
|
|
81
|
+
config.name,
|
|
82
|
+
)
|
|
83
|
+
continue
|
|
84
|
+
agents[config.name] = config
|
|
85
|
+
|
|
86
|
+
logger.info("Loaded %d agent(s) from %s", len(agents), directory)
|
|
87
|
+
return agents
|
|
88
|
+
|
|
89
|
+
def discover(
|
|
90
|
+
self,
|
|
91
|
+
directory: str | Path,
|
|
92
|
+
recursive: bool = False,
|
|
93
|
+
) -> Iterator[UnifiedAgentConfig]:
|
|
94
|
+
"""Discover and yield agents from a directory.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
directory: Directory to scan
|
|
98
|
+
recursive: If True, scan subdirectories
|
|
99
|
+
|
|
100
|
+
Yields:
|
|
101
|
+
UnifiedAgentConfig instances
|
|
102
|
+
|
|
103
|
+
"""
|
|
104
|
+
directory = Path(directory)
|
|
105
|
+
|
|
106
|
+
if not directory.exists():
|
|
107
|
+
logger.warning("Agent directory not found: %s", directory)
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
if not directory.is_dir():
|
|
111
|
+
raise ValueError(f"Not a directory: {directory}")
|
|
112
|
+
|
|
113
|
+
# Get pattern for globbing
|
|
114
|
+
pattern = "**/*.md" if recursive else "*.md"
|
|
115
|
+
|
|
116
|
+
for file_path in sorted(directory.glob(pattern)):
|
|
117
|
+
if not file_path.is_file():
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
# Skip files that don't look like agent definitions
|
|
121
|
+
if file_path.name.startswith("_"):
|
|
122
|
+
continue
|
|
123
|
+
if file_path.name.upper() in ("README.MD", "CHANGELOG.MD"):
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
config = self.parser.parse_file(file_path)
|
|
128
|
+
yield config
|
|
129
|
+
except ValueError as e:
|
|
130
|
+
logger.warning("Skipping invalid agent file %s: %s", file_path, e)
|
|
131
|
+
except Exception as e:
|
|
132
|
+
logger.error("Error loading agent file %s: %s", file_path, e)
|
|
133
|
+
|
|
134
|
+
def validate_directory(
|
|
135
|
+
self,
|
|
136
|
+
directory: str | Path,
|
|
137
|
+
recursive: bool = False,
|
|
138
|
+
) -> dict[str, list[str]]:
|
|
139
|
+
"""Validate all agent files in a directory.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
directory: Directory to validate
|
|
143
|
+
recursive: If True, scan subdirectories
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Dictionary mapping file paths to lists of errors
|
|
147
|
+
|
|
148
|
+
"""
|
|
149
|
+
directory = Path(directory)
|
|
150
|
+
results = {}
|
|
151
|
+
|
|
152
|
+
pattern = "**/*.md" if recursive else "*.md"
|
|
153
|
+
|
|
154
|
+
for file_path in sorted(directory.glob(pattern)):
|
|
155
|
+
if not file_path.is_file():
|
|
156
|
+
continue
|
|
157
|
+
if file_path.name.startswith("_"):
|
|
158
|
+
continue
|
|
159
|
+
if file_path.name.upper() in ("README.MD", "CHANGELOG.MD"):
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
errors = self.parser.validate_file(file_path)
|
|
163
|
+
if errors:
|
|
164
|
+
results[str(file_path)] = errors
|
|
165
|
+
|
|
166
|
+
return results
|
|
167
|
+
|
|
168
|
+
def get_agent_names(
|
|
169
|
+
self,
|
|
170
|
+
directory: str | Path,
|
|
171
|
+
recursive: bool = False,
|
|
172
|
+
) -> list[str]:
|
|
173
|
+
"""Get list of agent names in a directory without fully loading.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
directory: Directory to scan
|
|
177
|
+
recursive: If True, scan subdirectories
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
List of agent names
|
|
181
|
+
|
|
182
|
+
"""
|
|
183
|
+
names = []
|
|
184
|
+
for config in self.discover(directory, recursive=recursive):
|
|
185
|
+
names.append(config.name)
|
|
186
|
+
return names
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def load_agents_from_paths(
|
|
190
|
+
paths: list[str | Path],
|
|
191
|
+
parser: MarkdownAgentParser | None = None,
|
|
192
|
+
) -> dict[str, UnifiedAgentConfig]:
|
|
193
|
+
"""Load agents from multiple paths (files or directories).
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
paths: List of file or directory paths
|
|
197
|
+
parser: Optional custom parser
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Dictionary mapping agent names to configs
|
|
201
|
+
|
|
202
|
+
"""
|
|
203
|
+
loader = AgentLoader(parser=parser)
|
|
204
|
+
agents = {}
|
|
205
|
+
|
|
206
|
+
for path in paths:
|
|
207
|
+
path = Path(path)
|
|
208
|
+
|
|
209
|
+
if path.is_file():
|
|
210
|
+
config = loader.load(path)
|
|
211
|
+
agents[config.name] = config
|
|
212
|
+
elif path.is_dir():
|
|
213
|
+
dir_agents = loader.load_directory(path)
|
|
214
|
+
agents.update(dir_agents)
|
|
215
|
+
else:
|
|
216
|
+
logger.warning("Path not found: %s", path)
|
|
217
|
+
|
|
218
|
+
return agents
|