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,503 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Task Planning Models - Pydantic schemas for workflow steps
|
|
3
|
+
|
|
4
|
+
This module contains all Pydantic models used in the task planning workflow:
|
|
5
|
+
- Step output schemas (TaskAnalysisOutput, ResourceDiscoveryOutput, etc.)
|
|
6
|
+
- Validation logic to prevent hallucinated IDs
|
|
7
|
+
- Fast selection schema for --local mode
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Optional, Dict, Any, List
|
|
11
|
+
from pydantic import BaseModel, Field, field_validator
|
|
12
|
+
import uuid
|
|
13
|
+
import structlog
|
|
14
|
+
|
|
15
|
+
logger = structlog.get_logger()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ============================================================================
|
|
19
|
+
# Step 1: Task Analysis Output
|
|
20
|
+
# ============================================================================
|
|
21
|
+
|
|
22
|
+
class TaskAnalysisOutput(BaseModel):
|
|
23
|
+
"""Output from Step 1: Task Analysis"""
|
|
24
|
+
|
|
25
|
+
task_summary: str = Field(description="Clear 1-2 sentence summary of what needs to be done")
|
|
26
|
+
required_capabilities: List[str] = Field(
|
|
27
|
+
description="List of required capabilities (e.g., 'aws_s3', 'kubectl', 'python')"
|
|
28
|
+
)
|
|
29
|
+
task_type: str = Field(
|
|
30
|
+
description="Type of task: deployment, analysis, automation, migration, monitoring, etc."
|
|
31
|
+
)
|
|
32
|
+
complexity_estimate: str = Field(
|
|
33
|
+
description="Initial complexity assessment: simple, moderate, complex"
|
|
34
|
+
)
|
|
35
|
+
story_points_estimate: int = Field(
|
|
36
|
+
description="Story points estimate (1-21 Fibonacci scale)",
|
|
37
|
+
ge=1,
|
|
38
|
+
le=21
|
|
39
|
+
)
|
|
40
|
+
needs_multi_agent: bool = Field(
|
|
41
|
+
description="Whether this task requires multiple agents (team) or single agent"
|
|
42
|
+
)
|
|
43
|
+
reasoning: str = Field(
|
|
44
|
+
description="Explanation of analysis and why these capabilities are needed"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ============================================================================
|
|
49
|
+
# Step 2: Resource Discovery Output
|
|
50
|
+
# ============================================================================
|
|
51
|
+
|
|
52
|
+
class ResourceDiscoveryOutput(BaseModel):
|
|
53
|
+
"""Output from Step 2: Resource Discovery
|
|
54
|
+
|
|
55
|
+
CRITICAL: recommended_entity_id MUST come from discovered_agents or discovered_teams.
|
|
56
|
+
This validator ensures no hallucinated IDs.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
discovered_agents: List[Dict[str, Any]] = Field(
|
|
60
|
+
description="REQUIRED: List of agents found using tools. Must call list_agents() or search_agents_by_capability().",
|
|
61
|
+
min_length=0
|
|
62
|
+
)
|
|
63
|
+
discovered_teams: List[Dict[str, Any]] = Field(
|
|
64
|
+
description="REQUIRED: List of teams found using tools. Must call list_teams() or search_teams_by_capability().",
|
|
65
|
+
min_length=0
|
|
66
|
+
)
|
|
67
|
+
recommended_entity_type: Optional[str] = Field(
|
|
68
|
+
default=None,
|
|
69
|
+
description="Either 'agent' or 'team' based on task needs (None if no resources available)"
|
|
70
|
+
)
|
|
71
|
+
recommended_entity_id: Optional[str] = Field(
|
|
72
|
+
default=None,
|
|
73
|
+
description="ID of the recommended agent or team - MUST exist in discovered_agents or discovered_teams (None if no resources available)"
|
|
74
|
+
)
|
|
75
|
+
recommended_entity_name: Optional[str] = Field(
|
|
76
|
+
default=None,
|
|
77
|
+
description="Name of the recommended agent or team - MUST match the name from tool results (None if no resources available)"
|
|
78
|
+
)
|
|
79
|
+
reasoning: str = Field(
|
|
80
|
+
description="Why this agent/team was selected as best match from the discovered options"
|
|
81
|
+
)
|
|
82
|
+
discovered_environments: List[Dict[str, Any]] = Field(
|
|
83
|
+
default_factory=list,
|
|
84
|
+
description="List of environments found using list_environments() tool. Required if recommending environment."
|
|
85
|
+
)
|
|
86
|
+
discovered_worker_queues: List[Dict[str, Any]] = Field(
|
|
87
|
+
default_factory=list,
|
|
88
|
+
description="List of worker queues found using list_worker_queues() tool. Required if recommending queue."
|
|
89
|
+
)
|
|
90
|
+
recommended_environment_id: Optional[str] = Field(
|
|
91
|
+
default=None,
|
|
92
|
+
description="UUID of the recommended environment - MUST exist in discovered_environments (not a name!)"
|
|
93
|
+
)
|
|
94
|
+
recommended_environment_name: Optional[str] = Field(
|
|
95
|
+
default=None,
|
|
96
|
+
description="Name of the recommended environment - MUST match the name from discovered_environments"
|
|
97
|
+
)
|
|
98
|
+
recommended_worker_queue_id: Optional[str] = Field(
|
|
99
|
+
default=None,
|
|
100
|
+
description="UUID of the recommended worker queue - MUST exist in discovered_worker_queues (not a name!)"
|
|
101
|
+
)
|
|
102
|
+
recommended_worker_queue_name: Optional[str] = Field(
|
|
103
|
+
default=None,
|
|
104
|
+
description="Name of the recommended worker queue - MUST match the name from discovered_worker_queues"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
@field_validator('discovered_agents', 'discovered_teams')
|
|
108
|
+
@classmethod
|
|
109
|
+
def validate_discovered_not_empty(cls, v, info):
|
|
110
|
+
"""At least one of discovered_agents or discovered_teams must have results"""
|
|
111
|
+
return v
|
|
112
|
+
|
|
113
|
+
@field_validator('recommended_entity_id')
|
|
114
|
+
@classmethod
|
|
115
|
+
def validate_entity_id_exists(cls, v, info):
|
|
116
|
+
"""CRITICAL: Validate that recommended ID is a UUID and exists in discovered lists"""
|
|
117
|
+
if v is None:
|
|
118
|
+
return v
|
|
119
|
+
|
|
120
|
+
# Validate UUID format
|
|
121
|
+
try:
|
|
122
|
+
uuid.UUID(v)
|
|
123
|
+
except (ValueError, AttributeError, TypeError):
|
|
124
|
+
raise ValueError(
|
|
125
|
+
f"recommended_entity_id '{v}' is NOT a valid UUID! "
|
|
126
|
+
f"You MUST use the 'id' field (UUID) from tool results, NOT the 'name' field!"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
discovered_agents = info.data.get('discovered_agents', [])
|
|
130
|
+
discovered_teams = info.data.get('discovered_teams', [])
|
|
131
|
+
entity_type = info.data.get('recommended_entity_type', '')
|
|
132
|
+
|
|
133
|
+
if not discovered_agents and not discovered_teams:
|
|
134
|
+
raise ValueError(
|
|
135
|
+
"Cannot recommend an entity when no agents or teams were discovered."
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if entity_type == 'agent':
|
|
139
|
+
agent_ids = [str(a.get('id', '')) for a in discovered_agents if a.get('id')]
|
|
140
|
+
if v not in agent_ids:
|
|
141
|
+
raise ValueError(
|
|
142
|
+
f"Recommended agent_id '{v}' does not exist in discovered_agents. "
|
|
143
|
+
f"Available: {agent_ids}"
|
|
144
|
+
)
|
|
145
|
+
elif entity_type == 'team':
|
|
146
|
+
team_ids = [str(t.get('id', '')) for t in discovered_teams if t.get('id')]
|
|
147
|
+
if v not in team_ids:
|
|
148
|
+
raise ValueError(
|
|
149
|
+
f"Recommended team_id '{v}' does not exist in discovered_teams. "
|
|
150
|
+
f"Available: {team_ids}"
|
|
151
|
+
)
|
|
152
|
+
else:
|
|
153
|
+
raise ValueError(f"recommended_entity_type must be 'agent' or 'team', got '{entity_type}'")
|
|
154
|
+
|
|
155
|
+
return v
|
|
156
|
+
|
|
157
|
+
@field_validator('recommended_entity_name')
|
|
158
|
+
@classmethod
|
|
159
|
+
def validate_entity_name_matches(cls, v, info):
|
|
160
|
+
"""Validate that recommended name matches the entity from discovered lists"""
|
|
161
|
+
if v is None:
|
|
162
|
+
return v
|
|
163
|
+
|
|
164
|
+
discovered_agents = info.data.get('discovered_agents', [])
|
|
165
|
+
discovered_teams = info.data.get('discovered_teams', [])
|
|
166
|
+
entity_type = info.data.get('recommended_entity_type', '')
|
|
167
|
+
entity_id = info.data.get('recommended_entity_id', '')
|
|
168
|
+
|
|
169
|
+
if entity_type == 'agent':
|
|
170
|
+
for agent in discovered_agents:
|
|
171
|
+
if str(agent.get('id')) == entity_id:
|
|
172
|
+
actual_name = agent.get('name', '')
|
|
173
|
+
if v != actual_name:
|
|
174
|
+
raise ValueError(f"Name '{v}' doesn't match agent name '{actual_name}'")
|
|
175
|
+
break
|
|
176
|
+
elif entity_type == 'team':
|
|
177
|
+
for team in discovered_teams:
|
|
178
|
+
if str(team.get('id')) == entity_id:
|
|
179
|
+
actual_name = team.get('name', '')
|
|
180
|
+
if v != actual_name:
|
|
181
|
+
raise ValueError(f"Name '{v}' doesn't match team name '{actual_name}'")
|
|
182
|
+
break
|
|
183
|
+
|
|
184
|
+
return v
|
|
185
|
+
|
|
186
|
+
@field_validator('recommended_environment_id')
|
|
187
|
+
@classmethod
|
|
188
|
+
def validate_environment_id_exists(cls, v, info):
|
|
189
|
+
"""Validate environment ID exists in discovered list"""
|
|
190
|
+
if v is None:
|
|
191
|
+
return v
|
|
192
|
+
|
|
193
|
+
discovered_environments = info.data.get('discovered_environments', [])
|
|
194
|
+
if not discovered_environments:
|
|
195
|
+
raise ValueError("Cannot recommend environment when none were discovered.")
|
|
196
|
+
|
|
197
|
+
env_ids = [str(e.get('id', '')) for e in discovered_environments if e.get('id')]
|
|
198
|
+
if v not in env_ids:
|
|
199
|
+
raise ValueError(f"Environment ID '{v}' not in discovered list: {env_ids}")
|
|
200
|
+
|
|
201
|
+
return v
|
|
202
|
+
|
|
203
|
+
@field_validator('recommended_environment_name')
|
|
204
|
+
@classmethod
|
|
205
|
+
def validate_environment_name_matches(cls, v, info):
|
|
206
|
+
"""Validate environment name matches the ID"""
|
|
207
|
+
if v is None:
|
|
208
|
+
return v
|
|
209
|
+
|
|
210
|
+
discovered_environments = info.data.get('discovered_environments', [])
|
|
211
|
+
environment_id = info.data.get('recommended_environment_id', '')
|
|
212
|
+
|
|
213
|
+
for env in discovered_environments:
|
|
214
|
+
if str(env.get('id')) == environment_id:
|
|
215
|
+
actual_name = env.get('name', '')
|
|
216
|
+
if v != actual_name:
|
|
217
|
+
raise ValueError(f"Environment name '{v}' doesn't match '{actual_name}'")
|
|
218
|
+
break
|
|
219
|
+
|
|
220
|
+
return v
|
|
221
|
+
|
|
222
|
+
@field_validator('recommended_worker_queue_id')
|
|
223
|
+
@classmethod
|
|
224
|
+
def validate_worker_queue_id_exists(cls, v, info):
|
|
225
|
+
"""Validate worker queue ID exists in discovered list"""
|
|
226
|
+
if v is None:
|
|
227
|
+
return v
|
|
228
|
+
|
|
229
|
+
discovered_worker_queues = info.data.get('discovered_worker_queues', [])
|
|
230
|
+
if not discovered_worker_queues:
|
|
231
|
+
raise ValueError("Cannot recommend queue when none were discovered.")
|
|
232
|
+
|
|
233
|
+
queue_ids = [str(q.get('id', '')) for q in discovered_worker_queues if q.get('id')]
|
|
234
|
+
if v not in queue_ids:
|
|
235
|
+
raise ValueError(f"Queue ID '{v}' not in discovered list: {queue_ids}")
|
|
236
|
+
|
|
237
|
+
return v
|
|
238
|
+
|
|
239
|
+
@field_validator('recommended_worker_queue_name')
|
|
240
|
+
@classmethod
|
|
241
|
+
def validate_worker_queue_name_matches(cls, v, info):
|
|
242
|
+
"""Validate worker queue name matches the ID"""
|
|
243
|
+
if v is None:
|
|
244
|
+
return v
|
|
245
|
+
|
|
246
|
+
discovered_worker_queues = info.data.get('discovered_worker_queues', [])
|
|
247
|
+
queue_id = info.data.get('recommended_worker_queue_id', '')
|
|
248
|
+
|
|
249
|
+
for queue in discovered_worker_queues:
|
|
250
|
+
if str(queue.get('id')) == queue_id:
|
|
251
|
+
actual_name = queue.get('name', '')
|
|
252
|
+
if v != actual_name:
|
|
253
|
+
raise ValueError(f"Queue name '{v}' doesn't match '{actual_name}'")
|
|
254
|
+
break
|
|
255
|
+
|
|
256
|
+
return v
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# ============================================================================
|
|
260
|
+
# Fast Selection Output (--local mode)
|
|
261
|
+
# ============================================================================
|
|
262
|
+
|
|
263
|
+
class FastSelectionOutput(BaseModel):
|
|
264
|
+
"""
|
|
265
|
+
Fast selection output for --local mode - minimal fields for quick execution.
|
|
266
|
+
Uses same validators as ResourceDiscoveryOutput to prevent hallucination.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
discovered_agents: List[Dict[str, Any]] = Field(
|
|
270
|
+
default_factory=list,
|
|
271
|
+
description="List of agents found"
|
|
272
|
+
)
|
|
273
|
+
discovered_teams: List[Dict[str, Any]] = Field(
|
|
274
|
+
default_factory=list,
|
|
275
|
+
description="List of teams found"
|
|
276
|
+
)
|
|
277
|
+
discovered_environments: List[Dict[str, Any]] = Field(
|
|
278
|
+
default_factory=list,
|
|
279
|
+
description="List of environments found"
|
|
280
|
+
)
|
|
281
|
+
discovered_worker_queues: List[Dict[str, Any]] = Field(
|
|
282
|
+
default_factory=list,
|
|
283
|
+
description="List of worker queues found"
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
recommended_entity_type: str = Field(..., description="'agent' or 'team'")
|
|
287
|
+
recommended_entity_id: str = Field(..., description="UUID of agent/team from discovered list")
|
|
288
|
+
recommended_entity_name: str = Field(..., description="Name of agent/team from discovered list")
|
|
289
|
+
|
|
290
|
+
selected_agent_runtime: Optional[str] = Field(
|
|
291
|
+
None,
|
|
292
|
+
description="Runtime of selected agent ('default' or 'claude_code')"
|
|
293
|
+
)
|
|
294
|
+
selected_agent_model_id: Optional[str] = Field(
|
|
295
|
+
None,
|
|
296
|
+
description="Model ID of selected agent"
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
recommended_environment_id: Optional[str] = Field(None, description="UUID of environment")
|
|
300
|
+
recommended_environment_name: Optional[str] = Field(None, description="Name of environment")
|
|
301
|
+
recommended_worker_queue_id: Optional[str] = Field(None, description="UUID of worker queue")
|
|
302
|
+
recommended_worker_queue_name: Optional[str] = Field(None, description="Name of worker queue")
|
|
303
|
+
|
|
304
|
+
reasoning: str = Field(..., description="Brief explanation of selection")
|
|
305
|
+
|
|
306
|
+
# Reuse validators from ResourceDiscoveryOutput
|
|
307
|
+
@field_validator('recommended_entity_id')
|
|
308
|
+
@classmethod
|
|
309
|
+
def validate_entity_id_exists(cls, v, info):
|
|
310
|
+
"""Validate entity ID is UUID and exists in discovered lists"""
|
|
311
|
+
try:
|
|
312
|
+
uuid.UUID(v)
|
|
313
|
+
except (ValueError, AttributeError, TypeError):
|
|
314
|
+
raise ValueError(f"'{v}' is NOT a valid UUID!")
|
|
315
|
+
|
|
316
|
+
discovered_agents = info.data.get('discovered_agents', [])
|
|
317
|
+
discovered_teams = info.data.get('discovered_teams', [])
|
|
318
|
+
entity_type = info.data.get('recommended_entity_type', '')
|
|
319
|
+
|
|
320
|
+
if not discovered_agents and not discovered_teams:
|
|
321
|
+
raise ValueError("No agents or teams were discovered.")
|
|
322
|
+
|
|
323
|
+
if entity_type == 'agent':
|
|
324
|
+
agent_ids = [str(a.get('id', '')) for a in discovered_agents if a.get('id')]
|
|
325
|
+
if v not in agent_ids:
|
|
326
|
+
raise ValueError(f"Agent ID '{v}' not found. Available: {agent_ids}")
|
|
327
|
+
elif entity_type == 'team':
|
|
328
|
+
team_ids = [str(t.get('id', '')) for t in discovered_teams if t.get('id')]
|
|
329
|
+
if v not in team_ids:
|
|
330
|
+
raise ValueError(f"Team ID '{v}' not found. Available: {team_ids}")
|
|
331
|
+
else:
|
|
332
|
+
raise ValueError(f"Entity type must be 'agent' or 'team', got '{entity_type}'")
|
|
333
|
+
|
|
334
|
+
return v
|
|
335
|
+
|
|
336
|
+
@field_validator('recommended_entity_name')
|
|
337
|
+
@classmethod
|
|
338
|
+
def validate_entity_name_matches(cls, v, info):
|
|
339
|
+
"""Validate entity name matches the ID"""
|
|
340
|
+
discovered_agents = info.data.get('discovered_agents', [])
|
|
341
|
+
discovered_teams = info.data.get('discovered_teams', [])
|
|
342
|
+
entity_type = info.data.get('recommended_entity_type', '')
|
|
343
|
+
entity_id = info.data.get('recommended_entity_id', '')
|
|
344
|
+
|
|
345
|
+
if entity_type == 'agent':
|
|
346
|
+
for agent in discovered_agents:
|
|
347
|
+
if str(agent.get('id')) == entity_id:
|
|
348
|
+
actual_name = agent.get('name', '')
|
|
349
|
+
if v != actual_name:
|
|
350
|
+
raise ValueError(f"Name '{v}' doesn't match '{actual_name}'")
|
|
351
|
+
break
|
|
352
|
+
elif entity_type == 'team':
|
|
353
|
+
for team in discovered_teams:
|
|
354
|
+
if str(team.get('id')) == entity_id:
|
|
355
|
+
actual_name = team.get('name', '')
|
|
356
|
+
if v != actual_name:
|
|
357
|
+
raise ValueError(f"Name '{v}' doesn't match '{actual_name}'")
|
|
358
|
+
break
|
|
359
|
+
|
|
360
|
+
return v
|
|
361
|
+
|
|
362
|
+
@field_validator('recommended_environment_id')
|
|
363
|
+
@classmethod
|
|
364
|
+
def validate_environment_id_exists(cls, v, info):
|
|
365
|
+
"""Validate environment ID if provided"""
|
|
366
|
+
if v is None:
|
|
367
|
+
return v
|
|
368
|
+
|
|
369
|
+
discovered_environments = info.data.get('discovered_environments', [])
|
|
370
|
+
if not discovered_environments:
|
|
371
|
+
raise ValueError("No environments discovered.")
|
|
372
|
+
|
|
373
|
+
env_ids = [str(e.get('id', '')) for e in discovered_environments if e.get('id')]
|
|
374
|
+
if v not in env_ids:
|
|
375
|
+
raise ValueError(f"Environment ID '{v}' not found. Available: {env_ids}")
|
|
376
|
+
|
|
377
|
+
return v
|
|
378
|
+
|
|
379
|
+
@field_validator('recommended_worker_queue_id')
|
|
380
|
+
@classmethod
|
|
381
|
+
def validate_worker_queue_id_exists(cls, v, info):
|
|
382
|
+
"""Validate worker queue ID if provided"""
|
|
383
|
+
if v is None:
|
|
384
|
+
return v
|
|
385
|
+
|
|
386
|
+
discovered_worker_queues = info.data.get('discovered_worker_queues', [])
|
|
387
|
+
if not discovered_worker_queues:
|
|
388
|
+
raise ValueError("No worker queues discovered.")
|
|
389
|
+
|
|
390
|
+
queue_ids = [str(q.get('id', '')) for q in discovered_worker_queues if q.get('id')]
|
|
391
|
+
if v not in queue_ids:
|
|
392
|
+
raise ValueError(f"Queue ID '{v}' not found. Available: {queue_ids}")
|
|
393
|
+
|
|
394
|
+
return v
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
# ============================================================================
|
|
398
|
+
# Cost Estimation Output
|
|
399
|
+
# ============================================================================
|
|
400
|
+
|
|
401
|
+
class CostEstimationOutput(BaseModel):
|
|
402
|
+
"""Output from Step 3: Cost Estimation"""
|
|
403
|
+
|
|
404
|
+
estimated_tokens_input: int = Field(description="Estimated input tokens")
|
|
405
|
+
estimated_tokens_output: int = Field(description="Estimated output tokens")
|
|
406
|
+
estimated_llm_cost: float = Field(description="Estimated LLM API cost in USD")
|
|
407
|
+
estimated_tool_cost: float = Field(description="Estimated tool execution cost in USD")
|
|
408
|
+
estimated_runtime_cost: float = Field(description="Estimated worker runtime cost in USD")
|
|
409
|
+
total_cost: float = Field(description="Total estimated cost in USD")
|
|
410
|
+
estimated_time_hours: float = Field(description="Estimated execution time in hours")
|
|
411
|
+
|
|
412
|
+
# Savings calculation
|
|
413
|
+
manual_cost: float = Field(description="Cost if done manually by humans")
|
|
414
|
+
manual_time_hours: float = Field(description="Time if done manually in hours")
|
|
415
|
+
money_saved: float = Field(description="Money saved by using AI")
|
|
416
|
+
time_saved_hours: float = Field(description="Time saved in hours")
|
|
417
|
+
savings_percentage: float = Field(description="Percentage of time saved")
|
|
418
|
+
|
|
419
|
+
reasoning: str = Field(description="Explanation of cost calculations")
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
# ============================================================================
|
|
423
|
+
# Validation Helpers
|
|
424
|
+
# ============================================================================
|
|
425
|
+
|
|
426
|
+
def validate_resource_discovery(output: ResourceDiscoveryOutput) -> None:
|
|
427
|
+
"""
|
|
428
|
+
Explicitly validate ResourceDiscoveryOutput to catch issues.
|
|
429
|
+
|
|
430
|
+
This is a safety net in case Pydantic validation is bypassed.
|
|
431
|
+
Raises ValueError with detailed diagnostics.
|
|
432
|
+
"""
|
|
433
|
+
# Check discovered lists are populated
|
|
434
|
+
if not output.discovered_agents and not output.discovered_teams:
|
|
435
|
+
raise ValueError(
|
|
436
|
+
"Both discovered_agents and discovered_teams are empty. "
|
|
437
|
+
"You MUST call list_agents() or list_teams() tools!"
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
if output.recommended_entity_id is None:
|
|
441
|
+
logger.warning(
|
|
442
|
+
"no_entity_recommended",
|
|
443
|
+
discovered_agents=len(output.discovered_agents),
|
|
444
|
+
discovered_teams=len(output.discovered_teams)
|
|
445
|
+
)
|
|
446
|
+
return
|
|
447
|
+
|
|
448
|
+
# Validate UUID format
|
|
449
|
+
try:
|
|
450
|
+
uuid.UUID(output.recommended_entity_id)
|
|
451
|
+
except (ValueError, AttributeError) as e:
|
|
452
|
+
# Try to fix by finding matching entity
|
|
453
|
+
if output.recommended_entity_type == 'agent':
|
|
454
|
+
matching = next(
|
|
455
|
+
(a for a in output.discovered_agents if a.get('name') == output.recommended_entity_id),
|
|
456
|
+
None
|
|
457
|
+
)
|
|
458
|
+
if matching:
|
|
459
|
+
logger.warning(
|
|
460
|
+
"entity_id_was_name_fixed",
|
|
461
|
+
provided_name=output.recommended_entity_id,
|
|
462
|
+
correct_uuid=matching.get('id')
|
|
463
|
+
)
|
|
464
|
+
output.recommended_entity_id = str(matching.get('id'))
|
|
465
|
+
output.recommended_entity_name = matching.get('name')
|
|
466
|
+
else:
|
|
467
|
+
raise ValueError(f"'{output.recommended_entity_id}' is not a valid UUID and no matching agent found")
|
|
468
|
+
elif output.recommended_entity_type == 'team':
|
|
469
|
+
matching = next(
|
|
470
|
+
(t for t in output.discovered_teams if t.get('name') == output.recommended_entity_id),
|
|
471
|
+
None
|
|
472
|
+
)
|
|
473
|
+
if matching:
|
|
474
|
+
logger.warning(
|
|
475
|
+
"entity_id_was_name_fixed",
|
|
476
|
+
provided_name=output.recommended_entity_id,
|
|
477
|
+
correct_uuid=matching.get('id')
|
|
478
|
+
)
|
|
479
|
+
output.recommended_entity_id = str(matching.get('id'))
|
|
480
|
+
output.recommended_entity_name = matching.get('name')
|
|
481
|
+
else:
|
|
482
|
+
raise ValueError(f"'{output.recommended_entity_id}' is not a valid UUID and no matching team found")
|
|
483
|
+
|
|
484
|
+
# Validate ID exists in appropriate list
|
|
485
|
+
if output.recommended_entity_type == 'agent':
|
|
486
|
+
agent_ids = [str(a.get('id', '')) for a in output.discovered_agents if a.get('id')]
|
|
487
|
+
if output.recommended_entity_id not in agent_ids:
|
|
488
|
+
raise ValueError(
|
|
489
|
+
f"Entity ID '{output.recommended_entity_id}' not in discovered_agents: {agent_ids}"
|
|
490
|
+
)
|
|
491
|
+
elif output.recommended_entity_type == 'team':
|
|
492
|
+
team_ids = [str(t.get('id', '')) for t in output.discovered_teams if t.get('id')]
|
|
493
|
+
if output.recommended_entity_id not in team_ids:
|
|
494
|
+
raise ValueError(
|
|
495
|
+
f"Entity ID '{output.recommended_entity_id}' not in discovered_teams: {team_ids}"
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
logger.info(
|
|
499
|
+
"resource_discovery_validation_passed",
|
|
500
|
+
entity_type=output.recommended_entity_type,
|
|
501
|
+
entity_id=output.recommended_entity_id[:12] if output.recommended_entity_id else None,
|
|
502
|
+
entity_name=output.recommended_entity_name
|
|
503
|
+
)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Task Plan Response Validator
|
|
3
|
+
|
|
4
|
+
Validates planner output quality and completeness to ensure reliable plans.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List, Tuple
|
|
8
|
+
from control_plane_api.app.models.task_planning import TaskPlanResponse, TaskPlanRequest
|
|
9
|
+
import structlog
|
|
10
|
+
|
|
11
|
+
logger = structlog.get_logger()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def validate_plan_response(
|
|
15
|
+
plan: TaskPlanResponse,
|
|
16
|
+
request: TaskPlanRequest
|
|
17
|
+
) -> Tuple[bool, List[str]]:
|
|
18
|
+
"""
|
|
19
|
+
Validate plan quality and completeness.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
plan: The generated task plan
|
|
23
|
+
request: The original planning request
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Tuple of (is_valid, list_of_errors)
|
|
27
|
+
"""
|
|
28
|
+
errors = []
|
|
29
|
+
|
|
30
|
+
# 1. Check required fields
|
|
31
|
+
if not plan.title or len(plan.title.strip()) == 0:
|
|
32
|
+
errors.append("Missing or empty title")
|
|
33
|
+
|
|
34
|
+
if not plan.summary or len(plan.summary.strip()) < 10:
|
|
35
|
+
errors.append("Summary is missing or too short (minimum 10 characters)")
|
|
36
|
+
|
|
37
|
+
if not plan.recommended_execution or not plan.recommended_execution.entity_id:
|
|
38
|
+
errors.append("Missing recommended execution entity")
|
|
39
|
+
|
|
40
|
+
# 2. Validate entity exists in provided list
|
|
41
|
+
if plan.recommended_execution and plan.recommended_execution.entity_id:
|
|
42
|
+
entity_type = plan.recommended_execution.entity_type
|
|
43
|
+
entity_id = plan.recommended_execution.entity_id
|
|
44
|
+
|
|
45
|
+
if entity_type == "agent":
|
|
46
|
+
valid_ids = [a.id for a in request.agents]
|
|
47
|
+
if entity_id not in valid_ids:
|
|
48
|
+
errors.append(
|
|
49
|
+
f"Selected agent '{entity_id}' not in available agents list. "
|
|
50
|
+
f"Valid IDs: {valid_ids[:5]}{'...' if len(valid_ids) > 5 else ''}"
|
|
51
|
+
)
|
|
52
|
+
elif entity_type == "team":
|
|
53
|
+
valid_ids = [t.id for t in request.teams]
|
|
54
|
+
if entity_id not in valid_ids:
|
|
55
|
+
errors.append(
|
|
56
|
+
f"Selected team '{entity_id}' not in available teams list. "
|
|
57
|
+
f"Valid IDs: {valid_ids[:5]}{'...' if len(valid_ids) > 5 else ''}"
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
errors.append(f"Invalid entity_type: '{entity_type}'. Must be 'agent' or 'team'")
|
|
61
|
+
|
|
62
|
+
# 3. Check complexity is reasonable
|
|
63
|
+
if plan.complexity:
|
|
64
|
+
story_points = plan.complexity.story_points
|
|
65
|
+
if not (1 <= story_points <= 21):
|
|
66
|
+
errors.append(
|
|
67
|
+
f"Invalid story points: {story_points}. Must be between 1 and 21 (Fibonacci sequence)"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if not plan.complexity.confidence or plan.complexity.confidence not in ["low", "medium", "high"]:
|
|
71
|
+
errors.append(
|
|
72
|
+
f"Invalid confidence level: '{plan.complexity.confidence}'. Must be 'low', 'medium', or 'high'"
|
|
73
|
+
)
|
|
74
|
+
else:
|
|
75
|
+
errors.append("Missing complexity information")
|
|
76
|
+
|
|
77
|
+
# 4. Check cost is non-zero and reasonable
|
|
78
|
+
if plan.cost_estimate:
|
|
79
|
+
cost = plan.cost_estimate.estimated_cost_usd
|
|
80
|
+
if cost <= 0:
|
|
81
|
+
errors.append("Cost estimate must be positive (greater than 0)")
|
|
82
|
+
if cost > 100:
|
|
83
|
+
errors.append(
|
|
84
|
+
f"Cost estimate seems too high: ${cost:.2f}. "
|
|
85
|
+
"Review token estimates and tool costs. Typical tasks cost $0.01-$10."
|
|
86
|
+
)
|
|
87
|
+
else:
|
|
88
|
+
errors.append("Missing cost estimate")
|
|
89
|
+
|
|
90
|
+
# 5. Check reasoning quality
|
|
91
|
+
if plan.recommended_execution:
|
|
92
|
+
reasoning = plan.recommended_execution.execution_reasoning
|
|
93
|
+
if not reasoning or len(reasoning.strip()) < 20:
|
|
94
|
+
errors.append(
|
|
95
|
+
"Execution reasoning is too short or missing. "
|
|
96
|
+
"Need clear explanation (minimum 20 characters) of why this agent/team was selected."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# 6. Validate team breakdown exists and has content
|
|
100
|
+
if not plan.team_breakdown or len(plan.team_breakdown) == 0:
|
|
101
|
+
errors.append("Missing team breakdown - at least one team member entry required")
|
|
102
|
+
|
|
103
|
+
# 7. Check environment/queue selection if provided in request
|
|
104
|
+
if request.environments and len(request.environments) > 0:
|
|
105
|
+
if not plan.recommended_execution.recommended_environment_id:
|
|
106
|
+
errors.append(
|
|
107
|
+
"Environments were provided but none was selected. "
|
|
108
|
+
"Must select an environment from the available list."
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# 8. Validate realized savings calculations make sense
|
|
112
|
+
if plan.realized_savings:
|
|
113
|
+
savings = plan.realized_savings
|
|
114
|
+
if savings.with_kubiya_cost > savings.without_kubiya_cost:
|
|
115
|
+
errors.append(
|
|
116
|
+
f"Invalid savings calculation: with_kubiya_cost (${savings.with_kubiya_cost:.2f}) "
|
|
117
|
+
f"is greater than without_kubiya_cost (${savings.without_kubiya_cost:.2f}). "
|
|
118
|
+
"Kubiya should save money, not cost more."
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
calculated_savings = savings.without_kubiya_cost - savings.with_kubiya_cost
|
|
122
|
+
if abs(calculated_savings - savings.money_saved) > 0.01:
|
|
123
|
+
errors.append(
|
|
124
|
+
f"Savings calculation mismatch: money_saved (${savings.money_saved:.2f}) "
|
|
125
|
+
f"does not match without_kubiya_cost - with_kubiya_cost (${calculated_savings:.2f})"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
is_valid = len(errors) == 0
|
|
129
|
+
|
|
130
|
+
if not is_valid:
|
|
131
|
+
logger.warning(
|
|
132
|
+
"plan_validation_failed",
|
|
133
|
+
error_count=len(errors),
|
|
134
|
+
errors=errors,
|
|
135
|
+
title=plan.title if plan.title else "N/A"
|
|
136
|
+
)
|
|
137
|
+
else:
|
|
138
|
+
logger.info("plan_validation_succeeded", title=plan.title)
|
|
139
|
+
|
|
140
|
+
return (is_valid, errors)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def format_validation_errors_for_retry(errors: List[str]) -> str:
|
|
144
|
+
"""
|
|
145
|
+
Format validation errors into a prompt enhancement for retry.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
errors: List of validation error messages
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Formatted string to append to prompt
|
|
152
|
+
"""
|
|
153
|
+
error_bullets = "\n".join(f"- {error}" for error in errors)
|
|
154
|
+
|
|
155
|
+
return f"""
|
|
156
|
+
|
|
157
|
+
**⚠️ PREVIOUS ATTEMPT FAILED VALIDATION**
|
|
158
|
+
|
|
159
|
+
The previous plan had the following issues that must be fixed:
|
|
160
|
+
|
|
161
|
+
{error_bullets}
|
|
162
|
+
|
|
163
|
+
**CRITICAL**: Please address ALL of the above issues in your new plan.
|
|
164
|
+
Ensure all required fields are present, IDs match exactly from the provided lists,
|
|
165
|
+
and all calculations are correct.
|
|
166
|
+
"""
|