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,970 @@
|
|
|
1
|
+
"""Temporal workflow for plan orchestration using Claude Code Agent SDK.
|
|
2
|
+
|
|
3
|
+
This workflow uses a Claude Code agent to intelligently orchestrate plan execution.
|
|
4
|
+
The agent has access to tools that allow it to:
|
|
5
|
+
- Execute tasks (spawn child agent workflows)
|
|
6
|
+
- Check task status
|
|
7
|
+
- Validate task completion
|
|
8
|
+
- Update plan state
|
|
9
|
+
|
|
10
|
+
The agent manages the entire plan flow, making intelligent decisions about:
|
|
11
|
+
- Task execution order (respecting dependencies)
|
|
12
|
+
- Error handling and retries
|
|
13
|
+
- Task validation
|
|
14
|
+
- Progress reporting
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import json
|
|
19
|
+
from typing import Dict, Any, List
|
|
20
|
+
from datetime import timedelta, datetime, timezone
|
|
21
|
+
from temporalio import workflow
|
|
22
|
+
|
|
23
|
+
# Import activities and models in unsafe block
|
|
24
|
+
with workflow.unsafe.imports_passed_through():
|
|
25
|
+
from worker_internal.planner.activities import (
|
|
26
|
+
create_plan_execution,
|
|
27
|
+
update_plan_state,
|
|
28
|
+
execute_task_activity,
|
|
29
|
+
validate_task_completion,
|
|
30
|
+
get_task_status_activity,
|
|
31
|
+
continue_task_activity,
|
|
32
|
+
publish_event_activity,
|
|
33
|
+
)
|
|
34
|
+
from worker_internal.planner.models import (
|
|
35
|
+
PlanOrchestratorInput,
|
|
36
|
+
PlanExecutionSummary,
|
|
37
|
+
CreatePlanExecutionInput,
|
|
38
|
+
UpdatePlanStateInput,
|
|
39
|
+
TaskExecutionResult,
|
|
40
|
+
TaskValidationResult,
|
|
41
|
+
TaskStatus,
|
|
42
|
+
PlanStatus,
|
|
43
|
+
PlanTask,
|
|
44
|
+
)
|
|
45
|
+
from worker_internal.planner.agent_tools import get_agent_tools_formatted
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@workflow.defn
|
|
49
|
+
class PlanOrchestratorWorkflow:
|
|
50
|
+
"""
|
|
51
|
+
Orchestrates plan execution using a Claude Code agent.
|
|
52
|
+
|
|
53
|
+
The agent is given the full plan context and tools to execute tasks,
|
|
54
|
+
check status, validate completion, and update state. It makes intelligent
|
|
55
|
+
decisions about execution flow while the workflow provides durability.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
@workflow.signal
|
|
59
|
+
async def continue_task_signal(self, data: dict):
|
|
60
|
+
"""
|
|
61
|
+
Signal handler for continuing a task that's waiting for user input.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
data: Dict containing task_id and user_message
|
|
65
|
+
"""
|
|
66
|
+
task_id = data["task_id"]
|
|
67
|
+
user_message = data["user_message"]
|
|
68
|
+
|
|
69
|
+
workflow.logger.info(
|
|
70
|
+
f"continue_task_signal_received: task_id={task_id}, message={user_message[:100]}"
|
|
71
|
+
)
|
|
72
|
+
self._pending_user_messages[task_id] = user_message
|
|
73
|
+
|
|
74
|
+
def __init__(self):
|
|
75
|
+
self._plan_execution_id: str = ""
|
|
76
|
+
self._task_results: Dict[int, TaskExecutionResult] = {}
|
|
77
|
+
self._completed_tasks: int = 0
|
|
78
|
+
self._failed_tasks: int = 0
|
|
79
|
+
self._total_tasks: int = 0
|
|
80
|
+
self._tasks: List[PlanTask] = []
|
|
81
|
+
self._agent_id: str = ""
|
|
82
|
+
self._organization_id: str = ""
|
|
83
|
+
self._worker_queue_id: str = ""
|
|
84
|
+
self._jwt_token: str = ""
|
|
85
|
+
self._model_id: str = ""
|
|
86
|
+
self._pending_user_messages: Dict[int, str] = {} # task_id -> user_message
|
|
87
|
+
self._waiting_tasks: List[Dict] = [] # List of tasks currently waiting for user input
|
|
88
|
+
|
|
89
|
+
@workflow.run
|
|
90
|
+
async def run(self, input: PlanOrchestratorInput) -> PlanExecutionSummary:
|
|
91
|
+
"""
|
|
92
|
+
Execute a plan using Claude Code agent orchestration.
|
|
93
|
+
|
|
94
|
+
The agent manages the entire plan flow using provided tools.
|
|
95
|
+
"""
|
|
96
|
+
# Import TaskStatus at function scope
|
|
97
|
+
from worker_internal.planner.models import TaskExecutionResult, TaskStatus
|
|
98
|
+
|
|
99
|
+
workflow.logger.info(
|
|
100
|
+
"plan_orchestrator_started",
|
|
101
|
+
extra={
|
|
102
|
+
"plan_title": input.plan.title,
|
|
103
|
+
"organization_id": input.organization_id,
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Initialize workflow state
|
|
108
|
+
execution_id = input.execution_id or str(workflow.uuid4())
|
|
109
|
+
self._plan_execution_id = execution_id
|
|
110
|
+
self._organization_id = input.organization_id
|
|
111
|
+
self._agent_id = input.agent_id
|
|
112
|
+
self._worker_queue_id = input.worker_queue_id
|
|
113
|
+
self._jwt_token = input.jwt_token or ""
|
|
114
|
+
|
|
115
|
+
# Extract tasks from plan
|
|
116
|
+
if input.plan.team_breakdown:
|
|
117
|
+
self._tasks = input.plan.team_breakdown[0].tasks
|
|
118
|
+
self._total_tasks = len(self._tasks)
|
|
119
|
+
# Always use claude-sonnet-4 for the orchestrator agent (plan model is for tasks)
|
|
120
|
+
self._model_id = "kubiya/claude-sonnet-4"
|
|
121
|
+
|
|
122
|
+
# Load previous task results if this is a continuation
|
|
123
|
+
if input.is_continuation and input.previous_task_results:
|
|
124
|
+
try:
|
|
125
|
+
workflow.logger.info(
|
|
126
|
+
"loading_previous_task_results",
|
|
127
|
+
extra={"task_count": len(input.previous_task_results)}
|
|
128
|
+
)
|
|
129
|
+
# Convert dict results to TaskExecutionResult objects
|
|
130
|
+
for task_id_str, result_data in input.previous_task_results.items():
|
|
131
|
+
task_id = int(task_id_str)
|
|
132
|
+
# Reconstruct TaskExecutionResult from dict
|
|
133
|
+
self._task_results[task_id] = TaskExecutionResult(**result_data)
|
|
134
|
+
|
|
135
|
+
# Update counts based on previous status
|
|
136
|
+
if self._task_results[task_id].status == TaskStatus.SUCCESS:
|
|
137
|
+
self._completed_tasks += 1
|
|
138
|
+
elif self._task_results[task_id].status == TaskStatus.FAILED:
|
|
139
|
+
self._failed_tasks += 1
|
|
140
|
+
|
|
141
|
+
workflow.logger.info(
|
|
142
|
+
"previous_results_loaded",
|
|
143
|
+
extra={
|
|
144
|
+
"loaded_tasks": len(self._task_results),
|
|
145
|
+
"completed": self._completed_tasks,
|
|
146
|
+
"failed": self._failed_tasks,
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
except Exception as e:
|
|
150
|
+
workflow.logger.error(
|
|
151
|
+
"failed_to_load_previous_results",
|
|
152
|
+
extra={"error": str(e)}
|
|
153
|
+
)
|
|
154
|
+
# Continue without previous results
|
|
155
|
+
|
|
156
|
+
started_at = datetime.now(timezone.utc)
|
|
157
|
+
|
|
158
|
+
# Step 1: Create plan execution record
|
|
159
|
+
await workflow.execute_activity(
|
|
160
|
+
create_plan_execution,
|
|
161
|
+
CreatePlanExecutionInput(
|
|
162
|
+
execution_id=execution_id,
|
|
163
|
+
organization_id=input.organization_id,
|
|
164
|
+
agent_id=input.agent_id,
|
|
165
|
+
title=input.plan.title,
|
|
166
|
+
summary=input.plan.summary,
|
|
167
|
+
total_tasks=self._total_tasks,
|
|
168
|
+
plan_json=input.plan.dict(),
|
|
169
|
+
estimated_cost_usd=input.plan.cost_estimate.get("estimated_cost_usd"),
|
|
170
|
+
),
|
|
171
|
+
start_to_close_timeout=timedelta(seconds=30),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Publish initial TODO list (all tasks as pending)
|
|
175
|
+
todo_items = [
|
|
176
|
+
{
|
|
177
|
+
"task_id": task.id,
|
|
178
|
+
"title": task.title,
|
|
179
|
+
"description": task.description,
|
|
180
|
+
"status": "pending",
|
|
181
|
+
"dependencies": task.dependencies or [],
|
|
182
|
+
"agent_id": task.agent_id,
|
|
183
|
+
}
|
|
184
|
+
for task in self._tasks
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
await workflow.execute_activity(
|
|
188
|
+
publish_event_activity,
|
|
189
|
+
args=[
|
|
190
|
+
execution_id,
|
|
191
|
+
"todo_list_initialized",
|
|
192
|
+
{
|
|
193
|
+
"execution_id": execution_id,
|
|
194
|
+
"title": input.plan.title,
|
|
195
|
+
"total_tasks": self._total_tasks,
|
|
196
|
+
"items": todo_items,
|
|
197
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
198
|
+
}
|
|
199
|
+
],
|
|
200
|
+
start_to_close_timeout=timedelta(seconds=5),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Step 2: Execute plan using Claude Code agent
|
|
204
|
+
try:
|
|
205
|
+
await self._execute_plan_with_agent(input)
|
|
206
|
+
|
|
207
|
+
# All tasks completed (workflow waits internally for user input)
|
|
208
|
+
await workflow.execute_activity(
|
|
209
|
+
update_plan_state,
|
|
210
|
+
UpdatePlanStateInput(
|
|
211
|
+
plan_execution_id=self._plan_execution_id,
|
|
212
|
+
status=PlanStatus.COMPLETED,
|
|
213
|
+
completed_tasks=self._completed_tasks,
|
|
214
|
+
failed_tasks=self._failed_tasks,
|
|
215
|
+
),
|
|
216
|
+
start_to_close_timeout=timedelta(seconds=30),
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
status = PlanStatus.COMPLETED
|
|
220
|
+
|
|
221
|
+
except Exception as e:
|
|
222
|
+
import traceback
|
|
223
|
+
error_details = traceback.format_exc()
|
|
224
|
+
workflow.logger.error(
|
|
225
|
+
f"plan_execution_failed: {str(e)}\n{error_details}",
|
|
226
|
+
extra={"error": str(e), "traceback": error_details}
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Mark plan as failed
|
|
230
|
+
await workflow.execute_activity(
|
|
231
|
+
update_plan_state,
|
|
232
|
+
UpdatePlanStateInput(
|
|
233
|
+
plan_execution_id=self._plan_execution_id,
|
|
234
|
+
status=PlanStatus.FAILED,
|
|
235
|
+
completed_tasks=self._completed_tasks,
|
|
236
|
+
failed_tasks=self._failed_tasks,
|
|
237
|
+
),
|
|
238
|
+
start_to_close_timeout=timedelta(seconds=30),
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
status = PlanStatus.FAILED
|
|
242
|
+
|
|
243
|
+
# Step 3: Generate summary
|
|
244
|
+
completed_at = datetime.now(timezone.utc)
|
|
245
|
+
total_tokens = sum(r.tokens for r in self._task_results.values())
|
|
246
|
+
total_cost = sum(r.cost for r in self._task_results.values())
|
|
247
|
+
duration = (completed_at - started_at).total_seconds()
|
|
248
|
+
|
|
249
|
+
# Publish plan_completed event
|
|
250
|
+
await workflow.execute_activity(
|
|
251
|
+
publish_event_activity,
|
|
252
|
+
args=[
|
|
253
|
+
self._plan_execution_id,
|
|
254
|
+
"plan_completed",
|
|
255
|
+
{
|
|
256
|
+
"execution_id": self._plan_execution_id,
|
|
257
|
+
"status": "completed" if status == PlanStatus.COMPLETED else "failed",
|
|
258
|
+
"completed_tasks": self._completed_tasks,
|
|
259
|
+
"failed_tasks": self._failed_tasks,
|
|
260
|
+
"total_tasks": self._total_tasks,
|
|
261
|
+
"total_tokens": total_tokens,
|
|
262
|
+
"total_cost": total_cost,
|
|
263
|
+
"duration_seconds": duration,
|
|
264
|
+
"timestamp": completed_at.isoformat(),
|
|
265
|
+
}
|
|
266
|
+
],
|
|
267
|
+
start_to_close_timeout=timedelta(seconds=5),
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
return PlanExecutionSummary(
|
|
271
|
+
plan_execution_id=self._plan_execution_id,
|
|
272
|
+
status=status,
|
|
273
|
+
total_tasks=self._total_tasks,
|
|
274
|
+
completed_tasks=self._completed_tasks,
|
|
275
|
+
failed_tasks=self._failed_tasks,
|
|
276
|
+
total_tokens=total_tokens,
|
|
277
|
+
total_cost=total_cost,
|
|
278
|
+
started_at=started_at,
|
|
279
|
+
completed_at=completed_at,
|
|
280
|
+
execution_time_seconds=(completed_at - started_at).total_seconds(),
|
|
281
|
+
task_results=self._task_results,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
async def _execute_plan_with_agent(self, input: PlanOrchestratorInput):
|
|
285
|
+
"""
|
|
286
|
+
Execute the plan using a Claude Code agent.
|
|
287
|
+
|
|
288
|
+
The agent is given the full plan context and tools to manage execution.
|
|
289
|
+
It will intelligently orchestrate tasks, handle dependencies, and
|
|
290
|
+
provide status updates.
|
|
291
|
+
"""
|
|
292
|
+
workflow.logger.info("starting_agent_orchestration")
|
|
293
|
+
|
|
294
|
+
# Build system prompt for orchestrator agent
|
|
295
|
+
system_prompt = self._build_orchestrator_system_prompt(input.plan)
|
|
296
|
+
|
|
297
|
+
# Build initial user prompt
|
|
298
|
+
user_prompt = self._build_orchestrator_user_prompt(input.plan)
|
|
299
|
+
|
|
300
|
+
# Get available tools
|
|
301
|
+
tools = get_agent_tools_formatted()
|
|
302
|
+
|
|
303
|
+
# Run agent conversation loop
|
|
304
|
+
messages = [{"role": "user", "content": user_prompt}]
|
|
305
|
+
max_turns = 100 # Safety limit
|
|
306
|
+
|
|
307
|
+
for turn in range(max_turns):
|
|
308
|
+
workflow.logger.info(
|
|
309
|
+
"agent_turn",
|
|
310
|
+
extra={"turn": turn, "completed_tasks": self._completed_tasks}
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Call Claude API with tools
|
|
314
|
+
response = await self._call_claude_with_tools(
|
|
315
|
+
messages=messages,
|
|
316
|
+
system_prompt=system_prompt,
|
|
317
|
+
tools=tools,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
# Check if agent is done (no more tool calls)
|
|
321
|
+
if not response.get("tool_calls"):
|
|
322
|
+
# Agent has finished or provided final summary
|
|
323
|
+
workflow.logger.info(
|
|
324
|
+
"agent_orchestration_complete",
|
|
325
|
+
extra={"final_message": response.get("content", "")[:200]}
|
|
326
|
+
)
|
|
327
|
+
break
|
|
328
|
+
|
|
329
|
+
# Process tool calls - build in Anthropic format (content blocks)
|
|
330
|
+
content_blocks = []
|
|
331
|
+
|
|
332
|
+
# Add text content if present
|
|
333
|
+
if response.get("content"):
|
|
334
|
+
content_blocks.append({
|
|
335
|
+
"type": "text",
|
|
336
|
+
"text": response["content"]
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
# Add tool_use blocks
|
|
340
|
+
for tool_call in response.get("tool_calls", []):
|
|
341
|
+
content_blocks.append({
|
|
342
|
+
"type": "tool_use",
|
|
343
|
+
"id": tool_call["id"],
|
|
344
|
+
"name": tool_call["name"],
|
|
345
|
+
"input": tool_call["input"],
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
assistant_message = {
|
|
349
|
+
"role": "assistant",
|
|
350
|
+
"content": content_blocks, # Array of blocks (Anthropic format)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
messages.append(assistant_message)
|
|
354
|
+
|
|
355
|
+
# Execute each tool call
|
|
356
|
+
tool_results = []
|
|
357
|
+
for tool_call in response.get("tool_calls", []):
|
|
358
|
+
tool_name = tool_call.get("name")
|
|
359
|
+
tool_input = tool_call.get("input", {})
|
|
360
|
+
tool_call_id = tool_call.get("id")
|
|
361
|
+
|
|
362
|
+
workflow.logger.info(
|
|
363
|
+
"executing_tool",
|
|
364
|
+
extra={"tool": tool_name, "input": tool_input}
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
result = await self._execute_tool(tool_name, tool_input)
|
|
368
|
+
|
|
369
|
+
tool_results.append({
|
|
370
|
+
"type": "tool_result",
|
|
371
|
+
"tool_use_id": tool_call_id,
|
|
372
|
+
"content": json.dumps(result),
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
# Add tool results to conversation
|
|
376
|
+
messages.append({
|
|
377
|
+
"role": "user",
|
|
378
|
+
"content": tool_results,
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
# Check if all tasks are complete
|
|
382
|
+
if self._completed_tasks >= self._total_tasks:
|
|
383
|
+
workflow.logger.info("all_tasks_completed")
|
|
384
|
+
# Give agent one more turn to provide summary
|
|
385
|
+
continue
|
|
386
|
+
|
|
387
|
+
workflow.logger.info(
|
|
388
|
+
"agent_orchestration_finished",
|
|
389
|
+
extra={
|
|
390
|
+
"total_turns": turn + 1,
|
|
391
|
+
"completed_tasks": self._completed_tasks,
|
|
392
|
+
}
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
async def _call_claude_with_tools(
|
|
396
|
+
self,
|
|
397
|
+
messages: List[Dict[str, Any]],
|
|
398
|
+
system_prompt: str,
|
|
399
|
+
tools: List[Dict[str, Any]],
|
|
400
|
+
) -> Dict[str, Any]:
|
|
401
|
+
"""Call Claude API with tool support via activity."""
|
|
402
|
+
from worker_internal.planner.activities import call_llm_activity
|
|
403
|
+
|
|
404
|
+
response_data = await workflow.execute_activity(
|
|
405
|
+
call_llm_activity,
|
|
406
|
+
args=[
|
|
407
|
+
messages,
|
|
408
|
+
system_prompt,
|
|
409
|
+
tools,
|
|
410
|
+
self._model_id,
|
|
411
|
+
self._plan_execution_id,
|
|
412
|
+
self._organization_id,
|
|
413
|
+
None, # user_id - will be extracted from JWT
|
|
414
|
+
None, # task_id
|
|
415
|
+
"plan-orchestrator", # generation_name
|
|
416
|
+
self._jwt_token, # jwt_token for user extraction
|
|
417
|
+
],
|
|
418
|
+
start_to_close_timeout=timedelta(minutes=5),
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
return response_data
|
|
422
|
+
|
|
423
|
+
async def _execute_tool(self, tool_name: str, tool_input: Dict[str, Any]) -> Dict[str, Any]:
|
|
424
|
+
"""Execute a tool call from the agent."""
|
|
425
|
+
try:
|
|
426
|
+
if tool_name == "execute_task":
|
|
427
|
+
return await self._tool_execute_task(tool_input)
|
|
428
|
+
|
|
429
|
+
elif tool_name == "get_task_status":
|
|
430
|
+
return await self._tool_get_task_status(tool_input)
|
|
431
|
+
|
|
432
|
+
elif tool_name == "validate_task":
|
|
433
|
+
return await self._tool_validate_task(tool_input)
|
|
434
|
+
|
|
435
|
+
elif tool_name == "update_plan_status":
|
|
436
|
+
return await self._tool_update_plan_status(tool_input)
|
|
437
|
+
|
|
438
|
+
elif tool_name == "list_tasks":
|
|
439
|
+
return await self._tool_list_tasks(tool_input)
|
|
440
|
+
|
|
441
|
+
else:
|
|
442
|
+
return {"error": f"Unknown tool: {tool_name}"}
|
|
443
|
+
|
|
444
|
+
except Exception as e:
|
|
445
|
+
workflow.logger.error(
|
|
446
|
+
"tool_execution_failed",
|
|
447
|
+
extra={"tool": tool_name, "error": str(e)}
|
|
448
|
+
)
|
|
449
|
+
return {"error": str(e)}
|
|
450
|
+
|
|
451
|
+
async def _tool_execute_task(self, tool_input: Dict[str, Any]) -> Dict[str, Any]:
|
|
452
|
+
"""Tool: Execute a task (or multiple tasks in parallel if they're independent)."""
|
|
453
|
+
task_id = tool_input.get("task_id")
|
|
454
|
+
|
|
455
|
+
# Support executing multiple tasks: execute_task(task_id=1) or execute_task(task_ids=[1,2,3])
|
|
456
|
+
task_ids = tool_input.get("task_ids", [task_id] if task_id else [])
|
|
457
|
+
|
|
458
|
+
if not task_ids:
|
|
459
|
+
return {"error": "No task_id or task_ids provided"}
|
|
460
|
+
|
|
461
|
+
# Execute tasks in parallel
|
|
462
|
+
tasks_to_execute = []
|
|
463
|
+
for tid in task_ids:
|
|
464
|
+
task = next((t for t in self._tasks if t.id == tid), None)
|
|
465
|
+
if not task:
|
|
466
|
+
return {"error": f"Task {tid} not found"}
|
|
467
|
+
|
|
468
|
+
# Check if task already has a result
|
|
469
|
+
if tid in self._task_results:
|
|
470
|
+
existing_result = self._task_results[tid]
|
|
471
|
+
# If task is waiting for input, we'll continue it (not start new)
|
|
472
|
+
if existing_result.status == TaskStatus.WAITING_FOR_INPUT:
|
|
473
|
+
workflow.logger.info(
|
|
474
|
+
"task_already_waiting_will_continue",
|
|
475
|
+
extra={"task_id": tid, "execution_id": existing_result.execution_id}
|
|
476
|
+
)
|
|
477
|
+
tasks_to_execute.append(task)
|
|
478
|
+
# If task is complete or failed, skip it
|
|
479
|
+
elif existing_result.status in (TaskStatus.SUCCESS, TaskStatus.FAILED):
|
|
480
|
+
workflow.logger.info(
|
|
481
|
+
"task_already_complete_skipping",
|
|
482
|
+
extra={"task_id": tid, "status": existing_result.status.value}
|
|
483
|
+
)
|
|
484
|
+
continue
|
|
485
|
+
else:
|
|
486
|
+
tasks_to_execute.append(task)
|
|
487
|
+
else:
|
|
488
|
+
tasks_to_execute.append(task)
|
|
489
|
+
|
|
490
|
+
# Execute all tasks in parallel
|
|
491
|
+
import asyncio
|
|
492
|
+
workflow.logger.info(f"executing_{len(tasks_to_execute)}_tasks_in_parallel", task_ids=task_ids)
|
|
493
|
+
|
|
494
|
+
# Publish tasks_parallel event if multiple tasks
|
|
495
|
+
if len(tasks_to_execute) > 1:
|
|
496
|
+
await workflow.execute_activity(
|
|
497
|
+
publish_event_activity,
|
|
498
|
+
args=[
|
|
499
|
+
self._plan_execution_id,
|
|
500
|
+
"tasks_parallel",
|
|
501
|
+
{
|
|
502
|
+
"execution_id": self._plan_execution_id,
|
|
503
|
+
"task_ids": [t.id for t in tasks_to_execute],
|
|
504
|
+
"message": f"Executing {len(tasks_to_execute)} tasks in parallel",
|
|
505
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
506
|
+
}
|
|
507
|
+
],
|
|
508
|
+
start_to_close_timeout=timedelta(seconds=5),
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
async def execute_single_task(task):
|
|
512
|
+
# Check if task already has a result (from previous workflow run)
|
|
513
|
+
existing_result = self._task_results.get(task.id)
|
|
514
|
+
|
|
515
|
+
# If task was waiting for input and user already sent message, continue it
|
|
516
|
+
if existing_result and existing_result.status == TaskStatus.WAITING_FOR_INPUT:
|
|
517
|
+
workflow.logger.info(
|
|
518
|
+
"continuing_task_from_previous_execution",
|
|
519
|
+
extra={
|
|
520
|
+
"task_id": task.id,
|
|
521
|
+
"execution_id": existing_result.execution_id,
|
|
522
|
+
}
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
# Continue streaming from existing execution (message already sent via API)
|
|
526
|
+
result = await workflow.execute_activity(
|
|
527
|
+
continue_task_activity,
|
|
528
|
+
args=[
|
|
529
|
+
task,
|
|
530
|
+
existing_result.execution_id,
|
|
531
|
+
"",
|
|
532
|
+
self._plan_execution_id,
|
|
533
|
+
self._jwt_token,
|
|
534
|
+
self._model_id,
|
|
535
|
+
self._organization_id,
|
|
536
|
+
],
|
|
537
|
+
start_to_close_timeout=timedelta(minutes=15),
|
|
538
|
+
)
|
|
539
|
+
else:
|
|
540
|
+
from worker_internal.planner.retry_logic import (
|
|
541
|
+
should_retry_task,
|
|
542
|
+
build_retry_context,
|
|
543
|
+
create_retry_attempt_record,
|
|
544
|
+
)
|
|
545
|
+
from worker_internal.planner.models import TaskRetryAttempt
|
|
546
|
+
|
|
547
|
+
await workflow.execute_activity(
|
|
548
|
+
update_plan_state,
|
|
549
|
+
UpdatePlanStateInput(
|
|
550
|
+
plan_execution_id=self._plan_execution_id,
|
|
551
|
+
current_task_id=task.id,
|
|
552
|
+
current_task_status=TaskStatus.RUNNING,
|
|
553
|
+
),
|
|
554
|
+
start_to_close_timeout=timedelta(seconds=30),
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
dependency_outputs = {}
|
|
558
|
+
if task.dependencies:
|
|
559
|
+
for dep_task_id in task.dependencies:
|
|
560
|
+
if dep_task_id in self._task_results:
|
|
561
|
+
dependency_outputs[dep_task_id] = self._task_results[dep_task_id].output
|
|
562
|
+
|
|
563
|
+
retry_history: List[TaskRetryAttempt] = []
|
|
564
|
+
result = None
|
|
565
|
+
current_attempt = 1
|
|
566
|
+
|
|
567
|
+
while current_attempt <= 5:
|
|
568
|
+
retry_context = build_retry_context(retry_history, current_attempt) if retry_history else None
|
|
569
|
+
|
|
570
|
+
if retry_context:
|
|
571
|
+
workflow.logger.info(
|
|
572
|
+
f"retrying_task_attempt_{current_attempt}",
|
|
573
|
+
extra={
|
|
574
|
+
"task_id": task.id,
|
|
575
|
+
"attempt": current_attempt,
|
|
576
|
+
"previous_failures": len(retry_history),
|
|
577
|
+
}
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
result = await workflow.execute_activity(
|
|
581
|
+
execute_task_activity,
|
|
582
|
+
args=[
|
|
583
|
+
task,
|
|
584
|
+
self._plan_execution_id,
|
|
585
|
+
self._organization_id,
|
|
586
|
+
dependency_outputs,
|
|
587
|
+
self._jwt_token,
|
|
588
|
+
self._model_id,
|
|
589
|
+
retry_context,
|
|
590
|
+
self._worker_queue_id, # Pass workflow-level worker_queue_id as fallback
|
|
591
|
+
],
|
|
592
|
+
start_to_close_timeout=timedelta(minutes=15),
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
if result.status == TaskStatus.SUCCESS:
|
|
596
|
+
result.retry_count = current_attempt - 1
|
|
597
|
+
result.retry_history = retry_history
|
|
598
|
+
break
|
|
599
|
+
|
|
600
|
+
if result.status == TaskStatus.WAITING_FOR_INPUT:
|
|
601
|
+
break
|
|
602
|
+
|
|
603
|
+
if should_retry_task(result, current_attempt):
|
|
604
|
+
retry_attempt = create_retry_attempt_record(result, current_attempt)
|
|
605
|
+
retry_history.append(retry_attempt)
|
|
606
|
+
|
|
607
|
+
from worker_internal.planner.event_models import TaskRetryEvent
|
|
608
|
+
from worker_internal.planner.activities import publish_event_activity
|
|
609
|
+
|
|
610
|
+
await workflow.execute_activity(
|
|
611
|
+
publish_event_activity,
|
|
612
|
+
args=[
|
|
613
|
+
self._plan_execution_id,
|
|
614
|
+
"task_retry",
|
|
615
|
+
{
|
|
616
|
+
"execution_id": self._plan_execution_id,
|
|
617
|
+
"task_id": task.id,
|
|
618
|
+
"title": task.title,
|
|
619
|
+
"attempt_number": current_attempt + 1,
|
|
620
|
+
"max_attempts": 5,
|
|
621
|
+
"previous_error": (result.error or "Unknown error")[:200],
|
|
622
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
623
|
+
}
|
|
624
|
+
],
|
|
625
|
+
start_to_close_timeout=timedelta(seconds=5),
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
current_attempt += 1
|
|
629
|
+
else:
|
|
630
|
+
result.retry_count = current_attempt - 1
|
|
631
|
+
result.retry_history = retry_history
|
|
632
|
+
workflow.logger.error(
|
|
633
|
+
f"task_failed_after_{current_attempt}_attempts",
|
|
634
|
+
extra={
|
|
635
|
+
"task_id": task.id,
|
|
636
|
+
"final_error": result.error[:200] if result.error else "unknown",
|
|
637
|
+
}
|
|
638
|
+
)
|
|
639
|
+
break
|
|
640
|
+
|
|
641
|
+
# Check if task needs user input
|
|
642
|
+
if result.status == TaskStatus.WAITING_FOR_INPUT:
|
|
643
|
+
workflow.logger.info(
|
|
644
|
+
f"task_waiting_for_user_input: task_id={task.id}, execution_id={result.execution_id}"
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
# Add to waiting tasks list for tracking
|
|
648
|
+
waiting_task_info = {
|
|
649
|
+
"task_id": task.id,
|
|
650
|
+
"execution_id": result.execution_id,
|
|
651
|
+
"question": result.user_question or "Please provide input",
|
|
652
|
+
"waiting_since": datetime.now(timezone.utc).isoformat(),
|
|
653
|
+
}
|
|
654
|
+
self._waiting_tasks.append(waiting_task_info)
|
|
655
|
+
|
|
656
|
+
# Update plan status to pending_user_input
|
|
657
|
+
await workflow.execute_activity(
|
|
658
|
+
update_plan_state,
|
|
659
|
+
UpdatePlanStateInput(
|
|
660
|
+
plan_execution_id=self._plan_execution_id,
|
|
661
|
+
status=PlanStatus.PENDING_USER_INPUT,
|
|
662
|
+
current_task_id=task.id,
|
|
663
|
+
current_task_status=TaskStatus.WAITING_FOR_INPUT,
|
|
664
|
+
waiting_tasks=self._waiting_tasks,
|
|
665
|
+
),
|
|
666
|
+
start_to_close_timeout=timedelta(seconds=30),
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
workflow.logger.info(f"⏸️ PAUSING WORKFLOW: task_id={task.id} - waiting for signal")
|
|
670
|
+
|
|
671
|
+
# PAUSE: Wait indefinitely for user to send signal (no timeout)
|
|
672
|
+
await workflow.wait_condition(
|
|
673
|
+
lambda: task.id in self._pending_user_messages
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
# Resume: User sent message via signal (message was already sent by /continue endpoint)
|
|
677
|
+
self._pending_user_messages.pop(task.id, None) # Clear the signal data
|
|
678
|
+
|
|
679
|
+
workflow.logger.info(
|
|
680
|
+
f"▶️ WORKFLOW RESUMED: task_id={task.id} - message already sent by API, streaming result"
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
# Remove from waiting tasks list
|
|
684
|
+
self._waiting_tasks = [wt for wt in self._waiting_tasks if wt["task_id"] != task.id]
|
|
685
|
+
|
|
686
|
+
# Update status back to running
|
|
687
|
+
new_status = PlanStatus.PENDING_USER_INPUT if self._waiting_tasks else PlanStatus.RUNNING
|
|
688
|
+
await workflow.execute_activity(
|
|
689
|
+
update_plan_state,
|
|
690
|
+
UpdatePlanStateInput(
|
|
691
|
+
plan_execution_id=self._plan_execution_id,
|
|
692
|
+
status=new_status,
|
|
693
|
+
current_task_id=task.id,
|
|
694
|
+
current_task_status=TaskStatus.RUNNING,
|
|
695
|
+
waiting_tasks=self._waiting_tasks,
|
|
696
|
+
),
|
|
697
|
+
start_to_close_timeout=timedelta(seconds=30),
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
# Continue the task (message already sent by /continue endpoint, so pass empty string)
|
|
701
|
+
result = await workflow.execute_activity(
|
|
702
|
+
continue_task_activity,
|
|
703
|
+
args=[
|
|
704
|
+
task,
|
|
705
|
+
result.execution_id,
|
|
706
|
+
"",
|
|
707
|
+
self._plan_execution_id,
|
|
708
|
+
self._jwt_token,
|
|
709
|
+
self._model_id,
|
|
710
|
+
self._organization_id,
|
|
711
|
+
],
|
|
712
|
+
start_to_close_timeout=timedelta(minutes=15),
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
workflow.logger.info(
|
|
716
|
+
f"✅ TASK CONTINUED: task_id={task.id}, status={result.status.value}, output_length={len(result.output)}"
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
# Store
|
|
720
|
+
self._task_results[task.id] = result
|
|
721
|
+
if result.status == TaskStatus.SUCCESS:
|
|
722
|
+
self._completed_tasks += 1
|
|
723
|
+
workflow.logger.info(f"✅ TASK COMPLETED: task_id={task.id}, completed_count={self._completed_tasks}/{self._total_tasks}")
|
|
724
|
+
elif result.status == TaskStatus.FAILED:
|
|
725
|
+
self._failed_tasks += 1
|
|
726
|
+
workflow.logger.info(f"❌ TASK FAILED: task_id={task.id}, error={result.error[:100] if result.error else 'none'}")
|
|
727
|
+
# WAITING_FOR_INPUT tasks are not counted as failed or completed yet
|
|
728
|
+
|
|
729
|
+
# Update: task completed
|
|
730
|
+
await workflow.execute_activity(
|
|
731
|
+
update_plan_state,
|
|
732
|
+
UpdatePlanStateInput(
|
|
733
|
+
plan_execution_id=self._plan_execution_id,
|
|
734
|
+
current_task_id=task.id,
|
|
735
|
+
current_task_status=result.status,
|
|
736
|
+
completed_tasks=self._completed_tasks,
|
|
737
|
+
failed_tasks=self._failed_tasks,
|
|
738
|
+
),
|
|
739
|
+
start_to_close_timeout=timedelta(seconds=30),
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
return result
|
|
743
|
+
|
|
744
|
+
# Execute all tasks concurrently
|
|
745
|
+
results = await asyncio.gather(*[execute_single_task(t) for t in tasks_to_execute])
|
|
746
|
+
|
|
747
|
+
# Log summary before returning to orchestrator
|
|
748
|
+
workflow.logger.info(
|
|
749
|
+
f"🎯 EXECUTE_TASK TOOL COMPLETE: {len(results)} tasks, "
|
|
750
|
+
f"completed={len([r for r in results if r.status == TaskStatus.SUCCESS])}, "
|
|
751
|
+
f"failed={len([r for r in results if r.status == TaskStatus.FAILED])}, "
|
|
752
|
+
f"waiting={len([r for r in results if r.status == TaskStatus.WAITING_FOR_INPUT])}"
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
# Return summary
|
|
756
|
+
return {
|
|
757
|
+
"success": True,
|
|
758
|
+
"task_ids": task_ids,
|
|
759
|
+
"completed": len([r for r in results if r.status == TaskStatus.SUCCESS]),
|
|
760
|
+
"failed": len([r for r in results if r.status == TaskStatus.FAILED]),
|
|
761
|
+
"results": [
|
|
762
|
+
{
|
|
763
|
+
"task_id": r.task_id,
|
|
764
|
+
"status": r.status.value,
|
|
765
|
+
"output": r.output, # Full output, no truncation
|
|
766
|
+
"execution_id": r.execution_id,
|
|
767
|
+
}
|
|
768
|
+
for r in results
|
|
769
|
+
]
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
async def _tool_get_task_status(self, tool_input: Dict[str, Any]) -> Dict[str, Any]:
|
|
773
|
+
"""Tool: Get task status."""
|
|
774
|
+
task_id = tool_input.get("task_id")
|
|
775
|
+
|
|
776
|
+
result = await workflow.execute_activity(
|
|
777
|
+
get_task_status_activity,
|
|
778
|
+
args=[task_id, self._task_results],
|
|
779
|
+
start_to_close_timeout=timedelta(seconds=10),
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
return result
|
|
783
|
+
|
|
784
|
+
async def _tool_validate_task(self, tool_input: Dict[str, Any]) -> Dict[str, Any]:
|
|
785
|
+
"""Tool: Validate task completion."""
|
|
786
|
+
task_id = tool_input.get("task_id")
|
|
787
|
+
|
|
788
|
+
# Find task and result
|
|
789
|
+
task = next((t for t in self._tasks if t.id == task_id), None)
|
|
790
|
+
if not task:
|
|
791
|
+
return {"error": f"Task {task_id} not found"}
|
|
792
|
+
|
|
793
|
+
if task_id not in self._task_results:
|
|
794
|
+
return {"error": f"Task {task_id} not executed yet"}
|
|
795
|
+
|
|
796
|
+
result = self._task_results[task_id]
|
|
797
|
+
|
|
798
|
+
# Validate
|
|
799
|
+
validation = await workflow.execute_activity(
|
|
800
|
+
validate_task_completion,
|
|
801
|
+
args=[
|
|
802
|
+
task,
|
|
803
|
+
result,
|
|
804
|
+
self._plan_execution_id,
|
|
805
|
+
self._organization_id,
|
|
806
|
+
None, # user_id - will be extracted from JWT
|
|
807
|
+
self._jwt_token, # jwt_token for user extraction
|
|
808
|
+
],
|
|
809
|
+
start_to_close_timeout=timedelta(minutes=2),
|
|
810
|
+
)
|
|
811
|
+
|
|
812
|
+
return {
|
|
813
|
+
"task_id": task_id,
|
|
814
|
+
"validation_status": validation.status.value,
|
|
815
|
+
"reason": validation.reason,
|
|
816
|
+
"confidence": validation.confidence,
|
|
817
|
+
"suggestions": validation.suggestions,
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
async def _tool_update_plan_status(self, tool_input: Dict[str, Any]) -> Dict[str, Any]:
|
|
821
|
+
"""Tool: Update plan status."""
|
|
822
|
+
status_message = tool_input.get("status_message", "")
|
|
823
|
+
completed_tasks = tool_input.get("completed_tasks")
|
|
824
|
+
|
|
825
|
+
workflow.logger.info(
|
|
826
|
+
"plan_status_update",
|
|
827
|
+
extra={"message": status_message, "completed": completed_tasks}
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
# Update state
|
|
831
|
+
await workflow.execute_activity(
|
|
832
|
+
update_plan_state,
|
|
833
|
+
UpdatePlanStateInput(
|
|
834
|
+
plan_execution_id=self._plan_execution_id,
|
|
835
|
+
completed_tasks=completed_tasks if completed_tasks is not None else self._completed_tasks,
|
|
836
|
+
failed_tasks=self._failed_tasks,
|
|
837
|
+
),
|
|
838
|
+
start_to_close_timeout=timedelta(seconds=30),
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
return {"success": True, "message": "Status updated"}
|
|
842
|
+
|
|
843
|
+
async def _continue_task_execution(
|
|
844
|
+
self,
|
|
845
|
+
task: PlanTask,
|
|
846
|
+
execution_id: str,
|
|
847
|
+
user_message: str,
|
|
848
|
+
) -> TaskExecutionResult:
|
|
849
|
+
"""
|
|
850
|
+
Continue a task execution after receiving user input.
|
|
851
|
+
|
|
852
|
+
This sends the user's message to the existing agent execution,
|
|
853
|
+
then continues streaming events until the task completes or
|
|
854
|
+
needs more input.
|
|
855
|
+
"""
|
|
856
|
+
workflow.logger.info(
|
|
857
|
+
"continuing_task_after_user_input",
|
|
858
|
+
extra={"task_id": task.id, "execution_id": execution_id}
|
|
859
|
+
)
|
|
860
|
+
|
|
861
|
+
# Create an activity to continue the task
|
|
862
|
+
from worker_internal.planner.activities import continue_task_activity
|
|
863
|
+
|
|
864
|
+
result = await workflow.execute_activity(
|
|
865
|
+
continue_task_activity,
|
|
866
|
+
args=[task, execution_id, user_message, self._jwt_token, self._model_id],
|
|
867
|
+
start_to_close_timeout=timedelta(minutes=15),
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
return result
|
|
871
|
+
|
|
872
|
+
async def _tool_list_tasks(self, tool_input: Dict[str, Any]) -> Dict[str, Any]:
|
|
873
|
+
"""Tool: List all tasks."""
|
|
874
|
+
tasks_info = []
|
|
875
|
+
for task in self._tasks:
|
|
876
|
+
task_status = "pending"
|
|
877
|
+
if task.id in self._task_results:
|
|
878
|
+
task_status = self._task_results[task.id].status.value
|
|
879
|
+
|
|
880
|
+
tasks_info.append({
|
|
881
|
+
"id": task.id,
|
|
882
|
+
"title": task.title,
|
|
883
|
+
"description": task.description[:100],
|
|
884
|
+
"dependencies": task.dependencies,
|
|
885
|
+
"status": task_status,
|
|
886
|
+
"priority": task.priority,
|
|
887
|
+
})
|
|
888
|
+
|
|
889
|
+
return {
|
|
890
|
+
"total_tasks": self._total_tasks,
|
|
891
|
+
"completed_tasks": self._completed_tasks,
|
|
892
|
+
"failed_tasks": self._failed_tasks,
|
|
893
|
+
"tasks": tasks_info,
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
def _build_orchestrator_system_prompt(self, plan: Any) -> str:
|
|
897
|
+
"""Build system prompt for orchestrator agent."""
|
|
898
|
+
agent_info = plan.team_breakdown[0] if plan.team_breakdown else None
|
|
899
|
+
|
|
900
|
+
return f"""You are a Plan Orchestrator Agent for the Kubiya platform. Your job is to intelligently manage the execution of multi-task plans.
|
|
901
|
+
|
|
902
|
+
## Your Role
|
|
903
|
+
{agent_info.agent_name if agent_info else 'Plan Orchestrator'}
|
|
904
|
+
|
|
905
|
+
## Responsibilities
|
|
906
|
+
{chr(10).join(f"- {r}" for r in (agent_info.responsibilities if agent_info else ['Execute tasks in correct order', 'Monitor progress', 'Validate completion']))}
|
|
907
|
+
|
|
908
|
+
## Available Tools
|
|
909
|
+
You have access to these tools to manage plan execution:
|
|
910
|
+
|
|
911
|
+
1. **execute_task(task_id)** - Execute a specific task by spawning an agent execution
|
|
912
|
+
2. **get_task_status(task_id)** - Check the status of a task execution
|
|
913
|
+
3. **validate_task(task_id)** - Validate that a task completed successfully using LLM analysis
|
|
914
|
+
4. **update_plan_status(status_message, completed_tasks)** - Update the overall plan status for UI
|
|
915
|
+
5. **list_tasks()** - Get a list of all tasks with their dependencies and status
|
|
916
|
+
|
|
917
|
+
## Your Process
|
|
918
|
+
1. First, call list_tasks() to understand the plan structure and identify dependencies
|
|
919
|
+
2. **IMPORTANT: Execute independent tasks in PARALLEL for speed!**
|
|
920
|
+
- Group tasks by dependency level
|
|
921
|
+
- Tasks with no dependencies can run together: execute_task(task_ids=[1, 2, 3])
|
|
922
|
+
- Tasks that depend on others run after their dependencies complete
|
|
923
|
+
3. For each task or group:
|
|
924
|
+
- Call execute_task(task_ids=[...]) to run independent tasks in parallel
|
|
925
|
+
- OR execute_task(task_id=N) for single tasks
|
|
926
|
+
- Wait for completion
|
|
927
|
+
- Call validate_task(task_id) to verify success if needed
|
|
928
|
+
- Provide status updates using update_plan_status()
|
|
929
|
+
4. Handle errors gracefully - if a task fails, decide whether to retry or continue with other tasks
|
|
930
|
+
5. Provide clear, concise updates about progress
|
|
931
|
+
6. When all tasks are complete, provide a final summary
|
|
932
|
+
|
|
933
|
+
## Important Guidelines
|
|
934
|
+
- ALWAYS respect task dependencies - don't execute a task until its dependencies are complete
|
|
935
|
+
- Use validate_task() to ensure tasks actually completed successfully
|
|
936
|
+
- Provide regular status updates so users can track progress
|
|
937
|
+
- Be intelligent about error handling - don't fail the entire plan for one task failure
|
|
938
|
+
- Think step by step and explain your reasoning
|
|
939
|
+
|
|
940
|
+
Begin by analyzing the plan and executing tasks systematically.
|
|
941
|
+
"""
|
|
942
|
+
|
|
943
|
+
def _build_orchestrator_user_prompt(self, plan: Any) -> str:
|
|
944
|
+
"""Build initial user prompt for orchestrator agent."""
|
|
945
|
+
tasks_summary = []
|
|
946
|
+
if plan.team_breakdown and plan.team_breakdown[0].tasks:
|
|
947
|
+
for task in plan.team_breakdown[0].tasks:
|
|
948
|
+
tasks_summary.append(
|
|
949
|
+
f"- Task {task.id}: {task.title} (depends on: {task.dependencies or 'none'})"
|
|
950
|
+
)
|
|
951
|
+
|
|
952
|
+
return f"""# Plan Execution Request
|
|
953
|
+
|
|
954
|
+
## Plan: {plan.title}
|
|
955
|
+
|
|
956
|
+
{plan.summary}
|
|
957
|
+
|
|
958
|
+
## Tasks to Execute
|
|
959
|
+
{chr(10).join(tasks_summary)}
|
|
960
|
+
|
|
961
|
+
## Success Criteria
|
|
962
|
+
{chr(10).join(f"- {c}" for c in plan.success_criteria)}
|
|
963
|
+
|
|
964
|
+
## Risks to Consider
|
|
965
|
+
{chr(10).join(f"- {r}" for r in plan.risks)}
|
|
966
|
+
|
|
967
|
+
Please execute this plan systematically. Start by calling list_tasks() to see the full task structure, then proceed with execution while respecting dependencies.
|
|
968
|
+
|
|
969
|
+
Provide status updates as you progress, and validate each task after completion.
|
|
970
|
+
"""
|