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