kubiya-control-plane-api 0.9.15__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.
- control_plane_api/LICENSE +676 -0
- control_plane_api/README.md +350 -0
- control_plane_api/__init__.py +4 -0
- control_plane_api/__version__.py +8 -0
- control_plane_api/alembic/README +1 -0
- control_plane_api/alembic/env.py +121 -0
- control_plane_api/alembic/script.py.mako +28 -0
- control_plane_api/alembic/versions/2613c65c3dbe_initial_database_setup.py +32 -0
- control_plane_api/alembic/versions/2df520d4927d_merge_heads.py +28 -0
- control_plane_api/alembic/versions/43abf98d6a01_add_paused_status_to_executions.py +73 -0
- control_plane_api/alembic/versions/6289854264cb_merge_multiple_heads.py +28 -0
- control_plane_api/alembic/versions/6a4d4dc3d8dc_generate_execution_transitions.py +50 -0
- control_plane_api/alembic/versions/87d11cf0a783_add_disconnected_status_to_worker_.py +44 -0
- control_plane_api/alembic/versions/add_ephemeral_queue_support.py +85 -0
- control_plane_api/alembic/versions/add_model_type_to_llm_models.py +31 -0
- control_plane_api/alembic/versions/add_plan_executions_table.py +114 -0
- control_plane_api/alembic/versions/add_trace_span_tables.py +154 -0
- control_plane_api/alembic/versions/add_user_info_to_traces.py +36 -0
- control_plane_api/alembic/versions/adjusting_foreign_keys.py +32 -0
- control_plane_api/alembic/versions/b4983d976db2_initial_tables.py +1128 -0
- control_plane_api/alembic/versions/d181a3b40e71_rename_custom_metadata_to_metadata_in_.py +50 -0
- control_plane_api/alembic/versions/df9117888e82_add_missing_columns.py +82 -0
- control_plane_api/alembic/versions/f25de6ad895a_missing_migrations.py +34 -0
- control_plane_api/alembic/versions/f71305fb69b9_fix_ephemeral_queue_deletion_foreign_key.py +54 -0
- control_plane_api/alembic/versions/mark_local_exec_queues_as_ephemeral.py +68 -0
- control_plane_api/alembic.ini +148 -0
- control_plane_api/api/index.py +12 -0
- control_plane_api/app/__init__.py +11 -0
- control_plane_api/app/activities/__init__.py +20 -0
- control_plane_api/app/activities/agent_activities.py +384 -0
- control_plane_api/app/activities/plan_generation_activities.py +499 -0
- control_plane_api/app/activities/team_activities.py +424 -0
- control_plane_api/app/activities/temporal_cloud_activities.py +588 -0
- control_plane_api/app/config/__init__.py +35 -0
- control_plane_api/app/config/api_config.py +469 -0
- control_plane_api/app/config/config_loader.py +224 -0
- control_plane_api/app/config/model_pricing.py +323 -0
- control_plane_api/app/config/storage_config.py +159 -0
- control_plane_api/app/config.py +115 -0
- control_plane_api/app/controllers/__init__.py +0 -0
- control_plane_api/app/controllers/execution_environment_controller.py +1315 -0
- control_plane_api/app/database.py +135 -0
- control_plane_api/app/exceptions.py +408 -0
- control_plane_api/app/lib/__init__.py +11 -0
- control_plane_api/app/lib/environment.py +65 -0
- control_plane_api/app/lib/event_bus/__init__.py +17 -0
- control_plane_api/app/lib/event_bus/base.py +136 -0
- control_plane_api/app/lib/event_bus/manager.py +335 -0
- control_plane_api/app/lib/event_bus/providers/__init__.py +6 -0
- control_plane_api/app/lib/event_bus/providers/http_provider.py +166 -0
- control_plane_api/app/lib/event_bus/providers/nats_provider.py +324 -0
- control_plane_api/app/lib/event_bus/providers/redis_provider.py +233 -0
- control_plane_api/app/lib/event_bus/providers/websocket_provider.py +497 -0
- control_plane_api/app/lib/job_executor.py +330 -0
- control_plane_api/app/lib/kubiya_client.py +293 -0
- control_plane_api/app/lib/litellm_pricing.py +166 -0
- control_plane_api/app/lib/mcp_validation.py +163 -0
- control_plane_api/app/lib/nats/__init__.py +13 -0
- control_plane_api/app/lib/nats/credentials_manager.py +288 -0
- control_plane_api/app/lib/nats/listener.py +374 -0
- control_plane_api/app/lib/planning_prompt_builder.py +153 -0
- control_plane_api/app/lib/planning_tools/__init__.py +41 -0
- control_plane_api/app/lib/planning_tools/agents.py +409 -0
- control_plane_api/app/lib/planning_tools/agno_toolkit.py +836 -0
- control_plane_api/app/lib/planning_tools/base.py +119 -0
- control_plane_api/app/lib/planning_tools/cognitive_memory_tools.py +403 -0
- control_plane_api/app/lib/planning_tools/context_graph_tools.py +545 -0
- control_plane_api/app/lib/planning_tools/environments.py +218 -0
- control_plane_api/app/lib/planning_tools/knowledge.py +204 -0
- control_plane_api/app/lib/planning_tools/models.py +93 -0
- control_plane_api/app/lib/planning_tools/planning_service.py +646 -0
- control_plane_api/app/lib/planning_tools/resources.py +242 -0
- control_plane_api/app/lib/planning_tools/teams.py +334 -0
- control_plane_api/app/lib/policy_enforcer_client.py +1016 -0
- control_plane_api/app/lib/redis_client.py +803 -0
- control_plane_api/app/lib/sqlalchemy_utils.py +486 -0
- control_plane_api/app/lib/state_transition_tools/__init__.py +7 -0
- control_plane_api/app/lib/state_transition_tools/execution_context.py +388 -0
- control_plane_api/app/lib/storage/__init__.py +20 -0
- control_plane_api/app/lib/storage/base_provider.py +274 -0
- control_plane_api/app/lib/storage/provider_factory.py +157 -0
- control_plane_api/app/lib/storage/vercel_blob_provider.py +468 -0
- control_plane_api/app/lib/supabase.py +71 -0
- control_plane_api/app/lib/supabase_utils.py +138 -0
- control_plane_api/app/lib/task_planning/__init__.py +138 -0
- control_plane_api/app/lib/task_planning/agent_factory.py +308 -0
- control_plane_api/app/lib/task_planning/agents.py +389 -0
- control_plane_api/app/lib/task_planning/cache.py +218 -0
- control_plane_api/app/lib/task_planning/entity_resolver.py +273 -0
- control_plane_api/app/lib/task_planning/helpers.py +293 -0
- control_plane_api/app/lib/task_planning/hooks.py +474 -0
- control_plane_api/app/lib/task_planning/models.py +503 -0
- control_plane_api/app/lib/task_planning/plan_validator.py +166 -0
- control_plane_api/app/lib/task_planning/planning_workflow.py +2911 -0
- control_plane_api/app/lib/task_planning/runner.py +656 -0
- control_plane_api/app/lib/task_planning/streaming_hook.py +213 -0
- control_plane_api/app/lib/task_planning/workflow.py +424 -0
- control_plane_api/app/lib/templating/__init__.py +88 -0
- control_plane_api/app/lib/templating/compiler.py +278 -0
- control_plane_api/app/lib/templating/engine.py +178 -0
- control_plane_api/app/lib/templating/parsers/__init__.py +29 -0
- control_plane_api/app/lib/templating/parsers/base.py +96 -0
- control_plane_api/app/lib/templating/parsers/env.py +85 -0
- control_plane_api/app/lib/templating/parsers/graph.py +112 -0
- control_plane_api/app/lib/templating/parsers/secret.py +87 -0
- control_plane_api/app/lib/templating/parsers/simple.py +81 -0
- control_plane_api/app/lib/templating/resolver.py +366 -0
- control_plane_api/app/lib/templating/types.py +214 -0
- control_plane_api/app/lib/templating/validator.py +201 -0
- control_plane_api/app/lib/temporal_client.py +232 -0
- control_plane_api/app/lib/temporal_credentials_cache.py +178 -0
- control_plane_api/app/lib/temporal_credentials_service.py +203 -0
- control_plane_api/app/lib/validation/__init__.py +24 -0
- control_plane_api/app/lib/validation/runtime_validation.py +388 -0
- control_plane_api/app/main.py +531 -0
- control_plane_api/app/middleware/__init__.py +10 -0
- control_plane_api/app/middleware/auth.py +645 -0
- control_plane_api/app/middleware/exception_handler.py +267 -0
- control_plane_api/app/middleware/prometheus_middleware.py +173 -0
- control_plane_api/app/middleware/rate_limiting.py +384 -0
- control_plane_api/app/middleware/request_id.py +202 -0
- control_plane_api/app/models/__init__.py +40 -0
- control_plane_api/app/models/agent.py +90 -0
- control_plane_api/app/models/analytics.py +206 -0
- control_plane_api/app/models/associations.py +107 -0
- control_plane_api/app/models/auth_user.py +73 -0
- control_plane_api/app/models/context.py +161 -0
- control_plane_api/app/models/custom_integration.py +99 -0
- control_plane_api/app/models/environment.py +64 -0
- control_plane_api/app/models/execution.py +125 -0
- control_plane_api/app/models/execution_transition.py +50 -0
- control_plane_api/app/models/job.py +159 -0
- control_plane_api/app/models/llm_model.py +78 -0
- control_plane_api/app/models/orchestration.py +66 -0
- control_plane_api/app/models/plan_execution.py +102 -0
- control_plane_api/app/models/presence.py +49 -0
- control_plane_api/app/models/project.py +61 -0
- control_plane_api/app/models/project_management.py +85 -0
- control_plane_api/app/models/session.py +29 -0
- control_plane_api/app/models/skill.py +155 -0
- control_plane_api/app/models/system_tables.py +43 -0
- control_plane_api/app/models/task_planning.py +372 -0
- control_plane_api/app/models/team.py +86 -0
- control_plane_api/app/models/trace.py +257 -0
- control_plane_api/app/models/user_profile.py +54 -0
- control_plane_api/app/models/worker.py +221 -0
- control_plane_api/app/models/workflow.py +161 -0
- control_plane_api/app/models/workspace.py +50 -0
- control_plane_api/app/observability/__init__.py +177 -0
- control_plane_api/app/observability/context_logging.py +475 -0
- control_plane_api/app/observability/decorators.py +337 -0
- control_plane_api/app/observability/local_span_processor.py +702 -0
- control_plane_api/app/observability/metrics.py +303 -0
- control_plane_api/app/observability/middleware.py +246 -0
- control_plane_api/app/observability/optional.py +115 -0
- control_plane_api/app/observability/tracing.py +382 -0
- control_plane_api/app/policies/README.md +149 -0
- control_plane_api/app/policies/approved_users.rego +62 -0
- control_plane_api/app/policies/business_hours.rego +51 -0
- control_plane_api/app/policies/rate_limiting.rego +100 -0
- control_plane_api/app/policies/tool_enforcement/README.md +336 -0
- control_plane_api/app/policies/tool_enforcement/bash_command_validation.rego +71 -0
- control_plane_api/app/policies/tool_enforcement/business_hours_enforcement.rego +82 -0
- control_plane_api/app/policies/tool_enforcement/mcp_tool_allowlist.rego +58 -0
- control_plane_api/app/policies/tool_enforcement/production_safeguards.rego +80 -0
- control_plane_api/app/policies/tool_enforcement/role_based_tool_access.rego +44 -0
- control_plane_api/app/policies/tool_restrictions.rego +86 -0
- control_plane_api/app/routers/__init__.py +4 -0
- control_plane_api/app/routers/agents.py +382 -0
- control_plane_api/app/routers/agents_v2.py +1598 -0
- control_plane_api/app/routers/analytics.py +1310 -0
- control_plane_api/app/routers/auth.py +59 -0
- control_plane_api/app/routers/client_config.py +57 -0
- control_plane_api/app/routers/context_graph.py +561 -0
- control_plane_api/app/routers/context_manager.py +577 -0
- control_plane_api/app/routers/custom_integrations.py +490 -0
- control_plane_api/app/routers/enforcer.py +132 -0
- control_plane_api/app/routers/environment_context.py +252 -0
- control_plane_api/app/routers/environments.py +761 -0
- control_plane_api/app/routers/execution_environment.py +847 -0
- control_plane_api/app/routers/executions/__init__.py +28 -0
- control_plane_api/app/routers/executions/router.py +286 -0
- control_plane_api/app/routers/executions/services/__init__.py +22 -0
- control_plane_api/app/routers/executions/services/demo_worker_health.py +156 -0
- control_plane_api/app/routers/executions/services/status_service.py +420 -0
- control_plane_api/app/routers/executions/services/test_worker_health.py +480 -0
- control_plane_api/app/routers/executions/services/worker_health.py +514 -0
- control_plane_api/app/routers/executions/streaming/__init__.py +22 -0
- control_plane_api/app/routers/executions/streaming/deduplication.py +352 -0
- control_plane_api/app/routers/executions/streaming/event_buffer.py +353 -0
- control_plane_api/app/routers/executions/streaming/event_formatter.py +964 -0
- control_plane_api/app/routers/executions/streaming/history_loader.py +588 -0
- control_plane_api/app/routers/executions/streaming/live_source.py +693 -0
- control_plane_api/app/routers/executions/streaming/streamer.py +849 -0
- control_plane_api/app/routers/executions.py +4888 -0
- control_plane_api/app/routers/health.py +165 -0
- control_plane_api/app/routers/health_v2.py +394 -0
- control_plane_api/app/routers/integration_templates.py +496 -0
- control_plane_api/app/routers/integrations.py +287 -0
- control_plane_api/app/routers/jobs.py +1809 -0
- control_plane_api/app/routers/metrics.py +517 -0
- control_plane_api/app/routers/models.py +82 -0
- control_plane_api/app/routers/models_v2.py +628 -0
- control_plane_api/app/routers/plan_executions.py +1481 -0
- control_plane_api/app/routers/plan_generation_async.py +304 -0
- control_plane_api/app/routers/policies.py +669 -0
- control_plane_api/app/routers/presence.py +234 -0
- control_plane_api/app/routers/projects.py +987 -0
- control_plane_api/app/routers/runners.py +379 -0
- control_plane_api/app/routers/runtimes.py +172 -0
- control_plane_api/app/routers/secrets.py +171 -0
- control_plane_api/app/routers/skills.py +1010 -0
- control_plane_api/app/routers/skills_definitions.py +140 -0
- control_plane_api/app/routers/storage.py +456 -0
- control_plane_api/app/routers/task_planning.py +611 -0
- control_plane_api/app/routers/task_queues.py +650 -0
- control_plane_api/app/routers/team_context.py +274 -0
- control_plane_api/app/routers/teams.py +1747 -0
- control_plane_api/app/routers/templates.py +248 -0
- control_plane_api/app/routers/traces.py +571 -0
- control_plane_api/app/routers/websocket_client.py +479 -0
- control_plane_api/app/routers/websocket_executions_status.py +437 -0
- control_plane_api/app/routers/websocket_gateway.py +323 -0
- control_plane_api/app/routers/websocket_traces.py +576 -0
- control_plane_api/app/routers/worker_queues.py +2555 -0
- control_plane_api/app/routers/worker_websocket.py +419 -0
- control_plane_api/app/routers/workers.py +1004 -0
- control_plane_api/app/routers/workflows.py +204 -0
- control_plane_api/app/runtimes/__init__.py +6 -0
- control_plane_api/app/runtimes/validation.py +344 -0
- control_plane_api/app/schemas/__init__.py +1 -0
- control_plane_api/app/schemas/job_schemas.py +302 -0
- control_plane_api/app/schemas/mcp_schemas.py +311 -0
- control_plane_api/app/schemas/template_schemas.py +133 -0
- control_plane_api/app/schemas/trace_schemas.py +168 -0
- control_plane_api/app/schemas/worker_queue_observability_schemas.py +165 -0
- control_plane_api/app/services/__init__.py +1 -0
- control_plane_api/app/services/agno_planning_strategy.py +233 -0
- control_plane_api/app/services/agno_service.py +838 -0
- control_plane_api/app/services/claude_code_planning_service.py +203 -0
- control_plane_api/app/services/context_graph_client.py +224 -0
- control_plane_api/app/services/custom_integration_service.py +415 -0
- control_plane_api/app/services/integration_resolution_service.py +345 -0
- control_plane_api/app/services/litellm_service.py +394 -0
- control_plane_api/app/services/plan_generator.py +79 -0
- control_plane_api/app/services/planning_strategy.py +66 -0
- control_plane_api/app/services/planning_strategy_factory.py +118 -0
- control_plane_api/app/services/policy_service.py +615 -0
- control_plane_api/app/services/state_transition_service.py +755 -0
- control_plane_api/app/services/storage_service.py +593 -0
- control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
- control_plane_api/app/services/toolsets/context_graph_skill.py +432 -0
- control_plane_api/app/services/trace_retention.py +354 -0
- control_plane_api/app/services/worker_queue_metrics_service.py +190 -0
- control_plane_api/app/services/workflow_cancellation_manager.py +135 -0
- control_plane_api/app/services/workflow_operations_service.py +611 -0
- control_plane_api/app/skills/__init__.py +100 -0
- control_plane_api/app/skills/base.py +239 -0
- control_plane_api/app/skills/builtin/__init__.py +37 -0
- control_plane_api/app/skills/builtin/agent_communication/__init__.py +8 -0
- control_plane_api/app/skills/builtin/agent_communication/skill.py +246 -0
- control_plane_api/app/skills/builtin/code_ingestion/__init__.py +4 -0
- control_plane_api/app/skills/builtin/code_ingestion/skill.py +267 -0
- control_plane_api/app/skills/builtin/cognitive_memory/__init__.py +4 -0
- control_plane_api/app/skills/builtin/cognitive_memory/skill.py +174 -0
- control_plane_api/app/skills/builtin/contextual_awareness/__init__.py +4 -0
- control_plane_api/app/skills/builtin/contextual_awareness/skill.py +387 -0
- control_plane_api/app/skills/builtin/data_visualization/__init__.py +4 -0
- control_plane_api/app/skills/builtin/data_visualization/skill.py +154 -0
- control_plane_api/app/skills/builtin/docker/__init__.py +4 -0
- control_plane_api/app/skills/builtin/docker/skill.py +104 -0
- control_plane_api/app/skills/builtin/file_generation/__init__.py +4 -0
- control_plane_api/app/skills/builtin/file_generation/skill.py +94 -0
- control_plane_api/app/skills/builtin/file_system/__init__.py +4 -0
- control_plane_api/app/skills/builtin/file_system/skill.py +110 -0
- control_plane_api/app/skills/builtin/knowledge_api/__init__.py +5 -0
- control_plane_api/app/skills/builtin/knowledge_api/skill.py +124 -0
- control_plane_api/app/skills/builtin/python/__init__.py +4 -0
- control_plane_api/app/skills/builtin/python/skill.py +92 -0
- control_plane_api/app/skills/builtin/remote_filesystem/__init__.py +5 -0
- control_plane_api/app/skills/builtin/remote_filesystem/skill.py +170 -0
- control_plane_api/app/skills/builtin/shell/__init__.py +4 -0
- control_plane_api/app/skills/builtin/shell/skill.py +161 -0
- control_plane_api/app/skills/builtin/slack/__init__.py +3 -0
- control_plane_api/app/skills/builtin/slack/skill.py +302 -0
- control_plane_api/app/skills/builtin/workflow_executor/__init__.py +4 -0
- control_plane_api/app/skills/builtin/workflow_executor/skill.py +469 -0
- control_plane_api/app/skills/business_intelligence.py +189 -0
- control_plane_api/app/skills/config.py +63 -0
- control_plane_api/app/skills/loaders/__init__.py +14 -0
- control_plane_api/app/skills/loaders/base.py +73 -0
- control_plane_api/app/skills/loaders/filesystem_loader.py +199 -0
- control_plane_api/app/skills/registry.py +125 -0
- control_plane_api/app/utils/helpers.py +12 -0
- control_plane_api/app/utils/workflow_executor.py +354 -0
- control_plane_api/app/workflows/__init__.py +11 -0
- control_plane_api/app/workflows/agent_execution.py +520 -0
- control_plane_api/app/workflows/agent_execution_with_skills.py +223 -0
- control_plane_api/app/workflows/namespace_provisioning.py +326 -0
- control_plane_api/app/workflows/plan_generation.py +254 -0
- control_plane_api/app/workflows/team_execution.py +442 -0
- control_plane_api/scripts/seed_models.py +240 -0
- control_plane_api/scripts/validate_existing_tool_names.py +492 -0
- control_plane_api/shared/__init__.py +8 -0
- control_plane_api/shared/version.py +17 -0
- control_plane_api/test_deduplication.py +274 -0
- control_plane_api/test_executor_deduplication_e2e.py +309 -0
- control_plane_api/test_job_execution_e2e.py +283 -0
- control_plane_api/test_real_integration.py +193 -0
- control_plane_api/version.py +38 -0
- control_plane_api/worker/__init__.py +0 -0
- control_plane_api/worker/activities/__init__.py +0 -0
- control_plane_api/worker/activities/agent_activities.py +1585 -0
- control_plane_api/worker/activities/approval_activities.py +234 -0
- control_plane_api/worker/activities/job_activities.py +199 -0
- control_plane_api/worker/activities/runtime_activities.py +1167 -0
- control_plane_api/worker/activities/skill_activities.py +282 -0
- control_plane_api/worker/activities/team_activities.py +479 -0
- control_plane_api/worker/agent_runtime_server.py +370 -0
- control_plane_api/worker/binary_manager.py +333 -0
- control_plane_api/worker/config/__init__.py +31 -0
- control_plane_api/worker/config/worker_config.py +273 -0
- control_plane_api/worker/control_plane_client.py +1491 -0
- control_plane_api/worker/examples/analytics_integration_example.py +362 -0
- control_plane_api/worker/health_monitor.py +159 -0
- control_plane_api/worker/metrics.py +237 -0
- control_plane_api/worker/models/__init__.py +1 -0
- control_plane_api/worker/models/error_events.py +105 -0
- control_plane_api/worker/models/inputs.py +89 -0
- control_plane_api/worker/runtimes/__init__.py +35 -0
- control_plane_api/worker/runtimes/agent_runtime/runtime.py +485 -0
- control_plane_api/worker/runtimes/agno/__init__.py +34 -0
- control_plane_api/worker/runtimes/agno/config.py +248 -0
- control_plane_api/worker/runtimes/agno/hooks.py +385 -0
- control_plane_api/worker/runtimes/agno/mcp_builder.py +195 -0
- control_plane_api/worker/runtimes/agno/runtime.py +1063 -0
- control_plane_api/worker/runtimes/agno/utils.py +163 -0
- control_plane_api/worker/runtimes/base.py +979 -0
- control_plane_api/worker/runtimes/claude_code/__init__.py +38 -0
- control_plane_api/worker/runtimes/claude_code/cleanup.py +184 -0
- control_plane_api/worker/runtimes/claude_code/client_pool.py +529 -0
- control_plane_api/worker/runtimes/claude_code/config.py +829 -0
- control_plane_api/worker/runtimes/claude_code/hooks.py +482 -0
- control_plane_api/worker/runtimes/claude_code/litellm_proxy.py +1702 -0
- control_plane_api/worker/runtimes/claude_code/mcp_builder.py +467 -0
- control_plane_api/worker/runtimes/claude_code/mcp_discovery.py +558 -0
- control_plane_api/worker/runtimes/claude_code/runtime.py +1546 -0
- control_plane_api/worker/runtimes/claude_code/tool_mapper.py +403 -0
- control_plane_api/worker/runtimes/claude_code/utils.py +149 -0
- control_plane_api/worker/runtimes/factory.py +173 -0
- control_plane_api/worker/runtimes/model_utils.py +107 -0
- control_plane_api/worker/runtimes/validation.py +93 -0
- control_plane_api/worker/services/__init__.py +1 -0
- control_plane_api/worker/services/agent_communication_tools.py +908 -0
- control_plane_api/worker/services/agent_executor.py +485 -0
- control_plane_api/worker/services/agent_executor_v2.py +793 -0
- control_plane_api/worker/services/analytics_collector.py +457 -0
- control_plane_api/worker/services/analytics_service.py +464 -0
- control_plane_api/worker/services/approval_tools.py +310 -0
- control_plane_api/worker/services/approval_tools_agno.py +207 -0
- control_plane_api/worker/services/cancellation_manager.py +177 -0
- control_plane_api/worker/services/code_ingestion_tools.py +465 -0
- control_plane_api/worker/services/contextual_awareness_tools.py +405 -0
- control_plane_api/worker/services/data_visualization.py +834 -0
- control_plane_api/worker/services/event_publisher.py +531 -0
- control_plane_api/worker/services/jira_tools.py +257 -0
- control_plane_api/worker/services/remote_filesystem_tools.py +498 -0
- control_plane_api/worker/services/runtime_analytics.py +328 -0
- control_plane_api/worker/services/session_service.py +365 -0
- control_plane_api/worker/services/skill_context_enhancement.py +181 -0
- control_plane_api/worker/services/skill_factory.py +471 -0
- control_plane_api/worker/services/system_prompt_enhancement.py +410 -0
- control_plane_api/worker/services/team_executor.py +715 -0
- control_plane_api/worker/services/team_executor_v2.py +1866 -0
- control_plane_api/worker/services/tool_enforcement.py +254 -0
- control_plane_api/worker/services/workflow_executor/__init__.py +52 -0
- control_plane_api/worker/services/workflow_executor/event_processor.py +287 -0
- control_plane_api/worker/services/workflow_executor/event_publisher.py +210 -0
- control_plane_api/worker/services/workflow_executor/executors/__init__.py +15 -0
- control_plane_api/worker/services/workflow_executor/executors/base.py +270 -0
- control_plane_api/worker/services/workflow_executor/executors/json_executor.py +50 -0
- control_plane_api/worker/services/workflow_executor/executors/python_executor.py +50 -0
- control_plane_api/worker/services/workflow_executor/models.py +142 -0
- control_plane_api/worker/services/workflow_executor_tools.py +1748 -0
- control_plane_api/worker/skills/__init__.py +12 -0
- control_plane_api/worker/skills/builtin/context_graph_search/README.md +213 -0
- control_plane_api/worker/skills/builtin/context_graph_search/__init__.py +5 -0
- control_plane_api/worker/skills/builtin/context_graph_search/agno_impl.py +808 -0
- control_plane_api/worker/skills/builtin/context_graph_search/skill.yaml +67 -0
- control_plane_api/worker/skills/builtin/contextual_awareness/__init__.py +4 -0
- control_plane_api/worker/skills/builtin/contextual_awareness/agno_impl.py +62 -0
- control_plane_api/worker/skills/builtin/data_visualization/agno_impl.py +18 -0
- control_plane_api/worker/skills/builtin/data_visualization/skill.yaml +84 -0
- control_plane_api/worker/skills/builtin/docker/agno_impl.py +65 -0
- control_plane_api/worker/skills/builtin/docker/skill.yaml +60 -0
- control_plane_api/worker/skills/builtin/file_generation/agno_impl.py +47 -0
- control_plane_api/worker/skills/builtin/file_generation/skill.yaml +64 -0
- control_plane_api/worker/skills/builtin/file_system/agno_impl.py +32 -0
- control_plane_api/worker/skills/builtin/file_system/skill.yaml +54 -0
- control_plane_api/worker/skills/builtin/knowledge_api/__init__.py +4 -0
- control_plane_api/worker/skills/builtin/knowledge_api/agno_impl.py +50 -0
- control_plane_api/worker/skills/builtin/knowledge_api/skill.yaml +66 -0
- control_plane_api/worker/skills/builtin/python/agno_impl.py +25 -0
- control_plane_api/worker/skills/builtin/python/skill.yaml +60 -0
- control_plane_api/worker/skills/builtin/schema_fix_mixin.py +260 -0
- control_plane_api/worker/skills/builtin/shell/agno_impl.py +31 -0
- control_plane_api/worker/skills/builtin/shell/skill.yaml +60 -0
- control_plane_api/worker/skills/builtin/slack/__init__.py +3 -0
- control_plane_api/worker/skills/builtin/slack/agno_impl.py +1282 -0
- control_plane_api/worker/skills/builtin/slack/skill.yaml +276 -0
- control_plane_api/worker/skills/builtin/workflow_executor/agno_impl.py +62 -0
- control_plane_api/worker/skills/builtin/workflow_executor/skill.yaml +79 -0
- control_plane_api/worker/skills/loaders/__init__.py +5 -0
- control_plane_api/worker/skills/loaders/base.py +23 -0
- control_plane_api/worker/skills/loaders/filesystem_loader.py +357 -0
- control_plane_api/worker/skills/registry.py +208 -0
- control_plane_api/worker/tests/__init__.py +1 -0
- control_plane_api/worker/tests/conftest.py +12 -0
- control_plane_api/worker/tests/e2e/__init__.py +0 -0
- control_plane_api/worker/tests/e2e/test_context_graph_real_api.py +338 -0
- control_plane_api/worker/tests/e2e/test_context_graph_templates_e2e.py +523 -0
- control_plane_api/worker/tests/e2e/test_enforcement_e2e.py +344 -0
- control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
- control_plane_api/worker/tests/e2e/test_single_execution_mode.py +656 -0
- control_plane_api/worker/tests/integration/__init__.py +0 -0
- control_plane_api/worker/tests/integration/test_builtin_skills_fixes.py +245 -0
- control_plane_api/worker/tests/integration/test_context_graph_search_integration.py +365 -0
- control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
- control_plane_api/worker/tests/integration/test_hook_enforcement_integration.py +579 -0
- control_plane_api/worker/tests/integration/test_scheduled_job_workflow.py +237 -0
- control_plane_api/worker/tests/integration/test_system_prompt_enhancement_integration.py +343 -0
- control_plane_api/worker/tests/unit/__init__.py +0 -0
- control_plane_api/worker/tests/unit/test_builtin_skill_autoload.py +396 -0
- control_plane_api/worker/tests/unit/test_context_graph_search.py +450 -0
- control_plane_api/worker/tests/unit/test_context_graph_templates.py +403 -0
- control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
- control_plane_api/worker/tests/unit/test_control_plane_client_jobs.py +345 -0
- control_plane_api/worker/tests/unit/test_job_activities.py +353 -0
- control_plane_api/worker/tests/unit/test_skill_context_enhancement.py +321 -0
- control_plane_api/worker/tests/unit/test_system_prompt_enhancement.py +415 -0
- control_plane_api/worker/tests/unit/test_tool_enforcement.py +324 -0
- control_plane_api/worker/utils/__init__.py +1 -0
- control_plane_api/worker/utils/chunk_batcher.py +330 -0
- control_plane_api/worker/utils/environment.py +65 -0
- control_plane_api/worker/utils/error_publisher.py +260 -0
- control_plane_api/worker/utils/event_batcher.py +256 -0
- control_plane_api/worker/utils/logging_config.py +335 -0
- control_plane_api/worker/utils/logging_helper.py +326 -0
- control_plane_api/worker/utils/parameter_validator.py +120 -0
- control_plane_api/worker/utils/retry_utils.py +60 -0
- control_plane_api/worker/utils/streaming_utils.py +665 -0
- control_plane_api/worker/utils/tool_validation.py +332 -0
- control_plane_api/worker/utils/workspace_manager.py +163 -0
- control_plane_api/worker/websocket_client.py +393 -0
- control_plane_api/worker/worker.py +1297 -0
- control_plane_api/worker/workflows/__init__.py +0 -0
- control_plane_api/worker/workflows/agent_execution.py +909 -0
- control_plane_api/worker/workflows/scheduled_job_wrapper.py +332 -0
- control_plane_api/worker/workflows/team_execution.py +611 -0
- kubiya_control_plane_api-0.9.15.dist-info/METADATA +354 -0
- kubiya_control_plane_api-0.9.15.dist-info/RECORD +479 -0
- kubiya_control_plane_api-0.9.15.dist-info/WHEEL +5 -0
- kubiya_control_plane_api-0.9.15.dist-info/entry_points.txt +5 -0
- kubiya_control_plane_api-0.9.15.dist-info/licenses/LICENSE +676 -0
- kubiya_control_plane_api-0.9.15.dist-info/top_level.txt +3 -0
- scripts/__init__.py +1 -0
- scripts/migrations.py +39 -0
- scripts/seed_worker_queues.py +128 -0
- scripts/setup_agent_runtime.py +142 -0
- worker_internal/__init__.py +1 -0
- worker_internal/planner/__init__.py +1 -0
- worker_internal/planner/activities.py +1499 -0
- worker_internal/planner/agent_tools.py +197 -0
- worker_internal/planner/event_models.py +148 -0
- worker_internal/planner/event_publisher.py +67 -0
- worker_internal/planner/models.py +199 -0
- worker_internal/planner/retry_logic.py +134 -0
- worker_internal/planner/worker.py +300 -0
- worker_internal/planner/workflows.py +970 -0
|
@@ -0,0 +1,1866 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Team executor service with runtime abstraction support.
|
|
3
|
+
|
|
4
|
+
This version supports both Agno-based teams and Claude Code SDK runtime teams.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Any, Optional, List
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
import structlog
|
|
10
|
+
import asyncio
|
|
11
|
+
import os
|
|
12
|
+
import time
|
|
13
|
+
|
|
14
|
+
from control_plane_api.worker.control_plane_client import ControlPlaneClient
|
|
15
|
+
from control_plane_api.worker.services.session_service import SessionService
|
|
16
|
+
from control_plane_api.worker.services.cancellation_manager import CancellationManager
|
|
17
|
+
from control_plane_api.worker.services.analytics_service import AnalyticsService
|
|
18
|
+
from control_plane_api.worker.services.runtime_analytics import submit_runtime_analytics
|
|
19
|
+
from control_plane_api.worker.runtimes import (
|
|
20
|
+
RuntimeFactory,
|
|
21
|
+
RuntimeType,
|
|
22
|
+
RuntimeExecutionContext,
|
|
23
|
+
)
|
|
24
|
+
from control_plane_api.worker.utils.streaming_utils import StreamingHelper
|
|
25
|
+
from control_plane_api.app.lib.templating.types import TemplateContext
|
|
26
|
+
from control_plane_api.app.lib.templating.resolver import resolve_templates
|
|
27
|
+
from control_plane_api.worker.utils.logging_config import sanitize_value
|
|
28
|
+
|
|
29
|
+
logger = structlog.get_logger()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TeamExecutorServiceV2:
|
|
33
|
+
"""
|
|
34
|
+
Service for executing teams using runtime abstraction.
|
|
35
|
+
|
|
36
|
+
This service orchestrates team execution by:
|
|
37
|
+
1. Loading session history
|
|
38
|
+
2. Determining runtime type (Agno or Claude Code)
|
|
39
|
+
3. Delegating execution to appropriate runtime
|
|
40
|
+
4. Persisting session after execution
|
|
41
|
+
|
|
42
|
+
For Claude Code runtime:
|
|
43
|
+
- Team leader uses Claude Code SDK with Task tool
|
|
44
|
+
- Team members are executed as subagents via Task tool
|
|
45
|
+
- Streaming and tool hooks supported
|
|
46
|
+
|
|
47
|
+
For Agno runtime:
|
|
48
|
+
- Uses existing Agno Team implementation
|
|
49
|
+
- Full multi-agent coordination support
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
control_plane: ControlPlaneClient,
|
|
55
|
+
session_service: SessionService,
|
|
56
|
+
cancellation_manager: CancellationManager,
|
|
57
|
+
):
|
|
58
|
+
"""
|
|
59
|
+
Initialize the team executor service.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
control_plane: Control Plane API client
|
|
63
|
+
session_service: Session management service
|
|
64
|
+
cancellation_manager: Execution cancellation manager
|
|
65
|
+
"""
|
|
66
|
+
self.control_plane = control_plane
|
|
67
|
+
self.session_service = session_service
|
|
68
|
+
self.cancellation_manager = cancellation_manager
|
|
69
|
+
self.runtime_factory = RuntimeFactory()
|
|
70
|
+
self.streaming_helper = None # Will be set during execution for tool message tracking
|
|
71
|
+
|
|
72
|
+
# Initialize analytics service for tracking LLM usage, tool calls, etc.
|
|
73
|
+
control_plane_url = os.getenv("CONTROL_PLANE_URL", "http://localhost:8000")
|
|
74
|
+
api_key = os.getenv("KUBIYA_API_KEY", "")
|
|
75
|
+
self.analytics_service = AnalyticsService(control_plane_url, api_key)
|
|
76
|
+
|
|
77
|
+
async def execute(self, input: Any) -> Dict[str, Any]:
|
|
78
|
+
"""
|
|
79
|
+
Execute a team using the configured runtime.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
input: TeamExecutionInput with execution details
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Dict with response, usage, success flag, runtime_type, etc.
|
|
86
|
+
"""
|
|
87
|
+
execution_id = input.execution_id
|
|
88
|
+
|
|
89
|
+
logger.info(
|
|
90
|
+
"team_workflow_start",
|
|
91
|
+
execution_id=execution_id,
|
|
92
|
+
team_id=input.team_id,
|
|
93
|
+
organization_id=input.organization_id,
|
|
94
|
+
agent_count=len(input.agents),
|
|
95
|
+
session_id=input.session_id,
|
|
96
|
+
prompt_preview=input.prompt[:100] + "..." if len(input.prompt) > 100 else input.prompt
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
# Capture timestamp at start of execution for accurate user message timestamp
|
|
101
|
+
from datetime import datetime, timezone
|
|
102
|
+
user_message_timestamp = datetime.now(timezone.utc).isoformat()
|
|
103
|
+
|
|
104
|
+
# STEP 1: Load session history
|
|
105
|
+
logger.info("loading_session_history", session_id=input.session_id)
|
|
106
|
+
session_history = self.session_service.load_session(
|
|
107
|
+
execution_id=execution_id,
|
|
108
|
+
session_id=input.session_id
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if session_history:
|
|
112
|
+
logger.info("session_history_loaded", message_count=len(session_history))
|
|
113
|
+
else:
|
|
114
|
+
logger.info("starting_new_conversation", session_id=input.session_id)
|
|
115
|
+
|
|
116
|
+
# STEP 2: Determine runtime type
|
|
117
|
+
# Priority: input.runtime_type (if explicitly set) > team_config.runtime > "default"
|
|
118
|
+
runtime_type_str = getattr(input, "runtime_type", "default")
|
|
119
|
+
team_config = getattr(input, "team_config", {}) or {}
|
|
120
|
+
|
|
121
|
+
# Debug: Log what we received
|
|
122
|
+
logger.debug("runtime_type_input", input_runtime_type=runtime_type_str)
|
|
123
|
+
logger.debug("team_config_keys", keys=list(team_config.keys()))
|
|
124
|
+
if "runtime" in team_config:
|
|
125
|
+
logger.debug("team_config_runtime", runtime=team_config.get('runtime'))
|
|
126
|
+
|
|
127
|
+
# If runtime_type is still "default", check team_config.runtime
|
|
128
|
+
if runtime_type_str == "default" and "runtime" in team_config:
|
|
129
|
+
runtime_type_str = team_config.get("runtime", "default")
|
|
130
|
+
logger.debug("using_team_config_runtime", runtime=runtime_type_str)
|
|
131
|
+
|
|
132
|
+
runtime_type = self.runtime_factory.parse_runtime_type(runtime_type_str)
|
|
133
|
+
|
|
134
|
+
logger.info(
|
|
135
|
+
"runtime_type_selected",
|
|
136
|
+
runtime_type=runtime_type.value,
|
|
137
|
+
framework=self._get_framework_name(runtime_type)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
logger.info(
|
|
141
|
+
"runtime_selected",
|
|
142
|
+
execution_id=execution_id[:8],
|
|
143
|
+
runtime=runtime_type.value,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# STEP 3: Publish user message to stream before execution
|
|
147
|
+
# This ensures chronological ordering in UI
|
|
148
|
+
turn_number = len(session_history) // 2 + 1
|
|
149
|
+
user_message_id = f"{execution_id}_user_{turn_number}"
|
|
150
|
+
self.control_plane.publish_event(
|
|
151
|
+
execution_id=execution_id,
|
|
152
|
+
event_type="message",
|
|
153
|
+
data={
|
|
154
|
+
"role": "user",
|
|
155
|
+
"content": input.prompt,
|
|
156
|
+
"timestamp": user_message_timestamp,
|
|
157
|
+
"message_id": user_message_id,
|
|
158
|
+
"user_id": input.user_id,
|
|
159
|
+
"user_name": getattr(input, "user_name", None),
|
|
160
|
+
"user_email": getattr(input, "user_email", None),
|
|
161
|
+
"user_avatar": getattr(input, "user_avatar", None),
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
logger.debug("user_message_published", message_id=user_message_id)
|
|
165
|
+
|
|
166
|
+
# STEP 4: Detect execution mode and route appropriately
|
|
167
|
+
execution_mode = self._detect_execution_mode(input, runtime_type)
|
|
168
|
+
|
|
169
|
+
logger.info(
|
|
170
|
+
"execution_mode_detected",
|
|
171
|
+
execution_id=execution_id[:8],
|
|
172
|
+
mode=execution_mode,
|
|
173
|
+
runtime_type=runtime_type.value,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Route based on detected execution mode
|
|
177
|
+
if execution_mode == "agent_runtime_native":
|
|
178
|
+
result = await self._execute_via_agent_runtime(input, session_history)
|
|
179
|
+
elif execution_mode in ("claude_code_native", "claude_code_task"):
|
|
180
|
+
result = await self._execute_with_claude_code(input, session_history, runtime_type)
|
|
181
|
+
else: # agno
|
|
182
|
+
# Fall back to Agno-based team execution
|
|
183
|
+
from control_plane_api.worker.services.team_executor import TeamExecutorService
|
|
184
|
+
|
|
185
|
+
agno_executor = TeamExecutorService(
|
|
186
|
+
self.control_plane,
|
|
187
|
+
self.session_service,
|
|
188
|
+
self.cancellation_manager
|
|
189
|
+
)
|
|
190
|
+
return await agno_executor.execute(input)
|
|
191
|
+
|
|
192
|
+
logger.info("team_execution_completed")
|
|
193
|
+
logger.info("execution_response_length", length=len(result['response']))
|
|
194
|
+
logger.info("execution_success_status", success=result['success'])
|
|
195
|
+
|
|
196
|
+
logger.info(
|
|
197
|
+
"team_execution_completed",
|
|
198
|
+
execution_id=execution_id[:8],
|
|
199
|
+
success=result["success"],
|
|
200
|
+
response_length=len(result["response"]),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# STEP 4: Persist session
|
|
204
|
+
if result["success"] and result["response"]:
|
|
205
|
+
logger.info("persisting_session_history")
|
|
206
|
+
|
|
207
|
+
turn_number = len(session_history) // 2 + 1
|
|
208
|
+
|
|
209
|
+
# Finalize streaming to transition to post-tool phase
|
|
210
|
+
if self.streaming_helper:
|
|
211
|
+
self.streaming_helper.finalize_streaming()
|
|
212
|
+
|
|
213
|
+
# Build user message
|
|
214
|
+
user_message = {
|
|
215
|
+
"role": "user",
|
|
216
|
+
"content": input.prompt,
|
|
217
|
+
"timestamp": user_message_timestamp, # Use timestamp from start
|
|
218
|
+
"message_id": f"{execution_id}_user_{turn_number}",
|
|
219
|
+
"user_id": input.user_id,
|
|
220
|
+
"user_name": getattr(input, "user_name", None),
|
|
221
|
+
"user_email": getattr(input, "user_email", None),
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
# Build structured messages using StreamingHelper
|
|
225
|
+
# This properly splits assistant messages around tool usage
|
|
226
|
+
if self.streaming_helper:
|
|
227
|
+
new_messages = self.streaming_helper.build_structured_messages(
|
|
228
|
+
execution_id=execution_id,
|
|
229
|
+
turn_number=turn_number,
|
|
230
|
+
user_message=user_message,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
logger.debug("structured_messages_built", count=len(new_messages))
|
|
234
|
+
if self.streaming_helper.has_any_tools:
|
|
235
|
+
logger.debug("assistant_message_split_into_phases")
|
|
236
|
+
assistant_parts = self.streaming_helper.get_assistant_message_parts()
|
|
237
|
+
for part in assistant_parts:
|
|
238
|
+
logger.debug("message_part", phase=part['phase'], length=len(part['content']))
|
|
239
|
+
else:
|
|
240
|
+
# Fallback if no streaming helper (shouldn't happen)
|
|
241
|
+
assistant_message_timestamp = datetime.now(timezone.utc).isoformat()
|
|
242
|
+
new_messages = [
|
|
243
|
+
user_message,
|
|
244
|
+
{
|
|
245
|
+
"role": "assistant",
|
|
246
|
+
"content": result["response"],
|
|
247
|
+
"timestamp": assistant_message_timestamp,
|
|
248
|
+
"message_id": f"{execution_id}_assistant_{turn_number}",
|
|
249
|
+
},
|
|
250
|
+
]
|
|
251
|
+
|
|
252
|
+
# Combine with previous history
|
|
253
|
+
complete_session = session_history + new_messages
|
|
254
|
+
|
|
255
|
+
# CRITICAL: Deduplicate messages by message_id AND content to prevent duplicates
|
|
256
|
+
# Use session_service.deduplicate_messages() which has enhanced two-level deduplication
|
|
257
|
+
original_count = len(complete_session)
|
|
258
|
+
complete_session = self.session_service.deduplicate_messages(complete_session)
|
|
259
|
+
|
|
260
|
+
# CRITICAL: Sort by timestamp to ensure chronological order
|
|
261
|
+
# Tool messages happen DURING streaming, so they need to be interleaved with user/assistant messages
|
|
262
|
+
complete_session.sort(key=lambda msg: msg.get("timestamp", ""))
|
|
263
|
+
logger.info("messages_deduplicated", before=original_count, after=len(complete_session))
|
|
264
|
+
|
|
265
|
+
success = self.session_service.persist_session(
|
|
266
|
+
execution_id=execution_id,
|
|
267
|
+
session_id=input.session_id or execution_id,
|
|
268
|
+
user_id=input.user_id,
|
|
269
|
+
messages=complete_session,
|
|
270
|
+
metadata={
|
|
271
|
+
"team_id": input.team_id,
|
|
272
|
+
"organization_id": input.organization_id,
|
|
273
|
+
"runtime_type": runtime_type.value,
|
|
274
|
+
"turn_count": len(complete_session),
|
|
275
|
+
"member_count": len(input.agents),
|
|
276
|
+
},
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
if success:
|
|
280
|
+
logger.info("session_persisted", total_messages=len(complete_session))
|
|
281
|
+
else:
|
|
282
|
+
logger.warning("session_persistence_failed")
|
|
283
|
+
|
|
284
|
+
# STEP 5: Print usage metrics
|
|
285
|
+
if result.get("usage"):
|
|
286
|
+
logger.info("token_usage_summary",
|
|
287
|
+
prompt_tokens=result['usage'].get('prompt_tokens', 0),
|
|
288
|
+
completion_tokens=result['usage'].get('completion_tokens', 0),
|
|
289
|
+
total_tokens=result['usage'].get('total_tokens', 0))
|
|
290
|
+
|
|
291
|
+
# Banner removed - using structured logging
|
|
292
|
+
logger.info("team_execution_end")
|
|
293
|
+
return result
|
|
294
|
+
|
|
295
|
+
except Exception as e:
|
|
296
|
+
logger.error("team_execution_failed")
|
|
297
|
+
# Banner removed - using structured logging
|
|
298
|
+
logger.error("execution_error", error=str(e))
|
|
299
|
+
logger.error(
|
|
300
|
+
"team_execution_failed",
|
|
301
|
+
execution_id=execution_id[:8],
|
|
302
|
+
error=str(e)
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
"success": False,
|
|
307
|
+
"error": str(e),
|
|
308
|
+
"model": input.model_id,
|
|
309
|
+
"usage": {},
|
|
310
|
+
"finish_reason": "error",
|
|
311
|
+
"runtime_type": runtime_type_str if "runtime_type_str" in locals() else "unknown",
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async def _execute_with_claude_code(
|
|
315
|
+
self, input: Any, session_history: List[Dict], runtime_type: RuntimeType
|
|
316
|
+
) -> Dict[str, Any]:
|
|
317
|
+
"""
|
|
318
|
+
Execute team using Claude Code SDK.
|
|
319
|
+
|
|
320
|
+
Strategy (V2 with native subagents):
|
|
321
|
+
- If all members are Claude Code → use native SDK subagents (optimal)
|
|
322
|
+
- Otherwise → use Task tool delegation (current implementation)
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
input: TeamExecutionInput
|
|
326
|
+
session_history: Previous conversation messages
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
Result dict
|
|
330
|
+
"""
|
|
331
|
+
execution_id = input.execution_id
|
|
332
|
+
|
|
333
|
+
# Check if we can use native subagent support
|
|
334
|
+
if input.agents and self._all_members_are_claude_code(input.agents):
|
|
335
|
+
logger.info("native_subagent_path_detected")
|
|
336
|
+
logger.info(
|
|
337
|
+
"using_native_subagents",
|
|
338
|
+
execution_id=execution_id[:8],
|
|
339
|
+
member_count=len(input.agents),
|
|
340
|
+
)
|
|
341
|
+
return await self._execute_claude_code_team_with_native_subagents(input, session_history)
|
|
342
|
+
|
|
343
|
+
# Fall back to Task tool delegation for mixed or non-Claude Code teams
|
|
344
|
+
logger.info("creating_claude_code_team_leader")
|
|
345
|
+
logger.info(
|
|
346
|
+
"using_task_tool_delegation",
|
|
347
|
+
execution_id=execution_id[:8],
|
|
348
|
+
member_count=len(input.agents) if input.agents else 0,
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
# Create runtime instance
|
|
352
|
+
runtime = self.runtime_factory.create_runtime(
|
|
353
|
+
runtime_type=RuntimeType.CLAUDE_CODE,
|
|
354
|
+
control_plane_client=self.control_plane,
|
|
355
|
+
cancellation_manager=self.cancellation_manager,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
logger.info("runtime_created", info=runtime.get_runtime_info())
|
|
359
|
+
|
|
360
|
+
# STEP 1: Build team context for system prompt
|
|
361
|
+
team_context = self._build_team_context(input.agents)
|
|
362
|
+
|
|
363
|
+
system_prompt = f"""You are the team leader coordinating a team of specialized AI agents.
|
|
364
|
+
|
|
365
|
+
Your team members:
|
|
366
|
+
{team_context}
|
|
367
|
+
|
|
368
|
+
When you need a team member to perform a task:
|
|
369
|
+
1. Use the Task tool to delegate work to the appropriate agent
|
|
370
|
+
2. Provide clear instructions in the subagent_type parameter
|
|
371
|
+
3. Wait for their response before continuing
|
|
372
|
+
4. Synthesize the results into a cohesive answer
|
|
373
|
+
|
|
374
|
+
Your goal is to coordinate the team effectively to solve the user's request.
|
|
375
|
+
"""
|
|
376
|
+
|
|
377
|
+
logger.info("team_context", member_count=len(input.agents))
|
|
378
|
+
# Logged in team_context above
|
|
379
|
+
for agent in input.agents:
|
|
380
|
+
logger.debug("team_member_info", name=agent.get('name'), role=agent.get('role', 'No role specified')[:60])
|
|
381
|
+
# Empty print removed
|
|
382
|
+
|
|
383
|
+
# STEP 2: Get skills for team leader (must include Task tool)
|
|
384
|
+
logger.info("fetching_skills_from_control_plane")
|
|
385
|
+
skills = []
|
|
386
|
+
try:
|
|
387
|
+
# Get skills from first agent (team leader)
|
|
388
|
+
if input.agents:
|
|
389
|
+
leader_id = input.agents[0].get("id")
|
|
390
|
+
if leader_id:
|
|
391
|
+
skill_configs = self.control_plane.get_skills(leader_id)
|
|
392
|
+
if skill_configs:
|
|
393
|
+
logger.info("skills_resolved", count=len(skill_configs))
|
|
394
|
+
else:
|
|
395
|
+
skill_configs = []
|
|
396
|
+
|
|
397
|
+
# Always include built-in context_graph_search skill
|
|
398
|
+
builtin_skill_types = {'context_graph_search'}
|
|
399
|
+
existing_skill_types = {cfg.get('type') for cfg in skill_configs}
|
|
400
|
+
|
|
401
|
+
for builtin_type in builtin_skill_types:
|
|
402
|
+
if builtin_type not in existing_skill_types:
|
|
403
|
+
builtin_config = {
|
|
404
|
+
'name': builtin_type,
|
|
405
|
+
'type': builtin_type,
|
|
406
|
+
'enabled': True,
|
|
407
|
+
'configuration': {}
|
|
408
|
+
}
|
|
409
|
+
skill_configs.append(builtin_config)
|
|
410
|
+
logger.info("builtin_skill_auto_included", skill_type=builtin_type)
|
|
411
|
+
|
|
412
|
+
if skill_configs:
|
|
413
|
+
from control_plane_api.worker.services.skill_factory import SkillFactory
|
|
414
|
+
|
|
415
|
+
# Create factory instance for the current runtime
|
|
416
|
+
logger.debug("creating_skill_factory", runtime_type=runtime_type.value)
|
|
417
|
+
skill_factory = SkillFactory(runtime_type=runtime_type.value) # Use actual runtime
|
|
418
|
+
skill_factory.initialize()
|
|
419
|
+
|
|
420
|
+
skills = skill_factory.create_skills_from_list(skill_configs, execution_id=input.execution_id)
|
|
421
|
+
|
|
422
|
+
if skills:
|
|
423
|
+
logger.info("skills_instantiated", count=len(skills))
|
|
424
|
+
except Exception as e:
|
|
425
|
+
logger.warning("skill_fetch_error", error=str(e))
|
|
426
|
+
logger.error("skill_fetch_error", error=str(e))
|
|
427
|
+
|
|
428
|
+
# Always ensure Task tool is available for delegation
|
|
429
|
+
task_skill = {"type": "task", "name": "Task"}
|
|
430
|
+
if task_skill not in skills:
|
|
431
|
+
skills.append(task_skill)
|
|
432
|
+
logger.info("task_tool_added_for_coordination")
|
|
433
|
+
|
|
434
|
+
# STEP 3: Inject environment variables into MCP servers (runtime-agnostic)
|
|
435
|
+
logger.info("fetching_resolved_environment")
|
|
436
|
+
from control_plane_api.worker.activities.runtime_activities import inject_env_vars_into_mcp_servers
|
|
437
|
+
team_config = getattr(input, "team_config", {}) or {}
|
|
438
|
+
mcp_servers_with_env = inject_env_vars_into_mcp_servers(
|
|
439
|
+
mcp_servers=getattr(input, "mcp_servers", None),
|
|
440
|
+
agent_config=team_config,
|
|
441
|
+
runtime_config=team_config.get("runtime_config"),
|
|
442
|
+
control_plane_client=self.control_plane,
|
|
443
|
+
team_id=input.team_id,
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# STEP 4: Compile system prompt templates
|
|
447
|
+
logger.info("compiling_system_prompt_templates")
|
|
448
|
+
compiled_system_prompt = system_prompt
|
|
449
|
+
if system_prompt:
|
|
450
|
+
try:
|
|
451
|
+
# Build template context with available variables
|
|
452
|
+
template_context = TemplateContext(
|
|
453
|
+
variables=getattr(input, "user_metadata", None) or {},
|
|
454
|
+
secrets=team_config.get("secrets", {}),
|
|
455
|
+
env_vars=dict(os.environ), # Make all env vars available
|
|
456
|
+
# Context graph API configuration for {{.graph.node-id}} templates
|
|
457
|
+
graph_api_base=os.environ.get("CONTEXT_GRAPH_API_BASE", "https://graph.kubiya.ai"),
|
|
458
|
+
graph_api_key=os.environ.get("KUBIYA_API_KEY"),
|
|
459
|
+
graph_org_id=os.environ.get("KUBIYA_ORG_ID") or input.organization_id
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
# Resolve templates in system prompt
|
|
463
|
+
compiled_system_prompt = resolve_templates(
|
|
464
|
+
system_prompt,
|
|
465
|
+
template_context,
|
|
466
|
+
strict=False, # Don't fail on missing variables
|
|
467
|
+
skip_on_error=True # Return original if compilation fails
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
if compiled_system_prompt != system_prompt:
|
|
471
|
+
logger.info(
|
|
472
|
+
"team_system_prompt_templates_compiled",
|
|
473
|
+
original_length=len(system_prompt),
|
|
474
|
+
compiled_length=len(compiled_system_prompt)
|
|
475
|
+
)
|
|
476
|
+
logger.info("system_prompt_templates_compiled")
|
|
477
|
+
else:
|
|
478
|
+
logger.info("no_templates_in_system_prompt")
|
|
479
|
+
|
|
480
|
+
except Exception as e:
|
|
481
|
+
logger.warning(
|
|
482
|
+
"team_system_prompt_template_compilation_failed",
|
|
483
|
+
error=str(e),
|
|
484
|
+
exc_info=True
|
|
485
|
+
)
|
|
486
|
+
logger.warning("system_prompt_compilation_failed", error=str(e))
|
|
487
|
+
# Use original system prompt if compilation fails
|
|
488
|
+
compiled_system_prompt = system_prompt
|
|
489
|
+
|
|
490
|
+
# STEP 4.5: Enhance system prompt with complete execution environment context
|
|
491
|
+
execution_context_info = self._build_execution_context_info(
|
|
492
|
+
runtime_config=team_config.get("runtime_config", {}),
|
|
493
|
+
skills=skills,
|
|
494
|
+
mcp_servers=mcp_servers_with_env,
|
|
495
|
+
team_config=team_config
|
|
496
|
+
)
|
|
497
|
+
if execution_context_info:
|
|
498
|
+
if compiled_system_prompt:
|
|
499
|
+
compiled_system_prompt = compiled_system_prompt + "\n\n" + execution_context_info
|
|
500
|
+
else:
|
|
501
|
+
compiled_system_prompt = execution_context_info
|
|
502
|
+
logger.info("system_prompt_enhanced_with_context")
|
|
503
|
+
|
|
504
|
+
# STEP 5: Build execution context
|
|
505
|
+
logger.info("building_execution_context")
|
|
506
|
+
context = RuntimeExecutionContext(
|
|
507
|
+
execution_id=execution_id,
|
|
508
|
+
agent_id=input.team_id, # Use team_id as agent_id
|
|
509
|
+
organization_id=input.organization_id,
|
|
510
|
+
prompt=input.prompt,
|
|
511
|
+
system_prompt=compiled_system_prompt,
|
|
512
|
+
conversation_history=session_history,
|
|
513
|
+
model_id=input.model_id,
|
|
514
|
+
model_config=getattr(input, "model_config", None),
|
|
515
|
+
agent_config=team_config,
|
|
516
|
+
skills=skills,
|
|
517
|
+
mcp_servers=mcp_servers_with_env, # Use MCP servers with injected env vars
|
|
518
|
+
user_metadata=getattr(input, "user_metadata", None),
|
|
519
|
+
runtime_config=team_config.get("runtime_config"),
|
|
520
|
+
)
|
|
521
|
+
logger.info("execution_context_ready")
|
|
522
|
+
|
|
523
|
+
# STEP 5: Execute via runtime with streaming
|
|
524
|
+
logger.info("executing_team_via_claude_code")
|
|
525
|
+
|
|
526
|
+
# Track turn start time for analytics
|
|
527
|
+
turn_start_time = time.time()
|
|
528
|
+
turn_number = len(session_history) // 2 + 1 # Approximate turn number
|
|
529
|
+
|
|
530
|
+
# Create streaming helper for tracking tool messages (used in both streaming and non-streaming)
|
|
531
|
+
self.streaming_helper = StreamingHelper(
|
|
532
|
+
control_plane_client=self.control_plane, execution_id=input.execution_id
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
if runtime.supports_streaming():
|
|
536
|
+
result = await self._execute_streaming(runtime, context, input, self.streaming_helper)
|
|
537
|
+
else:
|
|
538
|
+
exec_result = await runtime.execute(context)
|
|
539
|
+
from datetime import datetime, timezone
|
|
540
|
+
result = {
|
|
541
|
+
"success": exec_result.success,
|
|
542
|
+
"response": exec_result.response,
|
|
543
|
+
"response_timestamp": datetime.now(timezone.utc).isoformat(),
|
|
544
|
+
"usage": exec_result.usage,
|
|
545
|
+
"model": exec_result.model or input.model_id,
|
|
546
|
+
"finish_reason": exec_result.finish_reason or "stop",
|
|
547
|
+
"tool_messages": exec_result.tool_messages or [],
|
|
548
|
+
"runtime_type": "claude_code",
|
|
549
|
+
"error": exec_result.error,
|
|
550
|
+
"team_member_count": len(input.agents),
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
# Track turn end time
|
|
554
|
+
turn_end_time = time.time()
|
|
555
|
+
|
|
556
|
+
# Submit analytics (non-blocking, fire-and-forget)
|
|
557
|
+
if result.get("success") and result.get("usage"):
|
|
558
|
+
try:
|
|
559
|
+
# Convert result dict to RuntimeExecutionResult for analytics
|
|
560
|
+
from runtimes.base import RuntimeExecutionResult
|
|
561
|
+
runtime_result = RuntimeExecutionResult(
|
|
562
|
+
response=result["response"],
|
|
563
|
+
usage=result["usage"],
|
|
564
|
+
success=result["success"],
|
|
565
|
+
finish_reason=result.get("finish_reason", "stop"),
|
|
566
|
+
model=result.get("model"),
|
|
567
|
+
tool_messages=result.get("tool_messages", []),
|
|
568
|
+
error=result.get("error"),
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
# Submit analytics in the background (doesn't block execution)
|
|
572
|
+
await submit_runtime_analytics(
|
|
573
|
+
result=runtime_result,
|
|
574
|
+
execution_id=execution_id,
|
|
575
|
+
turn_number=turn_number,
|
|
576
|
+
turn_start_time=turn_start_time,
|
|
577
|
+
turn_end_time=turn_end_time,
|
|
578
|
+
analytics_service=self.analytics_service,
|
|
579
|
+
)
|
|
580
|
+
logger.info(
|
|
581
|
+
"team_analytics_submitted",
|
|
582
|
+
execution_id=execution_id[:8],
|
|
583
|
+
tokens=result["usage"].get("total_tokens", 0),
|
|
584
|
+
)
|
|
585
|
+
except Exception as analytics_error:
|
|
586
|
+
# Analytics failures should not break execution
|
|
587
|
+
logger.warning(
|
|
588
|
+
"team_analytics_submission_failed",
|
|
589
|
+
execution_id=execution_id[:8],
|
|
590
|
+
error=str(analytics_error),
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
return result
|
|
594
|
+
|
|
595
|
+
async def _execute_streaming(
|
|
596
|
+
self, runtime, context: RuntimeExecutionContext, input: Any, streaming_helper: StreamingHelper
|
|
597
|
+
) -> Dict[str, Any]:
|
|
598
|
+
"""
|
|
599
|
+
Execute with streaming and publish events to Control Plane.
|
|
600
|
+
|
|
601
|
+
Args:
|
|
602
|
+
runtime: Runtime instance
|
|
603
|
+
context: Execution context
|
|
604
|
+
input: Original input for additional metadata
|
|
605
|
+
streaming_helper: StreamingHelper instance for tracking events
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
Result dict
|
|
609
|
+
"""
|
|
610
|
+
# streaming_helper is now passed as parameter instead of created here
|
|
611
|
+
from temporalio import activity
|
|
612
|
+
|
|
613
|
+
accumulated_response = ""
|
|
614
|
+
final_result = None
|
|
615
|
+
|
|
616
|
+
# Define event callback for publishing to Control Plane
|
|
617
|
+
def event_callback(event: Dict):
|
|
618
|
+
"""Callback to publish events to Control Plane SSE"""
|
|
619
|
+
event_type = event.get("type")
|
|
620
|
+
|
|
621
|
+
if event_type == "content_chunk":
|
|
622
|
+
# Publish content chunk
|
|
623
|
+
streaming_helper.publish_content_chunk(
|
|
624
|
+
content=event.get("content", ""),
|
|
625
|
+
message_id=event.get("message_id", context.execution_id),
|
|
626
|
+
)
|
|
627
|
+
elif event_type == "tool_start":
|
|
628
|
+
# Publish tool start
|
|
629
|
+
streaming_helper.publish_tool_start(
|
|
630
|
+
tool_name=event.get("tool_name"),
|
|
631
|
+
tool_execution_id=event.get("tool_execution_id"),
|
|
632
|
+
tool_args=event.get("tool_args", {}),
|
|
633
|
+
source="team_leader",
|
|
634
|
+
)
|
|
635
|
+
elif event_type == "tool_complete":
|
|
636
|
+
# Publish tool completion
|
|
637
|
+
streaming_helper.publish_tool_complete(
|
|
638
|
+
tool_name=event.get("tool_name"),
|
|
639
|
+
tool_execution_id=event.get("tool_execution_id"),
|
|
640
|
+
status=event.get("status", "success"),
|
|
641
|
+
output=event.get("output"),
|
|
642
|
+
error=event.get("error"),
|
|
643
|
+
source="team_leader",
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
# Stream execution
|
|
647
|
+
# Note: Temporal will raise asyncio.CancelledError when workflow cancels
|
|
648
|
+
# No need to check explicitly - cancellation happens automatically
|
|
649
|
+
async for chunk in runtime.stream_execute(context, event_callback):
|
|
650
|
+
if chunk.response:
|
|
651
|
+
accumulated_response += chunk.response
|
|
652
|
+
# Streaming output handled by response - removed print
|
|
653
|
+
|
|
654
|
+
# Keep final result for metadata
|
|
655
|
+
if chunk.usage or chunk.finish_reason:
|
|
656
|
+
final_result = chunk
|
|
657
|
+
|
|
658
|
+
# Empty print removed # New line after streaming
|
|
659
|
+
|
|
660
|
+
# Return final result with accumulated response
|
|
661
|
+
from datetime import datetime, timezone
|
|
662
|
+
response_timestamp = datetime.now(timezone.utc).isoformat()
|
|
663
|
+
|
|
664
|
+
if final_result:
|
|
665
|
+
return {
|
|
666
|
+
"success": final_result.success,
|
|
667
|
+
"response": accumulated_response,
|
|
668
|
+
"response_timestamp": response_timestamp,
|
|
669
|
+
"usage": final_result.usage,
|
|
670
|
+
"model": final_result.model or input.model_id,
|
|
671
|
+
"finish_reason": final_result.finish_reason or "stop",
|
|
672
|
+
"tool_messages": final_result.tool_messages or [],
|
|
673
|
+
"runtime_type": "claude_code",
|
|
674
|
+
"error": final_result.error,
|
|
675
|
+
"team_member_count": len(input.agents),
|
|
676
|
+
}
|
|
677
|
+
else:
|
|
678
|
+
return {
|
|
679
|
+
"success": True,
|
|
680
|
+
"response": accumulated_response,
|
|
681
|
+
"response_timestamp": response_timestamp,
|
|
682
|
+
"usage": {},
|
|
683
|
+
"model": input.model_id,
|
|
684
|
+
"finish_reason": "stop",
|
|
685
|
+
"tool_messages": [],
|
|
686
|
+
"runtime_type": "claude_code",
|
|
687
|
+
"team_member_count": len(input.agents),
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
async def _execute_claude_code_team_with_native_subagents(
|
|
691
|
+
self, input: Any, session_history: List[Dict]
|
|
692
|
+
) -> Dict[str, Any]:
|
|
693
|
+
"""
|
|
694
|
+
Execute Claude Code team using SDK's native subagent support.
|
|
695
|
+
|
|
696
|
+
This is the optimal path when:
|
|
697
|
+
- Leader runtime = claude_code
|
|
698
|
+
- All member runtimes = claude_code
|
|
699
|
+
|
|
700
|
+
Args:
|
|
701
|
+
input: TeamExecutionInput
|
|
702
|
+
session_history: Previous conversation messages
|
|
703
|
+
|
|
704
|
+
Returns:
|
|
705
|
+
Result dict
|
|
706
|
+
"""
|
|
707
|
+
execution_id = input.execution_id
|
|
708
|
+
|
|
709
|
+
logger.info("using_native_sdk_subagents")
|
|
710
|
+
|
|
711
|
+
# STEP 1: Build agents dictionary for SDK
|
|
712
|
+
agents_config = {}
|
|
713
|
+
failed_members = []
|
|
714
|
+
skipped_members = []
|
|
715
|
+
|
|
716
|
+
for member in input.agents:
|
|
717
|
+
member_id = member.get('id')
|
|
718
|
+
member_name = member.get('name', 'Unknown')
|
|
719
|
+
|
|
720
|
+
if not member_id:
|
|
721
|
+
logger.warning(
|
|
722
|
+
"member_missing_id_skipped",
|
|
723
|
+
execution_id=execution_id[:8],
|
|
724
|
+
member_name=member_name,
|
|
725
|
+
)
|
|
726
|
+
logger.warning("skipping_member_no_id", member_name=member_name)
|
|
727
|
+
skipped_members.append(member_name)
|
|
728
|
+
continue
|
|
729
|
+
|
|
730
|
+
# Fetch full member configuration from Control Plane
|
|
731
|
+
try:
|
|
732
|
+
member_full_config = self.control_plane.get_agent(member_id)
|
|
733
|
+
|
|
734
|
+
if not member_full_config:
|
|
735
|
+
logger.warning(
|
|
736
|
+
"member_config_not_found",
|
|
737
|
+
execution_id=execution_id[:8],
|
|
738
|
+
member_id=member_id,
|
|
739
|
+
member_name=member_name,
|
|
740
|
+
)
|
|
741
|
+
logger.warning("member_config_not_found", member_name=member_name, member_id=member_id)
|
|
742
|
+
failed_members.append(member_name)
|
|
743
|
+
continue
|
|
744
|
+
|
|
745
|
+
# Map skills to tools
|
|
746
|
+
skill_ids = member_full_config.get('skill_ids', [])
|
|
747
|
+
mapped_tools = self._map_skills_to_claude_tools(skill_ids, member_name)
|
|
748
|
+
|
|
749
|
+
# Convert to Claude Code agent format
|
|
750
|
+
agents_config[member_id] = {
|
|
751
|
+
'description': f"{member.get('role', member.get('name'))}. Use for: {member.get('capabilities', '')}",
|
|
752
|
+
'prompt': member_full_config.get('system_prompt', ''),
|
|
753
|
+
'tools': mapped_tools,
|
|
754
|
+
'model': self._map_model_to_sdk_format(member_full_config.get('model_id', 'inherit')),
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
logger.info("member_configured", member_name=member_name, model=agents_config[member_id]['model'], tools_count=len(agents_config[member_id]['tools']))
|
|
758
|
+
|
|
759
|
+
logger.info(
|
|
760
|
+
"native_subagent_registered",
|
|
761
|
+
execution_id=execution_id[:8],
|
|
762
|
+
member_name=member_name,
|
|
763
|
+
member_id=member_id,
|
|
764
|
+
model=agents_config[member_id]['model'],
|
|
765
|
+
tool_count=len(agents_config[member_id]['tools']),
|
|
766
|
+
skill_count=len(skill_ids),
|
|
767
|
+
)
|
|
768
|
+
except Exception as e:
|
|
769
|
+
logger.error(
|
|
770
|
+
"failed_to_load_member_config",
|
|
771
|
+
execution_id=execution_id[:8],
|
|
772
|
+
member_id=member_id,
|
|
773
|
+
member_name=member_name,
|
|
774
|
+
error=str(e),
|
|
775
|
+
error_type=type(e).__name__,
|
|
776
|
+
)
|
|
777
|
+
logger.error("member_load_failed", member_name=member_name, member_id=member_id, error=str(e)[:100])
|
|
778
|
+
failed_members.append(member_name)
|
|
779
|
+
continue
|
|
780
|
+
|
|
781
|
+
# Validate that we have at least one configured member
|
|
782
|
+
if not agents_config:
|
|
783
|
+
error_msg = f"No team members could be configured. Failed: {failed_members}, Skipped: {skipped_members}"
|
|
784
|
+
logger.error(
|
|
785
|
+
"no_members_configured_for_native_subagents",
|
|
786
|
+
execution_id=execution_id[:8],
|
|
787
|
+
total_members=len(input.agents),
|
|
788
|
+
failed_count=len(failed_members),
|
|
789
|
+
skipped_count=len(skipped_members),
|
|
790
|
+
)
|
|
791
|
+
logger.error("configuration_error", error=error_msg)
|
|
792
|
+
raise ValueError(error_msg)
|
|
793
|
+
|
|
794
|
+
logger.info("native_subagents_configured", count=len(agents_config))
|
|
795
|
+
if failed_members:
|
|
796
|
+
logger.warning('some_members_failed_to_configure', failed_members=failed_members)
|
|
797
|
+
logger.warning(
|
|
798
|
+
"some_members_failed_to_configure",
|
|
799
|
+
execution_id=execution_id[:8],
|
|
800
|
+
failed_members=failed_members,
|
|
801
|
+
configured_count=len(agents_config),
|
|
802
|
+
)
|
|
803
|
+
if skipped_members:
|
|
804
|
+
logger.warning('some_members_skipped_missing_id', skipped_members=skipped_members)
|
|
805
|
+
# Empty print removed
|
|
806
|
+
|
|
807
|
+
# STEP 2: Build team leader system prompt
|
|
808
|
+
system_prompt = self._build_team_leader_prompt(input.agents)
|
|
809
|
+
|
|
810
|
+
# STEP 3: Get team configuration
|
|
811
|
+
team_config = getattr(input, "team_config", {}) or {}
|
|
812
|
+
|
|
813
|
+
# STEP 4: Inject environment variables into MCP servers
|
|
814
|
+
logger.info("fetching_resolved_environment")
|
|
815
|
+
from control_plane_api.worker.activities.runtime_activities import inject_env_vars_into_mcp_servers
|
|
816
|
+
mcp_servers_with_env = inject_env_vars_into_mcp_servers(
|
|
817
|
+
mcp_servers=getattr(input, "mcp_servers", None),
|
|
818
|
+
agent_config=team_config,
|
|
819
|
+
runtime_config=team_config.get("runtime_config"),
|
|
820
|
+
control_plane_client=self.control_plane,
|
|
821
|
+
team_id=input.team_id,
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
# AUTO-INCLUDE BUILT-IN MCP SERVERS FOR ALL SUBAGENTS
|
|
825
|
+
# Ensure context-graph-search is available to all team members
|
|
826
|
+
if not mcp_servers_with_env:
|
|
827
|
+
mcp_servers_with_env = {}
|
|
828
|
+
|
|
829
|
+
if 'context-graph-search' not in mcp_servers_with_env:
|
|
830
|
+
# Add built-in context-graph-search MCP server
|
|
831
|
+
from control_plane_api.worker.services.skill_factory import SkillFactory
|
|
832
|
+
skill_factory = SkillFactory(runtime_type="claude_code")
|
|
833
|
+
skill_factory.initialize()
|
|
834
|
+
|
|
835
|
+
# Create context-graph-search skill config
|
|
836
|
+
builtin_skill_config = {
|
|
837
|
+
'name': 'context_graph_search',
|
|
838
|
+
'type': 'context_graph_search',
|
|
839
|
+
'enabled': True,
|
|
840
|
+
'configuration': {}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
# Create skill and convert to MCP server
|
|
844
|
+
try:
|
|
845
|
+
skills = skill_factory.create_skills_from_list([builtin_skill_config], execution_id=execution_id)
|
|
846
|
+
if skills:
|
|
847
|
+
# Build MCP servers from skills
|
|
848
|
+
from control_plane_api.worker.runtimes.claude_code import build_mcp_servers
|
|
849
|
+
builtin_mcp_servers = build_mcp_servers(skills, execution_id)
|
|
850
|
+
if builtin_mcp_servers and 'context-graph-search' in builtin_mcp_servers:
|
|
851
|
+
mcp_servers_with_env['context-graph-search'] = builtin_mcp_servers['context-graph-search']
|
|
852
|
+
logger.info("builtin_mcp_server_auto_included", server="context-graph-search")
|
|
853
|
+
logger.info(
|
|
854
|
+
"context_graph_search_mcp_auto_included",
|
|
855
|
+
execution_id=execution_id[:8],
|
|
856
|
+
)
|
|
857
|
+
except Exception as e:
|
|
858
|
+
logger.warning(
|
|
859
|
+
"failed_to_auto_include_context_graph_search_mcp",
|
|
860
|
+
execution_id=execution_id[:8],
|
|
861
|
+
error=str(e),
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
# STEP 5: Compile system prompt templates
|
|
865
|
+
logger.info("compiling_system_prompt_templates")
|
|
866
|
+
compiled_system_prompt = system_prompt
|
|
867
|
+
if system_prompt:
|
|
868
|
+
try:
|
|
869
|
+
# Build template context with available variables
|
|
870
|
+
template_context = TemplateContext(
|
|
871
|
+
variables=getattr(input, "user_metadata", None) or {},
|
|
872
|
+
secrets=team_config.get("secrets", {}),
|
|
873
|
+
env_vars=dict(os.environ), # Make all env vars available
|
|
874
|
+
# Context graph API configuration for {{.graph.node-id}} templates
|
|
875
|
+
graph_api_base=os.environ.get("CONTEXT_GRAPH_API_BASE", "https://graph.kubiya.ai"),
|
|
876
|
+
graph_api_key=os.environ.get("KUBIYA_API_KEY"),
|
|
877
|
+
graph_org_id=os.environ.get("KUBIYA_ORG_ID") or input.organization_id
|
|
878
|
+
)
|
|
879
|
+
|
|
880
|
+
# Resolve templates in system prompt
|
|
881
|
+
compiled_system_prompt = resolve_templates(
|
|
882
|
+
system_prompt,
|
|
883
|
+
template_context,
|
|
884
|
+
strict=False, # Don't fail on missing variables
|
|
885
|
+
skip_on_error=True # Return original if compilation fails
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
if compiled_system_prompt != system_prompt:
|
|
889
|
+
logger.info(
|
|
890
|
+
"native_team_system_prompt_templates_compiled",
|
|
891
|
+
original_length=len(system_prompt),
|
|
892
|
+
compiled_length=len(compiled_system_prompt)
|
|
893
|
+
)
|
|
894
|
+
logger.info("system_prompt_templates_compiled")
|
|
895
|
+
else:
|
|
896
|
+
logger.info("no_templates_in_system_prompt")
|
|
897
|
+
|
|
898
|
+
except Exception as e:
|
|
899
|
+
logger.warning(
|
|
900
|
+
"native_team_system_prompt_template_compilation_failed",
|
|
901
|
+
error=str(e),
|
|
902
|
+
exc_info=True
|
|
903
|
+
)
|
|
904
|
+
logger.warning("system_prompt_compilation_failed", error=str(e))
|
|
905
|
+
# Use original system prompt if compilation fails
|
|
906
|
+
compiled_system_prompt = system_prompt
|
|
907
|
+
|
|
908
|
+
# STEP 5.5: Enhance system prompt with complete execution environment context
|
|
909
|
+
execution_context_info = self._build_execution_context_info(
|
|
910
|
+
runtime_config=team_config.get("runtime_config", {}),
|
|
911
|
+
skills=[], # Native subagents path doesn't use leader skills
|
|
912
|
+
mcp_servers=mcp_servers_with_env,
|
|
913
|
+
team_config=team_config
|
|
914
|
+
)
|
|
915
|
+
if execution_context_info:
|
|
916
|
+
if compiled_system_prompt:
|
|
917
|
+
compiled_system_prompt = compiled_system_prompt + "\n\n" + execution_context_info
|
|
918
|
+
else:
|
|
919
|
+
compiled_system_prompt = execution_context_info
|
|
920
|
+
logger.info("system_prompt_enhanced_with_context")
|
|
921
|
+
|
|
922
|
+
# STEP 6: Build leader context with agents config
|
|
923
|
+
logger.info("building_execution_context_with_native_subagents")
|
|
924
|
+
context = RuntimeExecutionContext(
|
|
925
|
+
execution_id=execution_id,
|
|
926
|
+
agent_id=input.team_id,
|
|
927
|
+
organization_id=input.organization_id,
|
|
928
|
+
prompt=input.prompt,
|
|
929
|
+
system_prompt=compiled_system_prompt,
|
|
930
|
+
conversation_history=session_history,
|
|
931
|
+
model_id=input.model_id,
|
|
932
|
+
model_config=getattr(input, "model_config", None),
|
|
933
|
+
agent_config={
|
|
934
|
+
**team_config,
|
|
935
|
+
'runtime_config': {
|
|
936
|
+
'agents': agents_config # Pass to Claude Code SDK
|
|
937
|
+
}
|
|
938
|
+
},
|
|
939
|
+
skills=[], # Leader doesn't need extra skills, subagents have their own
|
|
940
|
+
mcp_servers=mcp_servers_with_env,
|
|
941
|
+
user_metadata=getattr(input, "user_metadata", None),
|
|
942
|
+
runtime_config=team_config.get("runtime_config"),
|
|
943
|
+
)
|
|
944
|
+
logger.info("execution_context_ready_with_native_subagents")
|
|
945
|
+
|
|
946
|
+
# STEP 6: Create runtime and execute
|
|
947
|
+
runtime = self.runtime_factory.create_runtime(
|
|
948
|
+
runtime_type=RuntimeType.CLAUDE_CODE,
|
|
949
|
+
control_plane_client=self.control_plane,
|
|
950
|
+
cancellation_manager=self.cancellation_manager,
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
logger.info("executing_with_native_sdk_subagents")
|
|
954
|
+
|
|
955
|
+
# Track turn start time for analytics
|
|
956
|
+
turn_start_time = time.time()
|
|
957
|
+
turn_number = len(session_history) // 2 + 1
|
|
958
|
+
|
|
959
|
+
# Create streaming helper for tracking tool messages
|
|
960
|
+
self.streaming_helper = StreamingHelper(
|
|
961
|
+
control_plane_client=self.control_plane, execution_id=input.execution_id
|
|
962
|
+
)
|
|
963
|
+
|
|
964
|
+
# Execute - SDK handles subagent routing automatically!
|
|
965
|
+
if runtime.supports_streaming():
|
|
966
|
+
result = await self._execute_streaming(runtime, context, input, self.streaming_helper)
|
|
967
|
+
else:
|
|
968
|
+
exec_result = await runtime.execute(context)
|
|
969
|
+
from datetime import datetime, timezone
|
|
970
|
+
result = {
|
|
971
|
+
"success": exec_result.success,
|
|
972
|
+
"response": exec_result.response,
|
|
973
|
+
"response_timestamp": datetime.now(timezone.utc).isoformat(),
|
|
974
|
+
"usage": exec_result.usage,
|
|
975
|
+
"model": exec_result.model or input.model_id,
|
|
976
|
+
"finish_reason": exec_result.finish_reason or "stop",
|
|
977
|
+
"tool_messages": exec_result.tool_messages or [],
|
|
978
|
+
"runtime_type": "claude_code",
|
|
979
|
+
"error": exec_result.error,
|
|
980
|
+
"team_member_count": len(input.agents),
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
# Track turn end time
|
|
984
|
+
turn_end_time = time.time()
|
|
985
|
+
|
|
986
|
+
# Submit analytics
|
|
987
|
+
if result.get("success") and result.get("usage"):
|
|
988
|
+
try:
|
|
989
|
+
from runtimes.base import RuntimeExecutionResult
|
|
990
|
+
runtime_result = RuntimeExecutionResult(
|
|
991
|
+
response=result["response"],
|
|
992
|
+
usage=result["usage"],
|
|
993
|
+
success=result["success"],
|
|
994
|
+
finish_reason=result.get("finish_reason", "stop"),
|
|
995
|
+
model=result.get("model"),
|
|
996
|
+
tool_messages=result.get("tool_messages", []),
|
|
997
|
+
error=result.get("error"),
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
await submit_runtime_analytics(
|
|
1001
|
+
result=runtime_result,
|
|
1002
|
+
execution_id=execution_id,
|
|
1003
|
+
turn_number=turn_number,
|
|
1004
|
+
turn_start_time=turn_start_time,
|
|
1005
|
+
turn_end_time=turn_end_time,
|
|
1006
|
+
analytics_service=self.analytics_service,
|
|
1007
|
+
)
|
|
1008
|
+
logger.info(
|
|
1009
|
+
"native_subagent_team_analytics_submitted",
|
|
1010
|
+
execution_id=execution_id[:8],
|
|
1011
|
+
tokens=result["usage"].get("total_tokens", 0),
|
|
1012
|
+
)
|
|
1013
|
+
except Exception as analytics_error:
|
|
1014
|
+
logger.warning(
|
|
1015
|
+
"native_subagent_team_analytics_failed",
|
|
1016
|
+
execution_id=execution_id[:8],
|
|
1017
|
+
error=str(analytics_error),
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
return result
|
|
1021
|
+
|
|
1022
|
+
def _map_skills_to_claude_tools(self, skill_ids: List[str], member_name: str = None) -> List[str]:
|
|
1023
|
+
"""
|
|
1024
|
+
Map skill IDs to Claude Code tool names.
|
|
1025
|
+
|
|
1026
|
+
Args:
|
|
1027
|
+
skill_ids: List of skill IDs from agent config
|
|
1028
|
+
member_name: Optional member name for logging context
|
|
1029
|
+
|
|
1030
|
+
Returns:
|
|
1031
|
+
List of Claude Code tool names
|
|
1032
|
+
"""
|
|
1033
|
+
if not skill_ids:
|
|
1034
|
+
logger.info(
|
|
1035
|
+
"no_skills_for_member_using_defaults",
|
|
1036
|
+
member_name=member_name or "unknown",
|
|
1037
|
+
default_tools=['Read', 'Write', 'Bash'],
|
|
1038
|
+
)
|
|
1039
|
+
return ['Read', 'Write', 'Bash']
|
|
1040
|
+
|
|
1041
|
+
# Fetch skill configurations
|
|
1042
|
+
tools = set()
|
|
1043
|
+
unmapped_skills = []
|
|
1044
|
+
failed_skills = []
|
|
1045
|
+
skill_type_counts = {}
|
|
1046
|
+
|
|
1047
|
+
for skill_id in skill_ids:
|
|
1048
|
+
try:
|
|
1049
|
+
skill_config = self.control_plane.get_skill(skill_id)
|
|
1050
|
+
if not skill_config:
|
|
1051
|
+
logger.warning(
|
|
1052
|
+
"skill_config_not_found",
|
|
1053
|
+
skill_id=skill_id,
|
|
1054
|
+
member_name=member_name or "unknown",
|
|
1055
|
+
)
|
|
1056
|
+
failed_skills.append(skill_id)
|
|
1057
|
+
continue
|
|
1058
|
+
|
|
1059
|
+
skill_type = skill_config.get('type', '').lower()
|
|
1060
|
+
skill_name = skill_config.get('name', skill_id)
|
|
1061
|
+
|
|
1062
|
+
# Track skill types for analytics
|
|
1063
|
+
skill_type_counts[skill_type] = skill_type_counts.get(skill_type, 0) + 1
|
|
1064
|
+
|
|
1065
|
+
# Map skill types to Claude Code tool names
|
|
1066
|
+
mapped = True
|
|
1067
|
+
if skill_type in ['file_system', 'filesystem', 'file']:
|
|
1068
|
+
tools.update(['Read', 'Write', 'Edit', 'Glob'])
|
|
1069
|
+
elif skill_type in ['shell', 'bash', 'command']:
|
|
1070
|
+
tools.add('Bash')
|
|
1071
|
+
elif skill_type in ['web', 'http', 'api']:
|
|
1072
|
+
tools.update(['WebFetch', 'WebSearch'])
|
|
1073
|
+
elif skill_type in ['data_visualization', 'visualization', 'plotting']:
|
|
1074
|
+
tools.update(['Read', 'Write']) # Needs read for data access, write for output
|
|
1075
|
+
elif skill_type in ['python', 'code', 'scripting']:
|
|
1076
|
+
tools.add('Bash') # Python via bash
|
|
1077
|
+
elif skill_type in ['grep', 'search', 'find']:
|
|
1078
|
+
tools.update(['Grep', 'Glob'])
|
|
1079
|
+
elif skill_type in ['task', 'workflow', 'delegation']:
|
|
1080
|
+
tools.add('Task')
|
|
1081
|
+
elif skill_type in ['planning', 'todo']:
|
|
1082
|
+
tools.update(['TodoWrite', 'Task'])
|
|
1083
|
+
elif skill_type in ['notebook', 'jupyter']:
|
|
1084
|
+
tools.update(['Read', 'Write', 'NotebookEdit'])
|
|
1085
|
+
elif skill_type in ['docker', 'container']:
|
|
1086
|
+
tools.add('Bash') # Docker commands via bash
|
|
1087
|
+
elif skill_type in ['git', 'version_control']:
|
|
1088
|
+
tools.add('Bash') # Git commands via bash
|
|
1089
|
+
elif skill_type in ['database', 'sql']:
|
|
1090
|
+
tools.add('Bash') # SQL commands via bash
|
|
1091
|
+
else:
|
|
1092
|
+
# Unknown skill type - log it for future mapping
|
|
1093
|
+
mapped = False
|
|
1094
|
+
unmapped_skills.append((skill_name, skill_type))
|
|
1095
|
+
logger.info(
|
|
1096
|
+
"unmapped_skill_type_encountered",
|
|
1097
|
+
skill_id=skill_id,
|
|
1098
|
+
skill_name=skill_name,
|
|
1099
|
+
skill_type=skill_type,
|
|
1100
|
+
member_name=member_name or "unknown",
|
|
1101
|
+
note="Consider adding mapping for this skill type",
|
|
1102
|
+
)
|
|
1103
|
+
|
|
1104
|
+
if mapped:
|
|
1105
|
+
logger.debug(
|
|
1106
|
+
"skill_mapped_successfully",
|
|
1107
|
+
skill_id=skill_id,
|
|
1108
|
+
skill_name=skill_name,
|
|
1109
|
+
skill_type=skill_type,
|
|
1110
|
+
member_name=member_name or "unknown",
|
|
1111
|
+
)
|
|
1112
|
+
|
|
1113
|
+
except Exception as e:
|
|
1114
|
+
logger.warning(
|
|
1115
|
+
"failed_to_map_skill",
|
|
1116
|
+
skill_id=skill_id,
|
|
1117
|
+
member_name=member_name or "unknown",
|
|
1118
|
+
error=str(e),
|
|
1119
|
+
error_type=type(e).__name__,
|
|
1120
|
+
)
|
|
1121
|
+
failed_skills.append(skill_id)
|
|
1122
|
+
continue
|
|
1123
|
+
|
|
1124
|
+
# If no tools mapped, provide basic defaults
|
|
1125
|
+
if not tools:
|
|
1126
|
+
logger.warning(
|
|
1127
|
+
"no_tools_mapped_using_defaults",
|
|
1128
|
+
member_name=member_name or "unknown",
|
|
1129
|
+
total_skills=len(skill_ids),
|
|
1130
|
+
unmapped_count=len(unmapped_skills),
|
|
1131
|
+
failed_count=len(failed_skills),
|
|
1132
|
+
default_tools=['Read', 'Write', 'Bash'],
|
|
1133
|
+
)
|
|
1134
|
+
tools = {'Read', 'Write', 'Bash'}
|
|
1135
|
+
|
|
1136
|
+
# Log mapping summary
|
|
1137
|
+
logger.info(
|
|
1138
|
+
"skill_mapping_completed",
|
|
1139
|
+
member_name=member_name or "unknown",
|
|
1140
|
+
total_skills=len(skill_ids),
|
|
1141
|
+
mapped_tools=list(tools),
|
|
1142
|
+
tool_count=len(tools),
|
|
1143
|
+
skill_types=skill_type_counts,
|
|
1144
|
+
unmapped_count=len(unmapped_skills),
|
|
1145
|
+
failed_count=len(failed_skills),
|
|
1146
|
+
)
|
|
1147
|
+
|
|
1148
|
+
if unmapped_skills:
|
|
1149
|
+
logger.info(
|
|
1150
|
+
"unmapped_skills_summary",
|
|
1151
|
+
member_name=member_name or "unknown",
|
|
1152
|
+
unmapped_skills=[f"{name} ({stype})" for name, stype in unmapped_skills],
|
|
1153
|
+
)
|
|
1154
|
+
|
|
1155
|
+
return list(tools)
|
|
1156
|
+
|
|
1157
|
+
def _map_model_to_sdk_format(self, model_id: str) -> str:
|
|
1158
|
+
"""
|
|
1159
|
+
Map model ID to SDK format (sonnet/opus/haiku/inherit).
|
|
1160
|
+
|
|
1161
|
+
Args:
|
|
1162
|
+
model_id: Full model ID (e.g., "kubiya/claude-sonnet-4")
|
|
1163
|
+
|
|
1164
|
+
Returns:
|
|
1165
|
+
SDK model format string
|
|
1166
|
+
"""
|
|
1167
|
+
if not model_id or model_id == 'inherit':
|
|
1168
|
+
return 'inherit'
|
|
1169
|
+
|
|
1170
|
+
model_lower = model_id.lower()
|
|
1171
|
+
|
|
1172
|
+
if 'sonnet' in model_lower:
|
|
1173
|
+
return 'sonnet'
|
|
1174
|
+
elif 'opus' in model_lower:
|
|
1175
|
+
return 'opus'
|
|
1176
|
+
elif 'haiku' in model_lower:
|
|
1177
|
+
return 'haiku'
|
|
1178
|
+
|
|
1179
|
+
# Default to inherit (use leader's model)
|
|
1180
|
+
return 'inherit'
|
|
1181
|
+
|
|
1182
|
+
def _build_team_leader_prompt(self, agents: List[Dict]) -> str:
|
|
1183
|
+
"""
|
|
1184
|
+
Build system prompt for team leader with native subagents.
|
|
1185
|
+
|
|
1186
|
+
Args:
|
|
1187
|
+
agents: List of agent configurations
|
|
1188
|
+
|
|
1189
|
+
Returns:
|
|
1190
|
+
Formatted system prompt
|
|
1191
|
+
"""
|
|
1192
|
+
member_descriptions = []
|
|
1193
|
+
|
|
1194
|
+
for agent in agents:
|
|
1195
|
+
name = agent.get('name', 'Agent')
|
|
1196
|
+
role = agent.get('role', 'Team member')
|
|
1197
|
+
agent_id = agent.get('id', 'unknown')
|
|
1198
|
+
member_descriptions.append(
|
|
1199
|
+
f"- **{name}** (ID: {agent_id}): {role}"
|
|
1200
|
+
)
|
|
1201
|
+
|
|
1202
|
+
return f"""You are the team leader coordinating a team of specialized AI agents.
|
|
1203
|
+
|
|
1204
|
+
Your team members:
|
|
1205
|
+
{chr(10).join(member_descriptions)}
|
|
1206
|
+
|
|
1207
|
+
Claude will automatically invoke the appropriate team member based on the task.
|
|
1208
|
+
Each member has their own:
|
|
1209
|
+
- Specialized system prompt and expertise
|
|
1210
|
+
- Dedicated tools and capabilities
|
|
1211
|
+
- Separate context (won't see each other's work)
|
|
1212
|
+
- Own model configuration
|
|
1213
|
+
|
|
1214
|
+
Coordinate effectively to solve the user's request. The SDK will handle routing tasks to the right team member.
|
|
1215
|
+
"""
|
|
1216
|
+
|
|
1217
|
+
def _all_members_are_claude_code(self, agents: List[Dict]) -> bool:
|
|
1218
|
+
"""
|
|
1219
|
+
Check if all team members use Claude Code runtime.
|
|
1220
|
+
|
|
1221
|
+
Optimized to batch-fetch agent configs to avoid N+1 queries.
|
|
1222
|
+
|
|
1223
|
+
Args:
|
|
1224
|
+
agents: List of agent configurations
|
|
1225
|
+
|
|
1226
|
+
Returns:
|
|
1227
|
+
True if all members are Claude Code runtime
|
|
1228
|
+
"""
|
|
1229
|
+
if not agents:
|
|
1230
|
+
logger.warning("no_agents_to_check_runtime")
|
|
1231
|
+
return False
|
|
1232
|
+
|
|
1233
|
+
agent_ids = [agent.get('id') for agent in agents if agent.get('id')]
|
|
1234
|
+
|
|
1235
|
+
if len(agent_ids) != len(agents):
|
|
1236
|
+
logger.warning(
|
|
1237
|
+
"some_agents_missing_ids_in_runtime_check",
|
|
1238
|
+
total_agents=len(agents),
|
|
1239
|
+
agents_with_ids=len(agent_ids),
|
|
1240
|
+
)
|
|
1241
|
+
# If some agents don't have IDs, we can't verify them
|
|
1242
|
+
return False
|
|
1243
|
+
|
|
1244
|
+
# Batch fetch all agent configs to avoid N+1 queries
|
|
1245
|
+
agent_configs = {}
|
|
1246
|
+
failed_fetches = []
|
|
1247
|
+
|
|
1248
|
+
logger.info(
|
|
1249
|
+
"batch_fetching_agent_configs_for_runtime_check",
|
|
1250
|
+
agent_count=len(agent_ids),
|
|
1251
|
+
agent_ids=agent_ids,
|
|
1252
|
+
)
|
|
1253
|
+
|
|
1254
|
+
# Try to fetch all agent configs
|
|
1255
|
+
for agent_id in agent_ids:
|
|
1256
|
+
try:
|
|
1257
|
+
agent_config = self.control_plane.get_agent(agent_id)
|
|
1258
|
+
if agent_config:
|
|
1259
|
+
agent_configs[agent_id] = agent_config
|
|
1260
|
+
else:
|
|
1261
|
+
logger.warning(
|
|
1262
|
+
"agent_config_not_found_in_runtime_check",
|
|
1263
|
+
agent_id=agent_id,
|
|
1264
|
+
)
|
|
1265
|
+
failed_fetches.append(agent_id)
|
|
1266
|
+
except Exception as e:
|
|
1267
|
+
logger.warning(
|
|
1268
|
+
"failed_to_fetch_agent_config_in_runtime_check",
|
|
1269
|
+
agent_id=agent_id,
|
|
1270
|
+
error=str(e),
|
|
1271
|
+
error_type=type(e).__name__,
|
|
1272
|
+
)
|
|
1273
|
+
failed_fetches.append(agent_id)
|
|
1274
|
+
|
|
1275
|
+
# If we couldn't fetch all configs, we can't verify runtime consistency
|
|
1276
|
+
if failed_fetches:
|
|
1277
|
+
logger.warning(
|
|
1278
|
+
"cannot_verify_all_agent_runtimes",
|
|
1279
|
+
failed_fetches=failed_fetches,
|
|
1280
|
+
fetched_count=len(agent_configs),
|
|
1281
|
+
total_count=len(agent_ids),
|
|
1282
|
+
)
|
|
1283
|
+
return False
|
|
1284
|
+
|
|
1285
|
+
# Check if all fetched agents use claude_code runtime
|
|
1286
|
+
non_claude_code_agents = []
|
|
1287
|
+
runtime_distribution = {}
|
|
1288
|
+
|
|
1289
|
+
for agent_id, config in agent_configs.items():
|
|
1290
|
+
runtime = config.get('runtime', 'default')
|
|
1291
|
+
runtime_distribution[runtime] = runtime_distribution.get(runtime, 0) + 1
|
|
1292
|
+
|
|
1293
|
+
if runtime != 'claude_code':
|
|
1294
|
+
agent_name = next(
|
|
1295
|
+
(a.get('name', agent_id) for a in agents if a.get('id') == agent_id),
|
|
1296
|
+
agent_id
|
|
1297
|
+
)
|
|
1298
|
+
non_claude_code_agents.append({
|
|
1299
|
+
'id': agent_id,
|
|
1300
|
+
'name': agent_name,
|
|
1301
|
+
'runtime': runtime,
|
|
1302
|
+
})
|
|
1303
|
+
|
|
1304
|
+
# Log runtime distribution for observability
|
|
1305
|
+
logger.info(
|
|
1306
|
+
"team_member_runtime_distribution",
|
|
1307
|
+
total_members=len(agent_configs),
|
|
1308
|
+
runtime_distribution=runtime_distribution,
|
|
1309
|
+
all_claude_code=len(non_claude_code_agents) == 0,
|
|
1310
|
+
)
|
|
1311
|
+
|
|
1312
|
+
if non_claude_code_agents:
|
|
1313
|
+
logger.info(
|
|
1314
|
+
"mixed_runtime_team_detected",
|
|
1315
|
+
claude_code_count=runtime_distribution.get('claude_code', 0),
|
|
1316
|
+
non_claude_code_count=len(non_claude_code_agents),
|
|
1317
|
+
non_claude_code_agents=[
|
|
1318
|
+
f"{a['name']} ({a['runtime']})" for a in non_claude_code_agents
|
|
1319
|
+
],
|
|
1320
|
+
decision="will_use_task_tool_delegation",
|
|
1321
|
+
)
|
|
1322
|
+
return False
|
|
1323
|
+
|
|
1324
|
+
logger.info(
|
|
1325
|
+
"all_members_are_claude_code",
|
|
1326
|
+
member_count=len(agent_configs),
|
|
1327
|
+
decision="will_use_native_subagents",
|
|
1328
|
+
)
|
|
1329
|
+
return True
|
|
1330
|
+
|
|
1331
|
+
def _build_team_context(self, agents: List[Dict]) -> str:
|
|
1332
|
+
"""
|
|
1333
|
+
Build team context description for system prompt.
|
|
1334
|
+
|
|
1335
|
+
Args:
|
|
1336
|
+
agents: List of agent configurations
|
|
1337
|
+
|
|
1338
|
+
Returns:
|
|
1339
|
+
Formatted team context string
|
|
1340
|
+
"""
|
|
1341
|
+
context_lines = []
|
|
1342
|
+
for i, agent in enumerate(agents, 1):
|
|
1343
|
+
name = agent.get("name", f"Agent {i}")
|
|
1344
|
+
role = agent.get("role", "No role specified")
|
|
1345
|
+
agent_id = agent.get("id", "unknown")
|
|
1346
|
+
|
|
1347
|
+
context_lines.append(
|
|
1348
|
+
f"{i}. **{name}** (ID: {agent_id})\n"
|
|
1349
|
+
f" Role: {role}\n"
|
|
1350
|
+
)
|
|
1351
|
+
|
|
1352
|
+
return "\n".join(context_lines)
|
|
1353
|
+
|
|
1354
|
+
def _build_execution_context_info(
|
|
1355
|
+
self,
|
|
1356
|
+
runtime_config: Dict[str, Any],
|
|
1357
|
+
skills: List[Any],
|
|
1358
|
+
mcp_servers: Optional[Dict[str, Any]],
|
|
1359
|
+
team_config: Dict[str, Any]
|
|
1360
|
+
) -> str:
|
|
1361
|
+
"""
|
|
1362
|
+
Build comprehensive execution environment context for system prompt.
|
|
1363
|
+
|
|
1364
|
+
This provides the team with awareness of:
|
|
1365
|
+
- Available environment variables (secrets, integrations, config)
|
|
1366
|
+
- Available skills/tools
|
|
1367
|
+
- MCP servers
|
|
1368
|
+
- Runtime configuration
|
|
1369
|
+
|
|
1370
|
+
Args:
|
|
1371
|
+
runtime_config: Runtime configuration with env vars
|
|
1372
|
+
skills: List of available skills
|
|
1373
|
+
mcp_servers: Dictionary of MCP server configurations
|
|
1374
|
+
team_config: Team configuration
|
|
1375
|
+
|
|
1376
|
+
Returns:
|
|
1377
|
+
Formatted execution context string for system prompt
|
|
1378
|
+
"""
|
|
1379
|
+
context_parts = []
|
|
1380
|
+
context_parts.append("---")
|
|
1381
|
+
context_parts.append("")
|
|
1382
|
+
context_parts.append("# 🔧 Execution Environment Context")
|
|
1383
|
+
context_parts.append("")
|
|
1384
|
+
context_parts.append("You are running in a managed execution environment with the following resources available:")
|
|
1385
|
+
context_parts.append("")
|
|
1386
|
+
|
|
1387
|
+
# 1. Environment Variables
|
|
1388
|
+
if runtime_config and "env" in runtime_config:
|
|
1389
|
+
available_env_vars = runtime_config["env"]
|
|
1390
|
+
|
|
1391
|
+
# Categorize environment variables
|
|
1392
|
+
secrets = [k for k in available_env_vars.keys() if any(
|
|
1393
|
+
keyword in k.lower()
|
|
1394
|
+
for keyword in ["secret", "password", "credential", "api_key", "private_key"]
|
|
1395
|
+
) and k not in ["KUBIYA_API_KEY", "KUBIYA_API_BASE", "ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL"]]
|
|
1396
|
+
|
|
1397
|
+
integrations = [k for k in available_env_vars.keys() if any(
|
|
1398
|
+
prefix in k
|
|
1399
|
+
for prefix in ["GH_TOKEN", "GITHUB_", "JIRA_", "SLACK_", "AWS_", "GCP_", "AZURE_"]
|
|
1400
|
+
)]
|
|
1401
|
+
|
|
1402
|
+
inherited_vars = [k for k in available_env_vars.keys()
|
|
1403
|
+
if k not in secrets
|
|
1404
|
+
and k not in integrations
|
|
1405
|
+
and k not in ["KUBIYA_API_KEY", "KUBIYA_API_BASE", "ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL", "LITELLM_API_KEY", "LITELLM_API_BASE"]]
|
|
1406
|
+
|
|
1407
|
+
context_parts.append("## 📦 Environment Variables")
|
|
1408
|
+
context_parts.append("")
|
|
1409
|
+
|
|
1410
|
+
if secrets:
|
|
1411
|
+
context_parts.append("**Secrets & API Keys** (use these for authenticated operations):")
|
|
1412
|
+
for var in sorted(secrets):
|
|
1413
|
+
context_parts.append(f"- `${var}` - Secret/credential available as environment variable")
|
|
1414
|
+
context_parts.append("")
|
|
1415
|
+
|
|
1416
|
+
if integrations:
|
|
1417
|
+
context_parts.append("**Integration Tokens** (pre-configured service access):")
|
|
1418
|
+
for var in sorted(integrations):
|
|
1419
|
+
service = var.split("_")[0].title() if "_" in var else var
|
|
1420
|
+
context_parts.append(f"- `${var}` - {service} integration token")
|
|
1421
|
+
context_parts.append("")
|
|
1422
|
+
|
|
1423
|
+
if inherited_vars:
|
|
1424
|
+
context_parts.append("**Configuration Variables** (inherited from environment):")
|
|
1425
|
+
# Limit to first 10 to avoid clutter
|
|
1426
|
+
for var in sorted(inherited_vars)[:10]:
|
|
1427
|
+
context_parts.append(f"- `${var}`")
|
|
1428
|
+
if len(inherited_vars) > 10:
|
|
1429
|
+
context_parts.append(f"- ... and {len(inherited_vars) - 10} more")
|
|
1430
|
+
context_parts.append("")
|
|
1431
|
+
|
|
1432
|
+
if secrets or integrations or inherited_vars:
|
|
1433
|
+
context_parts.append("**Usage Examples:**")
|
|
1434
|
+
context_parts.append("```bash")
|
|
1435
|
+
context_parts.append("# Access in Bash commands")
|
|
1436
|
+
context_parts.append("echo $VARIABLE_NAME")
|
|
1437
|
+
context_parts.append("")
|
|
1438
|
+
if integrations:
|
|
1439
|
+
example_token = sorted(integrations)[0]
|
|
1440
|
+
if "GH" in example_token or "GITHUB" in example_token:
|
|
1441
|
+
context_parts.append("# Use with GitHub API")
|
|
1442
|
+
context_parts.append(f"curl -H \"Authorization: token ${example_token}\" https://api.github.com/user")
|
|
1443
|
+
elif "JIRA" in example_token:
|
|
1444
|
+
context_parts.append("# Use with Jira API")
|
|
1445
|
+
context_parts.append(f"curl -H \"Authorization: Bearer ${example_token}\" https://yourinstance.atlassian.net/rest/api/3/myself")
|
|
1446
|
+
context_parts.append("```")
|
|
1447
|
+
context_parts.append("")
|
|
1448
|
+
|
|
1449
|
+
# 2. Available Skills/Tools
|
|
1450
|
+
if skills:
|
|
1451
|
+
context_parts.append("## 🛠️ Available Skills/Tools")
|
|
1452
|
+
context_parts.append("")
|
|
1453
|
+
skill_names = []
|
|
1454
|
+
for skill in skills:
|
|
1455
|
+
if isinstance(skill, dict):
|
|
1456
|
+
skill_names.append(skill.get("name", skill.get("type", "Unknown")))
|
|
1457
|
+
else:
|
|
1458
|
+
skill_names.append(type(skill).__name__ if hasattr(skill, '__class__') else str(skill))
|
|
1459
|
+
|
|
1460
|
+
if skill_names:
|
|
1461
|
+
context_parts.append(f"You have access to {len(skill_names)} skill(s):")
|
|
1462
|
+
for skill_name in sorted(set(skill_names))[:15]: # Limit to 15 to avoid clutter
|
|
1463
|
+
context_parts.append(f"- `{skill_name}`")
|
|
1464
|
+
if len(set(skill_names)) > 15:
|
|
1465
|
+
context_parts.append(f"- ... and {len(set(skill_names)) - 15} more")
|
|
1466
|
+
context_parts.append("")
|
|
1467
|
+
|
|
1468
|
+
# 3. MCP Servers
|
|
1469
|
+
if mcp_servers:
|
|
1470
|
+
context_parts.append("## 🔌 MCP Servers")
|
|
1471
|
+
context_parts.append("")
|
|
1472
|
+
context_parts.append(f"You have access to {len(mcp_servers)} MCP server(s):")
|
|
1473
|
+
for server_name in sorted(mcp_servers.keys())[:10]: # Limit to 10
|
|
1474
|
+
context_parts.append(f"- `{server_name}`")
|
|
1475
|
+
if len(mcp_servers) > 10:
|
|
1476
|
+
context_parts.append(f"- ... and {len(mcp_servers) - 10} more")
|
|
1477
|
+
context_parts.append("")
|
|
1478
|
+
context_parts.append("**Note:** All environment variables listed above are automatically available to these MCP servers.")
|
|
1479
|
+
context_parts.append("")
|
|
1480
|
+
|
|
1481
|
+
# 4. Best Practices
|
|
1482
|
+
context_parts.append("## 💡 Best Practices")
|
|
1483
|
+
context_parts.append("")
|
|
1484
|
+
context_parts.append("- **Environment Variables**: All listed variables are ready to use - no configuration needed")
|
|
1485
|
+
context_parts.append("- **Secrets**: Never log or display secret values in responses")
|
|
1486
|
+
context_parts.append("- **Integration Tokens**: These provide pre-authorized access to external services")
|
|
1487
|
+
context_parts.append("- **MCP Tools**: Prefer using MCP tools over Bash when available for better reliability")
|
|
1488
|
+
context_parts.append("")
|
|
1489
|
+
context_parts.append("---")
|
|
1490
|
+
|
|
1491
|
+
logger.info(
|
|
1492
|
+
"execution_context_info_built",
|
|
1493
|
+
env_vars_count=len(runtime_config.get("env", {})) if runtime_config else 0,
|
|
1494
|
+
skills_count=len(skills) if skills else 0,
|
|
1495
|
+
mcp_servers_count=len(mcp_servers) if mcp_servers else 0
|
|
1496
|
+
)
|
|
1497
|
+
|
|
1498
|
+
return "\n".join(context_parts)
|
|
1499
|
+
|
|
1500
|
+
def _get_framework_name(self, runtime_type: RuntimeType) -> str:
|
|
1501
|
+
"""
|
|
1502
|
+
Get friendly framework name for runtime type.
|
|
1503
|
+
|
|
1504
|
+
Args:
|
|
1505
|
+
runtime_type: Runtime type enum
|
|
1506
|
+
|
|
1507
|
+
Returns:
|
|
1508
|
+
Framework name string
|
|
1509
|
+
"""
|
|
1510
|
+
mapping = {
|
|
1511
|
+
RuntimeType.DEFAULT: "Agno",
|
|
1512
|
+
RuntimeType.CLAUDE_CODE: "Claude Code SDK",
|
|
1513
|
+
RuntimeType.AGENT_RUNTIME: "Agent Runtime (Rust/GRPC)",
|
|
1514
|
+
}
|
|
1515
|
+
return mapping.get(runtime_type, "Unknown")
|
|
1516
|
+
|
|
1517
|
+
# ==================== NEW: Agent-Runtime Integration ====================
|
|
1518
|
+
|
|
1519
|
+
def _detect_execution_mode(self, input: Any, runtime_type: RuntimeType) -> str:
|
|
1520
|
+
"""
|
|
1521
|
+
Detect which execution mode to use based on configuration and runtime type.
|
|
1522
|
+
|
|
1523
|
+
Execution modes:
|
|
1524
|
+
- "agent_runtime_native": Use agent-runtime GRPC with native subagents
|
|
1525
|
+
- "claude_code_native": Use Claude Code SDK with native subagents
|
|
1526
|
+
- "claude_code_task": Use Claude Code SDK with Task tool delegation
|
|
1527
|
+
- "agno": Use Agno-based team execution
|
|
1528
|
+
|
|
1529
|
+
Priority:
|
|
1530
|
+
1. Explicit runtime=agent_runtime in team_config
|
|
1531
|
+
2. ENABLE_AGENT_RUNTIME_TEAMS environment variable
|
|
1532
|
+
3. Runtime type from input
|
|
1533
|
+
4. Default to agno
|
|
1534
|
+
|
|
1535
|
+
Args:
|
|
1536
|
+
input: TeamExecutionInput
|
|
1537
|
+
runtime_type: Detected runtime type
|
|
1538
|
+
|
|
1539
|
+
Returns:
|
|
1540
|
+
Execution mode string
|
|
1541
|
+
"""
|
|
1542
|
+
team_config = getattr(input, "team_config", {}) or {}
|
|
1543
|
+
runtime_config_str = team_config.get("runtime", "default")
|
|
1544
|
+
|
|
1545
|
+
# Check for explicit agent-runtime request
|
|
1546
|
+
if runtime_config_str == "agent_runtime":
|
|
1547
|
+
logger.info(
|
|
1548
|
+
"agent_runtime_explicitly_requested",
|
|
1549
|
+
execution_id=input.execution_id[:8],
|
|
1550
|
+
)
|
|
1551
|
+
return "agent_runtime_native"
|
|
1552
|
+
|
|
1553
|
+
# Check environment variable for agent-runtime teams
|
|
1554
|
+
if os.getenv("ENABLE_AGENT_RUNTIME_TEAMS", "false").lower() == "true":
|
|
1555
|
+
# Only use agent-runtime if all members are compatible
|
|
1556
|
+
if input.agents and self._all_members_are_compatible_with_agent_runtime(input.agents):
|
|
1557
|
+
logger.info(
|
|
1558
|
+
"agent_runtime_enabled_via_env",
|
|
1559
|
+
execution_id=input.execution_id[:8],
|
|
1560
|
+
member_count=len(input.agents),
|
|
1561
|
+
)
|
|
1562
|
+
return "agent_runtime_native"
|
|
1563
|
+
else:
|
|
1564
|
+
logger.warning(
|
|
1565
|
+
"agent_runtime_env_set_but_members_incompatible",
|
|
1566
|
+
execution_id=input.execution_id[:8],
|
|
1567
|
+
)
|
|
1568
|
+
|
|
1569
|
+
# Check if runtime type is explicitly AGENT_RUNTIME
|
|
1570
|
+
if runtime_type == RuntimeType.AGENT_RUNTIME:
|
|
1571
|
+
return "agent_runtime_native"
|
|
1572
|
+
|
|
1573
|
+
# Check for Claude Code native subagents
|
|
1574
|
+
if runtime_type == RuntimeType.CLAUDE_CODE:
|
|
1575
|
+
if input.agents and self._all_members_are_claude_code(input.agents):
|
|
1576
|
+
return "claude_code_native"
|
|
1577
|
+
else:
|
|
1578
|
+
return "claude_code_task"
|
|
1579
|
+
|
|
1580
|
+
# Default to Agno
|
|
1581
|
+
return "agno"
|
|
1582
|
+
|
|
1583
|
+
def _all_members_are_compatible_with_agent_runtime(self, agents: List[Dict]) -> bool:
|
|
1584
|
+
"""
|
|
1585
|
+
Check if all team members are compatible with agent-runtime.
|
|
1586
|
+
|
|
1587
|
+
Currently, agent-runtime supports Claude-based models.
|
|
1588
|
+
This can be extended to check for specific runtime requirements.
|
|
1589
|
+
|
|
1590
|
+
Args:
|
|
1591
|
+
agents: List of agent configurations
|
|
1592
|
+
|
|
1593
|
+
Returns:
|
|
1594
|
+
True if all members can use agent-runtime
|
|
1595
|
+
"""
|
|
1596
|
+
if not agents:
|
|
1597
|
+
return False
|
|
1598
|
+
|
|
1599
|
+
agent_ids = [agent.get('id') for agent in agents if agent.get('id')]
|
|
1600
|
+
|
|
1601
|
+
if len(agent_ids) != len(agents):
|
|
1602
|
+
logger.warning(
|
|
1603
|
+
"some_agents_missing_ids_in_compatibility_check",
|
|
1604
|
+
total_agents=len(agents),
|
|
1605
|
+
agents_with_ids=len(agent_ids),
|
|
1606
|
+
)
|
|
1607
|
+
return False
|
|
1608
|
+
|
|
1609
|
+
# For now, consider all agents compatible with agent-runtime
|
|
1610
|
+
# In the future, we can add more specific checks (model type, runtime requirements, etc.)
|
|
1611
|
+
logger.info(
|
|
1612
|
+
"agent_runtime_compatibility_check",
|
|
1613
|
+
agent_count=len(agents),
|
|
1614
|
+
compatible=True,
|
|
1615
|
+
)
|
|
1616
|
+
|
|
1617
|
+
return True
|
|
1618
|
+
|
|
1619
|
+
async def _execute_via_agent_runtime(
|
|
1620
|
+
self,
|
|
1621
|
+
input: Any,
|
|
1622
|
+
session_history: List[Dict]
|
|
1623
|
+
) -> Dict[str, Any]:
|
|
1624
|
+
"""
|
|
1625
|
+
Execute team using agent-runtime GRPC service with native subagents.
|
|
1626
|
+
|
|
1627
|
+
This method:
|
|
1628
|
+
1. Builds agents configuration from team members
|
|
1629
|
+
2. Creates RuntimeExecutionContext with agents field
|
|
1630
|
+
3. Sets enable_native_subagents=True
|
|
1631
|
+
4. Executes via agent-runtime with streaming
|
|
1632
|
+
|
|
1633
|
+
Args:
|
|
1634
|
+
input: TeamExecutionInput
|
|
1635
|
+
session_history: Previous conversation messages
|
|
1636
|
+
|
|
1637
|
+
Returns:
|
|
1638
|
+
Result dict with response, usage, success, etc.
|
|
1639
|
+
"""
|
|
1640
|
+
execution_id = input.execution_id
|
|
1641
|
+
|
|
1642
|
+
logger.info(
|
|
1643
|
+
"executing_via_agent_runtime",
|
|
1644
|
+
execution_id=execution_id[:8],
|
|
1645
|
+
member_count=len(input.agents) if input.agents else 0,
|
|
1646
|
+
)
|
|
1647
|
+
|
|
1648
|
+
# STEP 1: Build agents configuration from team members
|
|
1649
|
+
team_config = getattr(input, "team_config", {}) or {}
|
|
1650
|
+
agents_config = await self._build_agents_config_for_agent_runtime(input.agents, team_config)
|
|
1651
|
+
|
|
1652
|
+
logger.info(
|
|
1653
|
+
"agents_config_built",
|
|
1654
|
+
execution_id=execution_id[:8],
|
|
1655
|
+
agent_count=len(agents_config),
|
|
1656
|
+
)
|
|
1657
|
+
|
|
1658
|
+
# STEP 2: Build team leader system prompt
|
|
1659
|
+
team_context = self._build_team_context(input.agents)
|
|
1660
|
+
system_prompt = f"""You are the team leader coordinating a team of specialized AI agents.
|
|
1661
|
+
|
|
1662
|
+
Your team members:
|
|
1663
|
+
{team_context}
|
|
1664
|
+
|
|
1665
|
+
You have native access to your team members. When you need a team member to perform a task, the runtime will automatically route the work to the appropriate agent based on their expertise.
|
|
1666
|
+
|
|
1667
|
+
Your goal is to coordinate the team effectively to solve the user's request.
|
|
1668
|
+
"""
|
|
1669
|
+
|
|
1670
|
+
# STEP 3: Get skills for team leader
|
|
1671
|
+
logger.info("fetching_skills_for_team_leader")
|
|
1672
|
+
skills = []
|
|
1673
|
+
skill_configs = []
|
|
1674
|
+
try:
|
|
1675
|
+
if input.agents:
|
|
1676
|
+
leader_id = input.agents[0].get("id")
|
|
1677
|
+
if leader_id:
|
|
1678
|
+
skill_configs = self.control_plane.get_skills(leader_id)
|
|
1679
|
+
if skill_configs:
|
|
1680
|
+
logger.info("skills_resolved", count=len(skill_configs))
|
|
1681
|
+
|
|
1682
|
+
from control_plane_api.worker.services.skill_factory import SkillFactory
|
|
1683
|
+
|
|
1684
|
+
skill_factory = SkillFactory(runtime_type="agent_runtime")
|
|
1685
|
+
skill_factory.initialize()
|
|
1686
|
+
skills = skill_factory.create_skills_from_list(skill_configs, execution_id=execution_id)
|
|
1687
|
+
|
|
1688
|
+
if skills:
|
|
1689
|
+
logger.info("skills_instantiated", count=len(skills))
|
|
1690
|
+
except Exception as e:
|
|
1691
|
+
logger.warning("skill_fetch_error", error=str(e))
|
|
1692
|
+
|
|
1693
|
+
# STEP 4: Inject environment variables into MCP servers
|
|
1694
|
+
from control_plane_api.worker.activities.runtime_activities import inject_env_vars_into_mcp_servers
|
|
1695
|
+
mcp_servers_with_env = inject_env_vars_into_mcp_servers(
|
|
1696
|
+
mcp_servers=getattr(input, "mcp_servers", None),
|
|
1697
|
+
agent_config=team_config,
|
|
1698
|
+
runtime_config=team_config.get("runtime_config"),
|
|
1699
|
+
control_plane_client=self.control_plane,
|
|
1700
|
+
team_id=input.team_id,
|
|
1701
|
+
)
|
|
1702
|
+
|
|
1703
|
+
# STEP 5: Build RuntimeExecutionContext with agents configuration
|
|
1704
|
+
context = RuntimeExecutionContext(
|
|
1705
|
+
execution_id=execution_id,
|
|
1706
|
+
agent_id=input.team_id,
|
|
1707
|
+
organization_id=input.organization_id,
|
|
1708
|
+
prompt=input.prompt,
|
|
1709
|
+
system_prompt=system_prompt,
|
|
1710
|
+
conversation_history=self._simplify_session_to_conversation(session_history),
|
|
1711
|
+
session_messages=session_history, # Full messages with metadata
|
|
1712
|
+
session_id=input.session_id,
|
|
1713
|
+
model_id=input.model_id,
|
|
1714
|
+
model_config=getattr(input, "model_config", None),
|
|
1715
|
+
agent_config=team_config,
|
|
1716
|
+
skills=skill_configs, # Raw skill configs for agent-runtime
|
|
1717
|
+
mcp_servers=mcp_servers_with_env,
|
|
1718
|
+
user_metadata=getattr(input, "user_metadata", None),
|
|
1719
|
+
runtime_config=team_config.get("runtime_config"),
|
|
1720
|
+
agents=agents_config, # Sub-agent definitions
|
|
1721
|
+
enable_native_subagents=True,
|
|
1722
|
+
enable_session_persistence=bool(input.session_id),
|
|
1723
|
+
# Enforcement context
|
|
1724
|
+
user_email=input.user_email if hasattr(input, 'user_email') else None,
|
|
1725
|
+
user_id=input.user_id,
|
|
1726
|
+
team_id=input.team_id,
|
|
1727
|
+
environment=os.getenv("ENVIRONMENT", "production"),
|
|
1728
|
+
)
|
|
1729
|
+
|
|
1730
|
+
# STEP 6: Create agent-runtime instance
|
|
1731
|
+
logger.info("creating_agent_runtime_instance")
|
|
1732
|
+
runtime = await self.runtime_factory.create_runtime(
|
|
1733
|
+
runtime_type=RuntimeType.AGENT_RUNTIME,
|
|
1734
|
+
control_plane_client=self.control_plane,
|
|
1735
|
+
cancellation_manager=self.cancellation_manager,
|
|
1736
|
+
)
|
|
1737
|
+
|
|
1738
|
+
logger.info("agent_runtime_created", info=runtime.get_runtime_info())
|
|
1739
|
+
|
|
1740
|
+
# STEP 7: Execute with streaming
|
|
1741
|
+
self.streaming_helper = StreamingHelper(
|
|
1742
|
+
control_plane_client=self.control_plane,
|
|
1743
|
+
execution_id=execution_id
|
|
1744
|
+
)
|
|
1745
|
+
|
|
1746
|
+
if runtime.supports_streaming():
|
|
1747
|
+
logger.info("executing_with_streaming")
|
|
1748
|
+
result = await self._execute_streaming(
|
|
1749
|
+
runtime, context, input, self.streaming_helper
|
|
1750
|
+
)
|
|
1751
|
+
else:
|
|
1752
|
+
logger.info("executing_without_streaming")
|
|
1753
|
+
exec_result = await runtime.execute(context)
|
|
1754
|
+
result = self._convert_exec_result_to_dict(exec_result, input)
|
|
1755
|
+
|
|
1756
|
+
logger.info(
|
|
1757
|
+
"agent_runtime_execution_completed",
|
|
1758
|
+
execution_id=execution_id[:8],
|
|
1759
|
+
success=result.get("success"),
|
|
1760
|
+
)
|
|
1761
|
+
|
|
1762
|
+
return result
|
|
1763
|
+
|
|
1764
|
+
async def _build_agents_config_for_agent_runtime(
|
|
1765
|
+
self,
|
|
1766
|
+
agents: List[Dict],
|
|
1767
|
+
team_config: Dict
|
|
1768
|
+
) -> Dict[str, Dict[str, Any]]:
|
|
1769
|
+
"""
|
|
1770
|
+
Build agents configuration dictionary for agent-runtime.
|
|
1771
|
+
|
|
1772
|
+
Fetches agent configs from Control Plane and formats for GRPC.
|
|
1773
|
+
|
|
1774
|
+
Args:
|
|
1775
|
+
agents: List of team member definitions
|
|
1776
|
+
team_config: Team configuration dictionary
|
|
1777
|
+
|
|
1778
|
+
Returns:
|
|
1779
|
+
Dict mapping agent_id to agent definition
|
|
1780
|
+
"""
|
|
1781
|
+
agents_config = {}
|
|
1782
|
+
|
|
1783
|
+
if not agents:
|
|
1784
|
+
logger.warning("no_agents_to_build_config")
|
|
1785
|
+
return agents_config
|
|
1786
|
+
|
|
1787
|
+
# Batch fetch agent configs to avoid N+1 queries
|
|
1788
|
+
agent_ids = [agent.get("id") for agent in agents if agent.get("id")]
|
|
1789
|
+
|
|
1790
|
+
logger.info(
|
|
1791
|
+
"batch_fetching_agent_configs_for_agent_runtime",
|
|
1792
|
+
agent_count=len(agent_ids),
|
|
1793
|
+
)
|
|
1794
|
+
|
|
1795
|
+
for agent_id in agent_ids:
|
|
1796
|
+
try:
|
|
1797
|
+
agent_config = self.control_plane.get_agent(agent_id)
|
|
1798
|
+
if not agent_config:
|
|
1799
|
+
logger.warning("agent_config_not_found", agent_id=agent_id)
|
|
1800
|
+
continue
|
|
1801
|
+
|
|
1802
|
+
# Find agent definition from input
|
|
1803
|
+
agent_def = next((a for a in agents if a.get("id") == agent_id), None)
|
|
1804
|
+
if not agent_def:
|
|
1805
|
+
logger.warning("agent_def_not_found_in_input", agent_id=agent_id)
|
|
1806
|
+
continue
|
|
1807
|
+
|
|
1808
|
+
# Get skills for this agent
|
|
1809
|
+
skill_names = []
|
|
1810
|
+
try:
|
|
1811
|
+
skill_configs = self.control_plane.get_skills(agent_id)
|
|
1812
|
+
if skill_configs:
|
|
1813
|
+
skill_names = [skill.get("name") for skill in skill_configs if skill.get("name")]
|
|
1814
|
+
except Exception as e:
|
|
1815
|
+
logger.warning("failed_to_fetch_skills", agent_id=agent_id, error=str(e))
|
|
1816
|
+
|
|
1817
|
+
# Build agent definition
|
|
1818
|
+
agents_config[agent_id] = {
|
|
1819
|
+
"description": agent_def.get("role", agent_config.get("description", "")),
|
|
1820
|
+
"prompt": agent_config.get("system_prompt", ""),
|
|
1821
|
+
"tools": skill_names,
|
|
1822
|
+
"model": agent_config.get("model_id", "inherit"),
|
|
1823
|
+
"config": agent_config.get("runtime_config", {}),
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
logger.debug(
|
|
1827
|
+
"agent_config_built",
|
|
1828
|
+
agent_id=agent_id,
|
|
1829
|
+
tools_count=len(skill_names),
|
|
1830
|
+
)
|
|
1831
|
+
|
|
1832
|
+
except Exception as e:
|
|
1833
|
+
logger.error(
|
|
1834
|
+
"failed_to_build_agent_config",
|
|
1835
|
+
agent_id=agent_id,
|
|
1836
|
+
error=str(e),
|
|
1837
|
+
)
|
|
1838
|
+
|
|
1839
|
+
logger.info(
|
|
1840
|
+
"agents_config_built_successfully",
|
|
1841
|
+
agent_count=len(agents_config),
|
|
1842
|
+
)
|
|
1843
|
+
|
|
1844
|
+
return agents_config
|
|
1845
|
+
|
|
1846
|
+
def _simplify_session_to_conversation(
|
|
1847
|
+
self,
|
|
1848
|
+
session_messages: List[Dict]
|
|
1849
|
+
) -> List[Dict[str, str]]:
|
|
1850
|
+
"""
|
|
1851
|
+
Convert full session messages to simple conversation_history format.
|
|
1852
|
+
|
|
1853
|
+
Extracts only role and content for backward compatibility with runtimes
|
|
1854
|
+
that don't need full metadata.
|
|
1855
|
+
|
|
1856
|
+
Args:
|
|
1857
|
+
session_messages: Full messages with metadata
|
|
1858
|
+
|
|
1859
|
+
Returns:
|
|
1860
|
+
Simplified list with only role + content
|
|
1861
|
+
"""
|
|
1862
|
+
return [
|
|
1863
|
+
{"role": msg.get("role"), "content": msg.get("content")}
|
|
1864
|
+
for msg in session_messages
|
|
1865
|
+
if msg.get("role") and msg.get("content")
|
|
1866
|
+
]
|