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.
Files changed (479) hide show
  1. control_plane_api/LICENSE +676 -0
  2. control_plane_api/README.md +350 -0
  3. control_plane_api/__init__.py +4 -0
  4. control_plane_api/__version__.py +8 -0
  5. control_plane_api/alembic/README +1 -0
  6. control_plane_api/alembic/env.py +121 -0
  7. control_plane_api/alembic/script.py.mako +28 -0
  8. control_plane_api/alembic/versions/2613c65c3dbe_initial_database_setup.py +32 -0
  9. control_plane_api/alembic/versions/2df520d4927d_merge_heads.py +28 -0
  10. control_plane_api/alembic/versions/43abf98d6a01_add_paused_status_to_executions.py +73 -0
  11. control_plane_api/alembic/versions/6289854264cb_merge_multiple_heads.py +28 -0
  12. control_plane_api/alembic/versions/6a4d4dc3d8dc_generate_execution_transitions.py +50 -0
  13. control_plane_api/alembic/versions/87d11cf0a783_add_disconnected_status_to_worker_.py +44 -0
  14. control_plane_api/alembic/versions/add_ephemeral_queue_support.py +85 -0
  15. control_plane_api/alembic/versions/add_model_type_to_llm_models.py +31 -0
  16. control_plane_api/alembic/versions/add_plan_executions_table.py +114 -0
  17. control_plane_api/alembic/versions/add_trace_span_tables.py +154 -0
  18. control_plane_api/alembic/versions/add_user_info_to_traces.py +36 -0
  19. control_plane_api/alembic/versions/adjusting_foreign_keys.py +32 -0
  20. control_plane_api/alembic/versions/b4983d976db2_initial_tables.py +1128 -0
  21. control_plane_api/alembic/versions/d181a3b40e71_rename_custom_metadata_to_metadata_in_.py +50 -0
  22. control_plane_api/alembic/versions/df9117888e82_add_missing_columns.py +82 -0
  23. control_plane_api/alembic/versions/f25de6ad895a_missing_migrations.py +34 -0
  24. control_plane_api/alembic/versions/f71305fb69b9_fix_ephemeral_queue_deletion_foreign_key.py +54 -0
  25. control_plane_api/alembic/versions/mark_local_exec_queues_as_ephemeral.py +68 -0
  26. control_plane_api/alembic.ini +148 -0
  27. control_plane_api/api/index.py +12 -0
  28. control_plane_api/app/__init__.py +11 -0
  29. control_plane_api/app/activities/__init__.py +20 -0
  30. control_plane_api/app/activities/agent_activities.py +384 -0
  31. control_plane_api/app/activities/plan_generation_activities.py +499 -0
  32. control_plane_api/app/activities/team_activities.py +424 -0
  33. control_plane_api/app/activities/temporal_cloud_activities.py +588 -0
  34. control_plane_api/app/config/__init__.py +35 -0
  35. control_plane_api/app/config/api_config.py +469 -0
  36. control_plane_api/app/config/config_loader.py +224 -0
  37. control_plane_api/app/config/model_pricing.py +323 -0
  38. control_plane_api/app/config/storage_config.py +159 -0
  39. control_plane_api/app/config.py +115 -0
  40. control_plane_api/app/controllers/__init__.py +0 -0
  41. control_plane_api/app/controllers/execution_environment_controller.py +1315 -0
  42. control_plane_api/app/database.py +135 -0
  43. control_plane_api/app/exceptions.py +408 -0
  44. control_plane_api/app/lib/__init__.py +11 -0
  45. control_plane_api/app/lib/environment.py +65 -0
  46. control_plane_api/app/lib/event_bus/__init__.py +17 -0
  47. control_plane_api/app/lib/event_bus/base.py +136 -0
  48. control_plane_api/app/lib/event_bus/manager.py +335 -0
  49. control_plane_api/app/lib/event_bus/providers/__init__.py +6 -0
  50. control_plane_api/app/lib/event_bus/providers/http_provider.py +166 -0
  51. control_plane_api/app/lib/event_bus/providers/nats_provider.py +324 -0
  52. control_plane_api/app/lib/event_bus/providers/redis_provider.py +233 -0
  53. control_plane_api/app/lib/event_bus/providers/websocket_provider.py +497 -0
  54. control_plane_api/app/lib/job_executor.py +330 -0
  55. control_plane_api/app/lib/kubiya_client.py +293 -0
  56. control_plane_api/app/lib/litellm_pricing.py +166 -0
  57. control_plane_api/app/lib/mcp_validation.py +163 -0
  58. control_plane_api/app/lib/nats/__init__.py +13 -0
  59. control_plane_api/app/lib/nats/credentials_manager.py +288 -0
  60. control_plane_api/app/lib/nats/listener.py +374 -0
  61. control_plane_api/app/lib/planning_prompt_builder.py +153 -0
  62. control_plane_api/app/lib/planning_tools/__init__.py +41 -0
  63. control_plane_api/app/lib/planning_tools/agents.py +409 -0
  64. control_plane_api/app/lib/planning_tools/agno_toolkit.py +836 -0
  65. control_plane_api/app/lib/planning_tools/base.py +119 -0
  66. control_plane_api/app/lib/planning_tools/cognitive_memory_tools.py +403 -0
  67. control_plane_api/app/lib/planning_tools/context_graph_tools.py +545 -0
  68. control_plane_api/app/lib/planning_tools/environments.py +218 -0
  69. control_plane_api/app/lib/planning_tools/knowledge.py +204 -0
  70. control_plane_api/app/lib/planning_tools/models.py +93 -0
  71. control_plane_api/app/lib/planning_tools/planning_service.py +646 -0
  72. control_plane_api/app/lib/planning_tools/resources.py +242 -0
  73. control_plane_api/app/lib/planning_tools/teams.py +334 -0
  74. control_plane_api/app/lib/policy_enforcer_client.py +1016 -0
  75. control_plane_api/app/lib/redis_client.py +803 -0
  76. control_plane_api/app/lib/sqlalchemy_utils.py +486 -0
  77. control_plane_api/app/lib/state_transition_tools/__init__.py +7 -0
  78. control_plane_api/app/lib/state_transition_tools/execution_context.py +388 -0
  79. control_plane_api/app/lib/storage/__init__.py +20 -0
  80. control_plane_api/app/lib/storage/base_provider.py +274 -0
  81. control_plane_api/app/lib/storage/provider_factory.py +157 -0
  82. control_plane_api/app/lib/storage/vercel_blob_provider.py +468 -0
  83. control_plane_api/app/lib/supabase.py +71 -0
  84. control_plane_api/app/lib/supabase_utils.py +138 -0
  85. control_plane_api/app/lib/task_planning/__init__.py +138 -0
  86. control_plane_api/app/lib/task_planning/agent_factory.py +308 -0
  87. control_plane_api/app/lib/task_planning/agents.py +389 -0
  88. control_plane_api/app/lib/task_planning/cache.py +218 -0
  89. control_plane_api/app/lib/task_planning/entity_resolver.py +273 -0
  90. control_plane_api/app/lib/task_planning/helpers.py +293 -0
  91. control_plane_api/app/lib/task_planning/hooks.py +474 -0
  92. control_plane_api/app/lib/task_planning/models.py +503 -0
  93. control_plane_api/app/lib/task_planning/plan_validator.py +166 -0
  94. control_plane_api/app/lib/task_planning/planning_workflow.py +2911 -0
  95. control_plane_api/app/lib/task_planning/runner.py +656 -0
  96. control_plane_api/app/lib/task_planning/streaming_hook.py +213 -0
  97. control_plane_api/app/lib/task_planning/workflow.py +424 -0
  98. control_plane_api/app/lib/templating/__init__.py +88 -0
  99. control_plane_api/app/lib/templating/compiler.py +278 -0
  100. control_plane_api/app/lib/templating/engine.py +178 -0
  101. control_plane_api/app/lib/templating/parsers/__init__.py +29 -0
  102. control_plane_api/app/lib/templating/parsers/base.py +96 -0
  103. control_plane_api/app/lib/templating/parsers/env.py +85 -0
  104. control_plane_api/app/lib/templating/parsers/graph.py +112 -0
  105. control_plane_api/app/lib/templating/parsers/secret.py +87 -0
  106. control_plane_api/app/lib/templating/parsers/simple.py +81 -0
  107. control_plane_api/app/lib/templating/resolver.py +366 -0
  108. control_plane_api/app/lib/templating/types.py +214 -0
  109. control_plane_api/app/lib/templating/validator.py +201 -0
  110. control_plane_api/app/lib/temporal_client.py +232 -0
  111. control_plane_api/app/lib/temporal_credentials_cache.py +178 -0
  112. control_plane_api/app/lib/temporal_credentials_service.py +203 -0
  113. control_plane_api/app/lib/validation/__init__.py +24 -0
  114. control_plane_api/app/lib/validation/runtime_validation.py +388 -0
  115. control_plane_api/app/main.py +531 -0
  116. control_plane_api/app/middleware/__init__.py +10 -0
  117. control_plane_api/app/middleware/auth.py +645 -0
  118. control_plane_api/app/middleware/exception_handler.py +267 -0
  119. control_plane_api/app/middleware/prometheus_middleware.py +173 -0
  120. control_plane_api/app/middleware/rate_limiting.py +384 -0
  121. control_plane_api/app/middleware/request_id.py +202 -0
  122. control_plane_api/app/models/__init__.py +40 -0
  123. control_plane_api/app/models/agent.py +90 -0
  124. control_plane_api/app/models/analytics.py +206 -0
  125. control_plane_api/app/models/associations.py +107 -0
  126. control_plane_api/app/models/auth_user.py +73 -0
  127. control_plane_api/app/models/context.py +161 -0
  128. control_plane_api/app/models/custom_integration.py +99 -0
  129. control_plane_api/app/models/environment.py +64 -0
  130. control_plane_api/app/models/execution.py +125 -0
  131. control_plane_api/app/models/execution_transition.py +50 -0
  132. control_plane_api/app/models/job.py +159 -0
  133. control_plane_api/app/models/llm_model.py +78 -0
  134. control_plane_api/app/models/orchestration.py +66 -0
  135. control_plane_api/app/models/plan_execution.py +102 -0
  136. control_plane_api/app/models/presence.py +49 -0
  137. control_plane_api/app/models/project.py +61 -0
  138. control_plane_api/app/models/project_management.py +85 -0
  139. control_plane_api/app/models/session.py +29 -0
  140. control_plane_api/app/models/skill.py +155 -0
  141. control_plane_api/app/models/system_tables.py +43 -0
  142. control_plane_api/app/models/task_planning.py +372 -0
  143. control_plane_api/app/models/team.py +86 -0
  144. control_plane_api/app/models/trace.py +257 -0
  145. control_plane_api/app/models/user_profile.py +54 -0
  146. control_plane_api/app/models/worker.py +221 -0
  147. control_plane_api/app/models/workflow.py +161 -0
  148. control_plane_api/app/models/workspace.py +50 -0
  149. control_plane_api/app/observability/__init__.py +177 -0
  150. control_plane_api/app/observability/context_logging.py +475 -0
  151. control_plane_api/app/observability/decorators.py +337 -0
  152. control_plane_api/app/observability/local_span_processor.py +702 -0
  153. control_plane_api/app/observability/metrics.py +303 -0
  154. control_plane_api/app/observability/middleware.py +246 -0
  155. control_plane_api/app/observability/optional.py +115 -0
  156. control_plane_api/app/observability/tracing.py +382 -0
  157. control_plane_api/app/policies/README.md +149 -0
  158. control_plane_api/app/policies/approved_users.rego +62 -0
  159. control_plane_api/app/policies/business_hours.rego +51 -0
  160. control_plane_api/app/policies/rate_limiting.rego +100 -0
  161. control_plane_api/app/policies/tool_enforcement/README.md +336 -0
  162. control_plane_api/app/policies/tool_enforcement/bash_command_validation.rego +71 -0
  163. control_plane_api/app/policies/tool_enforcement/business_hours_enforcement.rego +82 -0
  164. control_plane_api/app/policies/tool_enforcement/mcp_tool_allowlist.rego +58 -0
  165. control_plane_api/app/policies/tool_enforcement/production_safeguards.rego +80 -0
  166. control_plane_api/app/policies/tool_enforcement/role_based_tool_access.rego +44 -0
  167. control_plane_api/app/policies/tool_restrictions.rego +86 -0
  168. control_plane_api/app/routers/__init__.py +4 -0
  169. control_plane_api/app/routers/agents.py +382 -0
  170. control_plane_api/app/routers/agents_v2.py +1598 -0
  171. control_plane_api/app/routers/analytics.py +1310 -0
  172. control_plane_api/app/routers/auth.py +59 -0
  173. control_plane_api/app/routers/client_config.py +57 -0
  174. control_plane_api/app/routers/context_graph.py +561 -0
  175. control_plane_api/app/routers/context_manager.py +577 -0
  176. control_plane_api/app/routers/custom_integrations.py +490 -0
  177. control_plane_api/app/routers/enforcer.py +132 -0
  178. control_plane_api/app/routers/environment_context.py +252 -0
  179. control_plane_api/app/routers/environments.py +761 -0
  180. control_plane_api/app/routers/execution_environment.py +847 -0
  181. control_plane_api/app/routers/executions/__init__.py +28 -0
  182. control_plane_api/app/routers/executions/router.py +286 -0
  183. control_plane_api/app/routers/executions/services/__init__.py +22 -0
  184. control_plane_api/app/routers/executions/services/demo_worker_health.py +156 -0
  185. control_plane_api/app/routers/executions/services/status_service.py +420 -0
  186. control_plane_api/app/routers/executions/services/test_worker_health.py +480 -0
  187. control_plane_api/app/routers/executions/services/worker_health.py +514 -0
  188. control_plane_api/app/routers/executions/streaming/__init__.py +22 -0
  189. control_plane_api/app/routers/executions/streaming/deduplication.py +352 -0
  190. control_plane_api/app/routers/executions/streaming/event_buffer.py +353 -0
  191. control_plane_api/app/routers/executions/streaming/event_formatter.py +964 -0
  192. control_plane_api/app/routers/executions/streaming/history_loader.py +588 -0
  193. control_plane_api/app/routers/executions/streaming/live_source.py +693 -0
  194. control_plane_api/app/routers/executions/streaming/streamer.py +849 -0
  195. control_plane_api/app/routers/executions.py +4888 -0
  196. control_plane_api/app/routers/health.py +165 -0
  197. control_plane_api/app/routers/health_v2.py +394 -0
  198. control_plane_api/app/routers/integration_templates.py +496 -0
  199. control_plane_api/app/routers/integrations.py +287 -0
  200. control_plane_api/app/routers/jobs.py +1809 -0
  201. control_plane_api/app/routers/metrics.py +517 -0
  202. control_plane_api/app/routers/models.py +82 -0
  203. control_plane_api/app/routers/models_v2.py +628 -0
  204. control_plane_api/app/routers/plan_executions.py +1481 -0
  205. control_plane_api/app/routers/plan_generation_async.py +304 -0
  206. control_plane_api/app/routers/policies.py +669 -0
  207. control_plane_api/app/routers/presence.py +234 -0
  208. control_plane_api/app/routers/projects.py +987 -0
  209. control_plane_api/app/routers/runners.py +379 -0
  210. control_plane_api/app/routers/runtimes.py +172 -0
  211. control_plane_api/app/routers/secrets.py +171 -0
  212. control_plane_api/app/routers/skills.py +1010 -0
  213. control_plane_api/app/routers/skills_definitions.py +140 -0
  214. control_plane_api/app/routers/storage.py +456 -0
  215. control_plane_api/app/routers/task_planning.py +611 -0
  216. control_plane_api/app/routers/task_queues.py +650 -0
  217. control_plane_api/app/routers/team_context.py +274 -0
  218. control_plane_api/app/routers/teams.py +1747 -0
  219. control_plane_api/app/routers/templates.py +248 -0
  220. control_plane_api/app/routers/traces.py +571 -0
  221. control_plane_api/app/routers/websocket_client.py +479 -0
  222. control_plane_api/app/routers/websocket_executions_status.py +437 -0
  223. control_plane_api/app/routers/websocket_gateway.py +323 -0
  224. control_plane_api/app/routers/websocket_traces.py +576 -0
  225. control_plane_api/app/routers/worker_queues.py +2555 -0
  226. control_plane_api/app/routers/worker_websocket.py +419 -0
  227. control_plane_api/app/routers/workers.py +1004 -0
  228. control_plane_api/app/routers/workflows.py +204 -0
  229. control_plane_api/app/runtimes/__init__.py +6 -0
  230. control_plane_api/app/runtimes/validation.py +344 -0
  231. control_plane_api/app/schemas/__init__.py +1 -0
  232. control_plane_api/app/schemas/job_schemas.py +302 -0
  233. control_plane_api/app/schemas/mcp_schemas.py +311 -0
  234. control_plane_api/app/schemas/template_schemas.py +133 -0
  235. control_plane_api/app/schemas/trace_schemas.py +168 -0
  236. control_plane_api/app/schemas/worker_queue_observability_schemas.py +165 -0
  237. control_plane_api/app/services/__init__.py +1 -0
  238. control_plane_api/app/services/agno_planning_strategy.py +233 -0
  239. control_plane_api/app/services/agno_service.py +838 -0
  240. control_plane_api/app/services/claude_code_planning_service.py +203 -0
  241. control_plane_api/app/services/context_graph_client.py +224 -0
  242. control_plane_api/app/services/custom_integration_service.py +415 -0
  243. control_plane_api/app/services/integration_resolution_service.py +345 -0
  244. control_plane_api/app/services/litellm_service.py +394 -0
  245. control_plane_api/app/services/plan_generator.py +79 -0
  246. control_plane_api/app/services/planning_strategy.py +66 -0
  247. control_plane_api/app/services/planning_strategy_factory.py +118 -0
  248. control_plane_api/app/services/policy_service.py +615 -0
  249. control_plane_api/app/services/state_transition_service.py +755 -0
  250. control_plane_api/app/services/storage_service.py +593 -0
  251. control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
  252. control_plane_api/app/services/toolsets/context_graph_skill.py +432 -0
  253. control_plane_api/app/services/trace_retention.py +354 -0
  254. control_plane_api/app/services/worker_queue_metrics_service.py +190 -0
  255. control_plane_api/app/services/workflow_cancellation_manager.py +135 -0
  256. control_plane_api/app/services/workflow_operations_service.py +611 -0
  257. control_plane_api/app/skills/__init__.py +100 -0
  258. control_plane_api/app/skills/base.py +239 -0
  259. control_plane_api/app/skills/builtin/__init__.py +37 -0
  260. control_plane_api/app/skills/builtin/agent_communication/__init__.py +8 -0
  261. control_plane_api/app/skills/builtin/agent_communication/skill.py +246 -0
  262. control_plane_api/app/skills/builtin/code_ingestion/__init__.py +4 -0
  263. control_plane_api/app/skills/builtin/code_ingestion/skill.py +267 -0
  264. control_plane_api/app/skills/builtin/cognitive_memory/__init__.py +4 -0
  265. control_plane_api/app/skills/builtin/cognitive_memory/skill.py +174 -0
  266. control_plane_api/app/skills/builtin/contextual_awareness/__init__.py +4 -0
  267. control_plane_api/app/skills/builtin/contextual_awareness/skill.py +387 -0
  268. control_plane_api/app/skills/builtin/data_visualization/__init__.py +4 -0
  269. control_plane_api/app/skills/builtin/data_visualization/skill.py +154 -0
  270. control_plane_api/app/skills/builtin/docker/__init__.py +4 -0
  271. control_plane_api/app/skills/builtin/docker/skill.py +104 -0
  272. control_plane_api/app/skills/builtin/file_generation/__init__.py +4 -0
  273. control_plane_api/app/skills/builtin/file_generation/skill.py +94 -0
  274. control_plane_api/app/skills/builtin/file_system/__init__.py +4 -0
  275. control_plane_api/app/skills/builtin/file_system/skill.py +110 -0
  276. control_plane_api/app/skills/builtin/knowledge_api/__init__.py +5 -0
  277. control_plane_api/app/skills/builtin/knowledge_api/skill.py +124 -0
  278. control_plane_api/app/skills/builtin/python/__init__.py +4 -0
  279. control_plane_api/app/skills/builtin/python/skill.py +92 -0
  280. control_plane_api/app/skills/builtin/remote_filesystem/__init__.py +5 -0
  281. control_plane_api/app/skills/builtin/remote_filesystem/skill.py +170 -0
  282. control_plane_api/app/skills/builtin/shell/__init__.py +4 -0
  283. control_plane_api/app/skills/builtin/shell/skill.py +161 -0
  284. control_plane_api/app/skills/builtin/slack/__init__.py +3 -0
  285. control_plane_api/app/skills/builtin/slack/skill.py +302 -0
  286. control_plane_api/app/skills/builtin/workflow_executor/__init__.py +4 -0
  287. control_plane_api/app/skills/builtin/workflow_executor/skill.py +469 -0
  288. control_plane_api/app/skills/business_intelligence.py +189 -0
  289. control_plane_api/app/skills/config.py +63 -0
  290. control_plane_api/app/skills/loaders/__init__.py +14 -0
  291. control_plane_api/app/skills/loaders/base.py +73 -0
  292. control_plane_api/app/skills/loaders/filesystem_loader.py +199 -0
  293. control_plane_api/app/skills/registry.py +125 -0
  294. control_plane_api/app/utils/helpers.py +12 -0
  295. control_plane_api/app/utils/workflow_executor.py +354 -0
  296. control_plane_api/app/workflows/__init__.py +11 -0
  297. control_plane_api/app/workflows/agent_execution.py +520 -0
  298. control_plane_api/app/workflows/agent_execution_with_skills.py +223 -0
  299. control_plane_api/app/workflows/namespace_provisioning.py +326 -0
  300. control_plane_api/app/workflows/plan_generation.py +254 -0
  301. control_plane_api/app/workflows/team_execution.py +442 -0
  302. control_plane_api/scripts/seed_models.py +240 -0
  303. control_plane_api/scripts/validate_existing_tool_names.py +492 -0
  304. control_plane_api/shared/__init__.py +8 -0
  305. control_plane_api/shared/version.py +17 -0
  306. control_plane_api/test_deduplication.py +274 -0
  307. control_plane_api/test_executor_deduplication_e2e.py +309 -0
  308. control_plane_api/test_job_execution_e2e.py +283 -0
  309. control_plane_api/test_real_integration.py +193 -0
  310. control_plane_api/version.py +38 -0
  311. control_plane_api/worker/__init__.py +0 -0
  312. control_plane_api/worker/activities/__init__.py +0 -0
  313. control_plane_api/worker/activities/agent_activities.py +1585 -0
  314. control_plane_api/worker/activities/approval_activities.py +234 -0
  315. control_plane_api/worker/activities/job_activities.py +199 -0
  316. control_plane_api/worker/activities/runtime_activities.py +1167 -0
  317. control_plane_api/worker/activities/skill_activities.py +282 -0
  318. control_plane_api/worker/activities/team_activities.py +479 -0
  319. control_plane_api/worker/agent_runtime_server.py +370 -0
  320. control_plane_api/worker/binary_manager.py +333 -0
  321. control_plane_api/worker/config/__init__.py +31 -0
  322. control_plane_api/worker/config/worker_config.py +273 -0
  323. control_plane_api/worker/control_plane_client.py +1491 -0
  324. control_plane_api/worker/examples/analytics_integration_example.py +362 -0
  325. control_plane_api/worker/health_monitor.py +159 -0
  326. control_plane_api/worker/metrics.py +237 -0
  327. control_plane_api/worker/models/__init__.py +1 -0
  328. control_plane_api/worker/models/error_events.py +105 -0
  329. control_plane_api/worker/models/inputs.py +89 -0
  330. control_plane_api/worker/runtimes/__init__.py +35 -0
  331. control_plane_api/worker/runtimes/agent_runtime/runtime.py +485 -0
  332. control_plane_api/worker/runtimes/agno/__init__.py +34 -0
  333. control_plane_api/worker/runtimes/agno/config.py +248 -0
  334. control_plane_api/worker/runtimes/agno/hooks.py +385 -0
  335. control_plane_api/worker/runtimes/agno/mcp_builder.py +195 -0
  336. control_plane_api/worker/runtimes/agno/runtime.py +1063 -0
  337. control_plane_api/worker/runtimes/agno/utils.py +163 -0
  338. control_plane_api/worker/runtimes/base.py +979 -0
  339. control_plane_api/worker/runtimes/claude_code/__init__.py +38 -0
  340. control_plane_api/worker/runtimes/claude_code/cleanup.py +184 -0
  341. control_plane_api/worker/runtimes/claude_code/client_pool.py +529 -0
  342. control_plane_api/worker/runtimes/claude_code/config.py +829 -0
  343. control_plane_api/worker/runtimes/claude_code/hooks.py +482 -0
  344. control_plane_api/worker/runtimes/claude_code/litellm_proxy.py +1702 -0
  345. control_plane_api/worker/runtimes/claude_code/mcp_builder.py +467 -0
  346. control_plane_api/worker/runtimes/claude_code/mcp_discovery.py +558 -0
  347. control_plane_api/worker/runtimes/claude_code/runtime.py +1546 -0
  348. control_plane_api/worker/runtimes/claude_code/tool_mapper.py +403 -0
  349. control_plane_api/worker/runtimes/claude_code/utils.py +149 -0
  350. control_plane_api/worker/runtimes/factory.py +173 -0
  351. control_plane_api/worker/runtimes/model_utils.py +107 -0
  352. control_plane_api/worker/runtimes/validation.py +93 -0
  353. control_plane_api/worker/services/__init__.py +1 -0
  354. control_plane_api/worker/services/agent_communication_tools.py +908 -0
  355. control_plane_api/worker/services/agent_executor.py +485 -0
  356. control_plane_api/worker/services/agent_executor_v2.py +793 -0
  357. control_plane_api/worker/services/analytics_collector.py +457 -0
  358. control_plane_api/worker/services/analytics_service.py +464 -0
  359. control_plane_api/worker/services/approval_tools.py +310 -0
  360. control_plane_api/worker/services/approval_tools_agno.py +207 -0
  361. control_plane_api/worker/services/cancellation_manager.py +177 -0
  362. control_plane_api/worker/services/code_ingestion_tools.py +465 -0
  363. control_plane_api/worker/services/contextual_awareness_tools.py +405 -0
  364. control_plane_api/worker/services/data_visualization.py +834 -0
  365. control_plane_api/worker/services/event_publisher.py +531 -0
  366. control_plane_api/worker/services/jira_tools.py +257 -0
  367. control_plane_api/worker/services/remote_filesystem_tools.py +498 -0
  368. control_plane_api/worker/services/runtime_analytics.py +328 -0
  369. control_plane_api/worker/services/session_service.py +365 -0
  370. control_plane_api/worker/services/skill_context_enhancement.py +181 -0
  371. control_plane_api/worker/services/skill_factory.py +471 -0
  372. control_plane_api/worker/services/system_prompt_enhancement.py +410 -0
  373. control_plane_api/worker/services/team_executor.py +715 -0
  374. control_plane_api/worker/services/team_executor_v2.py +1866 -0
  375. control_plane_api/worker/services/tool_enforcement.py +254 -0
  376. control_plane_api/worker/services/workflow_executor/__init__.py +52 -0
  377. control_plane_api/worker/services/workflow_executor/event_processor.py +287 -0
  378. control_plane_api/worker/services/workflow_executor/event_publisher.py +210 -0
  379. control_plane_api/worker/services/workflow_executor/executors/__init__.py +15 -0
  380. control_plane_api/worker/services/workflow_executor/executors/base.py +270 -0
  381. control_plane_api/worker/services/workflow_executor/executors/json_executor.py +50 -0
  382. control_plane_api/worker/services/workflow_executor/executors/python_executor.py +50 -0
  383. control_plane_api/worker/services/workflow_executor/models.py +142 -0
  384. control_plane_api/worker/services/workflow_executor_tools.py +1748 -0
  385. control_plane_api/worker/skills/__init__.py +12 -0
  386. control_plane_api/worker/skills/builtin/context_graph_search/README.md +213 -0
  387. control_plane_api/worker/skills/builtin/context_graph_search/__init__.py +5 -0
  388. control_plane_api/worker/skills/builtin/context_graph_search/agno_impl.py +808 -0
  389. control_plane_api/worker/skills/builtin/context_graph_search/skill.yaml +67 -0
  390. control_plane_api/worker/skills/builtin/contextual_awareness/__init__.py +4 -0
  391. control_plane_api/worker/skills/builtin/contextual_awareness/agno_impl.py +62 -0
  392. control_plane_api/worker/skills/builtin/data_visualization/agno_impl.py +18 -0
  393. control_plane_api/worker/skills/builtin/data_visualization/skill.yaml +84 -0
  394. control_plane_api/worker/skills/builtin/docker/agno_impl.py +65 -0
  395. control_plane_api/worker/skills/builtin/docker/skill.yaml +60 -0
  396. control_plane_api/worker/skills/builtin/file_generation/agno_impl.py +47 -0
  397. control_plane_api/worker/skills/builtin/file_generation/skill.yaml +64 -0
  398. control_plane_api/worker/skills/builtin/file_system/agno_impl.py +32 -0
  399. control_plane_api/worker/skills/builtin/file_system/skill.yaml +54 -0
  400. control_plane_api/worker/skills/builtin/knowledge_api/__init__.py +4 -0
  401. control_plane_api/worker/skills/builtin/knowledge_api/agno_impl.py +50 -0
  402. control_plane_api/worker/skills/builtin/knowledge_api/skill.yaml +66 -0
  403. control_plane_api/worker/skills/builtin/python/agno_impl.py +25 -0
  404. control_plane_api/worker/skills/builtin/python/skill.yaml +60 -0
  405. control_plane_api/worker/skills/builtin/schema_fix_mixin.py +260 -0
  406. control_plane_api/worker/skills/builtin/shell/agno_impl.py +31 -0
  407. control_plane_api/worker/skills/builtin/shell/skill.yaml +60 -0
  408. control_plane_api/worker/skills/builtin/slack/__init__.py +3 -0
  409. control_plane_api/worker/skills/builtin/slack/agno_impl.py +1282 -0
  410. control_plane_api/worker/skills/builtin/slack/skill.yaml +276 -0
  411. control_plane_api/worker/skills/builtin/workflow_executor/agno_impl.py +62 -0
  412. control_plane_api/worker/skills/builtin/workflow_executor/skill.yaml +79 -0
  413. control_plane_api/worker/skills/loaders/__init__.py +5 -0
  414. control_plane_api/worker/skills/loaders/base.py +23 -0
  415. control_plane_api/worker/skills/loaders/filesystem_loader.py +357 -0
  416. control_plane_api/worker/skills/registry.py +208 -0
  417. control_plane_api/worker/tests/__init__.py +1 -0
  418. control_plane_api/worker/tests/conftest.py +12 -0
  419. control_plane_api/worker/tests/e2e/__init__.py +0 -0
  420. control_plane_api/worker/tests/e2e/test_context_graph_real_api.py +338 -0
  421. control_plane_api/worker/tests/e2e/test_context_graph_templates_e2e.py +523 -0
  422. control_plane_api/worker/tests/e2e/test_enforcement_e2e.py +344 -0
  423. control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
  424. control_plane_api/worker/tests/e2e/test_single_execution_mode.py +656 -0
  425. control_plane_api/worker/tests/integration/__init__.py +0 -0
  426. control_plane_api/worker/tests/integration/test_builtin_skills_fixes.py +245 -0
  427. control_plane_api/worker/tests/integration/test_context_graph_search_integration.py +365 -0
  428. control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
  429. control_plane_api/worker/tests/integration/test_hook_enforcement_integration.py +579 -0
  430. control_plane_api/worker/tests/integration/test_scheduled_job_workflow.py +237 -0
  431. control_plane_api/worker/tests/integration/test_system_prompt_enhancement_integration.py +343 -0
  432. control_plane_api/worker/tests/unit/__init__.py +0 -0
  433. control_plane_api/worker/tests/unit/test_builtin_skill_autoload.py +396 -0
  434. control_plane_api/worker/tests/unit/test_context_graph_search.py +450 -0
  435. control_plane_api/worker/tests/unit/test_context_graph_templates.py +403 -0
  436. control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
  437. control_plane_api/worker/tests/unit/test_control_plane_client_jobs.py +345 -0
  438. control_plane_api/worker/tests/unit/test_job_activities.py +353 -0
  439. control_plane_api/worker/tests/unit/test_skill_context_enhancement.py +321 -0
  440. control_plane_api/worker/tests/unit/test_system_prompt_enhancement.py +415 -0
  441. control_plane_api/worker/tests/unit/test_tool_enforcement.py +324 -0
  442. control_plane_api/worker/utils/__init__.py +1 -0
  443. control_plane_api/worker/utils/chunk_batcher.py +330 -0
  444. control_plane_api/worker/utils/environment.py +65 -0
  445. control_plane_api/worker/utils/error_publisher.py +260 -0
  446. control_plane_api/worker/utils/event_batcher.py +256 -0
  447. control_plane_api/worker/utils/logging_config.py +335 -0
  448. control_plane_api/worker/utils/logging_helper.py +326 -0
  449. control_plane_api/worker/utils/parameter_validator.py +120 -0
  450. control_plane_api/worker/utils/retry_utils.py +60 -0
  451. control_plane_api/worker/utils/streaming_utils.py +665 -0
  452. control_plane_api/worker/utils/tool_validation.py +332 -0
  453. control_plane_api/worker/utils/workspace_manager.py +163 -0
  454. control_plane_api/worker/websocket_client.py +393 -0
  455. control_plane_api/worker/worker.py +1297 -0
  456. control_plane_api/worker/workflows/__init__.py +0 -0
  457. control_plane_api/worker/workflows/agent_execution.py +909 -0
  458. control_plane_api/worker/workflows/scheduled_job_wrapper.py +332 -0
  459. control_plane_api/worker/workflows/team_execution.py +611 -0
  460. kubiya_control_plane_api-0.9.15.dist-info/METADATA +354 -0
  461. kubiya_control_plane_api-0.9.15.dist-info/RECORD +479 -0
  462. kubiya_control_plane_api-0.9.15.dist-info/WHEEL +5 -0
  463. kubiya_control_plane_api-0.9.15.dist-info/entry_points.txt +5 -0
  464. kubiya_control_plane_api-0.9.15.dist-info/licenses/LICENSE +676 -0
  465. kubiya_control_plane_api-0.9.15.dist-info/top_level.txt +3 -0
  466. scripts/__init__.py +1 -0
  467. scripts/migrations.py +39 -0
  468. scripts/seed_worker_queues.py +128 -0
  469. scripts/setup_agent_runtime.py +142 -0
  470. worker_internal/__init__.py +1 -0
  471. worker_internal/planner/__init__.py +1 -0
  472. worker_internal/planner/activities.py +1499 -0
  473. worker_internal/planner/agent_tools.py +197 -0
  474. worker_internal/planner/event_models.py +148 -0
  475. worker_internal/planner/event_publisher.py +67 -0
  476. worker_internal/planner/models.py +199 -0
  477. worker_internal/planner/retry_logic.py +134 -0
  478. worker_internal/planner/worker.py +300 -0
  479. 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
+ """