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,273 @@
1
+ """
2
+ Entity name-to-UUID resolution helpers for task planning.
3
+
4
+ This module provides functions to resolve entity names (agents, teams, worker queues,
5
+ environments) to their actual UUIDs. This is needed because the LLM planning workflow
6
+ often returns entity names in the entity_id field instead of UUIDs, which causes
7
+ PostgreSQL errors when trying to execute plans.
8
+ """
9
+ from typing import Optional
10
+ from sqlalchemy.orm import Session
11
+ from uuid import UUID
12
+ import structlog
13
+
14
+ from control_plane_api.app.models.agent import Agent
15
+ from control_plane_api.app.models.team import Team
16
+ from control_plane_api.app.models.worker import WorkerQueue
17
+ from control_plane_api.app.models.environment import Environment
18
+
19
+ logger = structlog.get_logger(__name__)
20
+
21
+
22
+ async def resolve_agent_name_to_uuid(
23
+ name_or_id: str,
24
+ organization_id: str,
25
+ db: Session
26
+ ) -> Optional[str]:
27
+ """
28
+ Resolve agent name or ID to UUID.
29
+
30
+ Args:
31
+ name_or_id: Agent name or UUID string
32
+ organization_id: Organization ID
33
+ db: Database session
34
+
35
+ Returns:
36
+ UUID string if found, None otherwise
37
+ """
38
+ # First check if it's already a valid UUID
39
+ try:
40
+ uuid_obj = UUID(name_or_id)
41
+ # It's a valid UUID, verify it exists
42
+ agent = db.query(Agent).filter(
43
+ Agent.id == str(uuid_obj),
44
+ Agent.organization_id == organization_id
45
+ ).first()
46
+
47
+ if agent:
48
+ logger.info("agent_uuid_valid", agent_id=str(uuid_obj))
49
+ return str(uuid_obj)
50
+ except (ValueError, AttributeError):
51
+ # Not a UUID, treat as name
52
+ pass
53
+
54
+ # Look up by name
55
+ agent = db.query(Agent).filter(
56
+ Agent.name == name_or_id,
57
+ Agent.organization_id == organization_id
58
+ ).first()
59
+
60
+ if agent:
61
+ logger.info("agent_resolved", name=name_or_id, uuid=str(agent.id))
62
+ return str(agent.id)
63
+
64
+ logger.warning("agent_not_found", name_or_id=name_or_id)
65
+ return None
66
+
67
+
68
+ async def resolve_team_name_to_uuid(
69
+ name_or_id: str,
70
+ organization_id: str,
71
+ db: Session
72
+ ) -> Optional[str]:
73
+ """
74
+ Resolve team name or ID to UUID.
75
+
76
+ Args:
77
+ name_or_id: Team name or UUID string
78
+ organization_id: Organization ID
79
+ db: Database session
80
+
81
+ Returns:
82
+ UUID string if found, None otherwise
83
+ """
84
+ # First check if it's already a valid UUID
85
+ try:
86
+ uuid_obj = UUID(name_or_id)
87
+ # It's a valid UUID, verify it exists
88
+ team = db.query(Team).filter(
89
+ Team.id == str(uuid_obj),
90
+ Team.organization_id == organization_id
91
+ ).first()
92
+
93
+ if team:
94
+ logger.info("team_uuid_valid", team_id=str(uuid_obj))
95
+ return str(uuid_obj)
96
+ except (ValueError, AttributeError):
97
+ # Not a UUID, treat as name
98
+ pass
99
+
100
+ # Look up by name
101
+ team = db.query(Team).filter(
102
+ Team.name == name_or_id,
103
+ Team.organization_id == organization_id
104
+ ).first()
105
+
106
+ if team:
107
+ logger.info("team_resolved", name=name_or_id, uuid=str(team.id))
108
+ return str(team.id)
109
+
110
+ logger.warning("team_not_found", name_or_id=name_or_id)
111
+ return None
112
+
113
+
114
+ async def resolve_worker_queue_name_to_uuid(
115
+ name_or_id: str,
116
+ organization_id: str,
117
+ db: Session
118
+ ) -> Optional[str]:
119
+ """
120
+ Resolve worker queue name or ID to UUID.
121
+
122
+ Args:
123
+ name_or_id: Worker queue name or UUID string
124
+ organization_id: Organization ID
125
+ db: Database session
126
+
127
+ Returns:
128
+ UUID string if found, None otherwise
129
+ """
130
+ # First check if it's already a valid UUID
131
+ try:
132
+ uuid_obj = UUID(name_or_id)
133
+ wq = db.query(WorkerQueue).filter(
134
+ WorkerQueue.id == str(uuid_obj),
135
+ WorkerQueue.organization_id == organization_id,
136
+ WorkerQueue.ephemeral == False, # Exclude ephemeral queues
137
+ ~WorkerQueue.name.startswith('local-exec') # Exclude local-exec queues
138
+ ).first()
139
+
140
+ if wq:
141
+ logger.info("worker_queue_uuid_valid", wq_id=str(uuid_obj))
142
+ return str(uuid_obj)
143
+ except (ValueError, AttributeError):
144
+ pass
145
+
146
+ # Look up by name
147
+ wq = db.query(WorkerQueue).filter(
148
+ WorkerQueue.name == name_or_id,
149
+ WorkerQueue.organization_id == organization_id,
150
+ WorkerQueue.ephemeral == False, # Exclude ephemeral queues
151
+ ~WorkerQueue.name.startswith('local-exec') # Exclude local-exec queues
152
+ ).first()
153
+
154
+ if wq:
155
+ logger.info("worker_queue_resolved", name=name_or_id, uuid=str(wq.id))
156
+ return str(wq.id)
157
+
158
+ logger.warning("worker_queue_not_found", name_or_id=name_or_id)
159
+ return None
160
+
161
+
162
+ async def resolve_environment_name_to_uuid(
163
+ name_or_id: str,
164
+ organization_id: str,
165
+ db: Session
166
+ ) -> Optional[str]:
167
+ """
168
+ Resolve environment name or ID to UUID.
169
+
170
+ Args:
171
+ name_or_id: Environment name or UUID string
172
+ organization_id: Organization ID
173
+ db: Database session
174
+
175
+ Returns:
176
+ UUID string if found, None otherwise
177
+ """
178
+ # First check if it's already a valid UUID
179
+ try:
180
+ uuid_obj = UUID(name_or_id)
181
+ env = db.query(Environment).filter(
182
+ Environment.id == str(uuid_obj),
183
+ Environment.organization_id == organization_id
184
+ ).first()
185
+
186
+ if env:
187
+ logger.info("environment_uuid_valid", env_id=str(uuid_obj))
188
+ return str(uuid_obj)
189
+ except (ValueError, AttributeError):
190
+ pass
191
+
192
+ # Look up by name
193
+ env = db.query(Environment).filter(
194
+ Environment.name == name_or_id,
195
+ Environment.organization_id == organization_id
196
+ ).first()
197
+
198
+ if env:
199
+ logger.info("environment_resolved", name=name_or_id, uuid=str(env.id))
200
+ return str(env.id)
201
+
202
+ logger.warning("environment_not_found", name_or_id=name_or_id)
203
+ return None
204
+
205
+
206
+ async def resolve_plan_entities(
207
+ plan_response,
208
+ organization_id: str,
209
+ db: Session
210
+ ) -> None:
211
+ """
212
+ Resolve all entity names in a plan response to UUIDs (in-place).
213
+
214
+ This modifies the plan_response object to replace entity names with UUIDs
215
+ in the recommended_execution section.
216
+
217
+ Args:
218
+ plan_response: TaskPlanResponse object
219
+ organization_id: Organization ID
220
+ db: Database session
221
+ """
222
+ if not plan_response.recommended_execution:
223
+ logger.info("no_recommended_execution_to_resolve")
224
+ return
225
+
226
+ rec_exec = plan_response.recommended_execution
227
+
228
+ # Resolve main entity (agent or team)
229
+ if rec_exec.entity_id:
230
+ entity_type = rec_exec.entity_type
231
+ name_or_id = rec_exec.entity_id
232
+
233
+ if entity_type == "agent":
234
+ uuid = await resolve_agent_name_to_uuid(name_or_id, organization_id, db)
235
+ else: # team
236
+ uuid = await resolve_team_name_to_uuid(name_or_id, organization_id, db)
237
+
238
+ if uuid:
239
+ rec_exec.entity_id = uuid
240
+ logger.info("entity_resolved",
241
+ entity_type=entity_type,
242
+ original=name_or_id,
243
+ resolved=uuid)
244
+ else:
245
+ error_msg = f"{entity_type.capitalize()} '{name_or_id}' not found in organization"
246
+ logger.error("entity_resolution_failed",
247
+ entity_type=entity_type,
248
+ name_or_id=name_or_id,
249
+ error=error_msg)
250
+ # Raise error instead of failing silently
251
+ raise ValueError(f"Entity resolution failed: {error_msg}. Please ensure the {entity_type} exists in your organization.")
252
+
253
+ # Resolve worker queue
254
+ if rec_exec.recommended_worker_queue_id:
255
+ wq_uuid = await resolve_worker_queue_name_to_uuid(
256
+ rec_exec.recommended_worker_queue_id,
257
+ organization_id,
258
+ db
259
+ )
260
+ if wq_uuid:
261
+ rec_exec.recommended_worker_queue_id = wq_uuid
262
+ logger.info("worker_queue_resolved", resolved=wq_uuid)
263
+
264
+ # Resolve environment
265
+ if rec_exec.recommended_environment_id:
266
+ env_uuid = await resolve_environment_name_to_uuid(
267
+ rec_exec.recommended_environment_id,
268
+ organization_id,
269
+ db
270
+ )
271
+ if env_uuid:
272
+ rec_exec.recommended_environment_id = env_uuid
273
+ logger.info("environment_resolved", resolved=env_uuid)
@@ -0,0 +1,293 @@
1
+ """
2
+ Task Planning Helper Functions
3
+ """
4
+ from typing import List, Dict, Optional
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+ from sqlalchemy.orm import Session
8
+ import structlog
9
+ import os
10
+
11
+ logger = structlog.get_logger()
12
+
13
+ # Debug configuration - set via environment variable
14
+ DEBUG_SAVE_PROMPTS = os.getenv("DEBUG_SAVE_PLANNING_PROMPTS", "false").lower() in ("true", "1", "yes")
15
+ DEBUG_PROMPTS_DIR = os.getenv("DEBUG_PROMPTS_DIR", "./debug_prompts")
16
+
17
+ # Refinement mode instructions template
18
+ REFINEMENT_INSTRUCTIONS = """
19
+ ## REFINEMENT MODE - Critical Instructions
20
+
21
+ You are refining an existing plan (Iteration #{iteration}). Follow these rules:
22
+
23
+ 1. **PRESERVE Unchanged Portions**:
24
+ - Keep ALL parts of the previous plan that the user didn't ask to change
25
+ - Maintain the same agent/team assignments unless specifically requested to change
26
+ - Preserve task IDs, dependencies, and structure where possible
27
+
28
+ 2. **ONLY Modify What Was Requested**:
29
+ - Read the user feedback carefully
30
+ - Change ONLY the specific aspects mentioned in the feedback
31
+ - Don't over-optimize or change things that work
32
+
33
+ 3. **EXPLAIN Changes**:
34
+ - In the summary, briefly mention what changed from the previous iteration
35
+ - Reference why changes were made based on user feedback
36
+ - Keep the reasoning from the previous plan if still valid
37
+
38
+ 4. **Incremental Updates**:
39
+ - If user says "change task 3 to use a different agent", only update task 3
40
+ - If user says "add a testing step", add it without changing other tasks
41
+ - Think minimal, surgical changes - not full replanning
42
+
43
+ 5. **Context Awareness**:
44
+ - Use the conversation history above to understand the full context
45
+ - Reference previous decisions and build upon them
46
+ - Maintain consistency with earlier iterations
47
+ """
48
+
49
+
50
+ def make_json_serializable(obj):
51
+ """
52
+ Recursively convert datetime objects to ISO format strings for JSON serialization
53
+
54
+ Args:
55
+ obj: Object to make JSON serializable (dict, list, or primitive)
56
+
57
+ Returns:
58
+ JSON-serializable version of the object
59
+ """
60
+ if isinstance(obj, datetime):
61
+ return obj.isoformat()
62
+ elif isinstance(obj, dict):
63
+ return {key: make_json_serializable(value) for key, value in obj.items()}
64
+ elif isinstance(obj, list):
65
+ return [make_json_serializable(item) for item in obj]
66
+ else:
67
+ return obj
68
+
69
+
70
+ def save_planning_prompt_debug(prompt: str, iteration: int = 1, task_desc: str = "") -> Optional[str]:
71
+ """
72
+ Save planning prompt to file for debugging if DEBUG_SAVE_PLANNING_PROMPTS is enabled
73
+
74
+ Args:
75
+ prompt: The planning prompt to save
76
+ iteration: Planning iteration number
77
+ task_desc: Short task description for filename
78
+
79
+ Returns:
80
+ Path to saved file or None if debug is disabled
81
+ """
82
+ if not DEBUG_SAVE_PROMPTS:
83
+ return None
84
+
85
+ try:
86
+ # Create debug directory if it doesn't exist
87
+ debug_dir = Path(DEBUG_PROMPTS_DIR)
88
+ debug_dir.mkdir(parents=True, exist_ok=True)
89
+
90
+ # Generate filename with timestamp
91
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3] # Include milliseconds
92
+
93
+ # Sanitize task description for filename
94
+ task_slug = "".join(c if c.isalnum() or c in ("-", "_") else "_" for c in task_desc[:50])
95
+ if not task_slug:
96
+ task_slug = "unknown_task"
97
+
98
+ filename = f"planning_prompt_{timestamp}_iter{iteration}_{task_slug}.txt"
99
+ filepath = debug_dir / filename
100
+
101
+ # Write prompt to file
102
+ with open(filepath, "w", encoding="utf-8") as f:
103
+ f.write(f"# Planning Prompt Debug\n")
104
+ f.write(f"# Generated: {datetime.now().isoformat()}\n")
105
+ f.write(f"# Iteration: {iteration}\n")
106
+ f.write(f"# Task: {task_desc}\n")
107
+ f.write(f"# {'=' * 80}\n\n")
108
+ f.write(prompt)
109
+
110
+ logger.info(
111
+ "saved_planning_prompt_debug",
112
+ filepath=str(filepath),
113
+ prompt_length=len(prompt),
114
+ iteration=iteration
115
+ )
116
+
117
+ return str(filepath)
118
+
119
+ except Exception as e:
120
+ logger.error("failed_to_save_planning_prompt_debug", error=str(e))
121
+ return None
122
+
123
+
124
+ def _extract_organization_id_from_token(api_token: Optional[str]) -> Optional[str]:
125
+ """
126
+ Extract organization ID from JWT token
127
+
128
+ Args:
129
+ api_token: JWT token string
130
+
131
+ Returns:
132
+ Organization ID if found, None otherwise
133
+ """
134
+ if not api_token:
135
+ return None
136
+
137
+ try:
138
+ import jwt
139
+ # Decode without verification to get organization
140
+ decoded = jwt.decode(api_token, options={"verify_signature": False})
141
+ org_id = decoded.get("organization") or decoded.get("org") or decoded.get("org_id")
142
+
143
+ if org_id:
144
+ logger.debug("extracted_org_from_token", organization_id=org_id)
145
+
146
+ return org_id
147
+ except Exception as e:
148
+ logger.warning("failed_to_decode_token", error=str(e))
149
+ return None
150
+
151
+
152
+ def _get_organization_id_fallback(agents: List, teams: List) -> Optional[str]:
153
+ """
154
+ Get organization ID from agents or teams as fallback
155
+
156
+ Args:
157
+ agents: List of agent objects
158
+ teams: List of team objects
159
+
160
+ Returns:
161
+ Organization ID if found, None otherwise
162
+ """
163
+ if agents and len(agents) > 0:
164
+ return getattr(agents[0], "organization_id", None)
165
+ elif teams and len(teams) > 0:
166
+ return getattr(teams[0], "organization_id", None)
167
+ return None
168
+
169
+
170
+ async def _discover_agents(db: Session, organization_id: Optional[str], limit: int = 50) -> List[dict]:
171
+ """
172
+ Discover available agents from database
173
+
174
+ Args:
175
+ db: Database session
176
+ organization_id: Organization ID for filtering
177
+ limit: Maximum number of agents to discover
178
+
179
+ Returns:
180
+ List of discovered agents as dicts
181
+ """
182
+ try:
183
+ from control_plane_api.app.lib.planning_tools import AgentsContextTools
184
+ agents_tools = AgentsContextTools(db=db, organization_id=organization_id)
185
+ discovered_agents = await agents_tools.list_agents(limit=limit)
186
+ logger.info("discovered_agents_before_planning", count=len(discovered_agents))
187
+ return discovered_agents
188
+ except Exception as e:
189
+ logger.error("failed_to_discover_agents", error=str(e))
190
+ return []
191
+
192
+
193
+ async def _discover_teams(db: Session, organization_id: Optional[str], limit: int = 50) -> List[dict]:
194
+ """
195
+ Discover available teams from database
196
+
197
+ Args:
198
+ db: Database session
199
+ organization_id: Organization ID for filtering
200
+ limit: Maximum number of teams to discover
201
+
202
+ Returns:
203
+ List of discovered teams as dicts
204
+ """
205
+ try:
206
+ from control_plane_api.app.lib.planning_tools import TeamsContextTools
207
+ teams_tools = TeamsContextTools(db=db, organization_id=organization_id)
208
+ discovered_teams = await teams_tools.list_teams(limit=limit)
209
+ logger.info("discovered_teams_before_planning", count=len(discovered_teams))
210
+ return discovered_teams
211
+ except Exception as e:
212
+ logger.error("failed_to_discover_teams", error=str(e))
213
+ return []
214
+
215
+
216
+ def _prepare_resources_for_planning(
217
+ request_agents: Optional[List],
218
+ request_teams: Optional[List],
219
+ discovered_agents: List[dict],
220
+ discovered_teams: List[dict]
221
+ ) -> tuple[List[dict], List[dict]]:
222
+ """
223
+ Prepare agents and teams for planning by converting to JSON-serializable format
224
+
225
+ Args:
226
+ request_agents: Agents provided in request
227
+ request_teams: Teams provided in request
228
+ discovered_agents: Agents discovered from database
229
+ discovered_teams: Teams discovered from database
230
+
231
+ Returns:
232
+ Tuple of (agents_to_use, teams_to_use) as JSON-serializable dicts
233
+ """
234
+ # Prepare agents
235
+ agents_to_use = []
236
+ if request_agents:
237
+ agents_to_use = [a.model_dump() for a in request_agents]
238
+ elif discovered_agents:
239
+ agents_to_use = discovered_agents
240
+
241
+ # Prepare teams
242
+ teams_to_use = []
243
+ if request_teams:
244
+ teams_to_use = [t.model_dump() for t in request_teams]
245
+ elif discovered_teams:
246
+ teams_to_use = discovered_teams
247
+
248
+ # Make JSON serializable
249
+ agents_to_use = make_json_serializable(agents_to_use)
250
+ teams_to_use = make_json_serializable(teams_to_use)
251
+
252
+ # Log agent data for debugging
253
+ if agents_to_use:
254
+ logger.info("agent_data_for_planner",
255
+ agent_count=len(agents_to_use),
256
+ first_agent_id=agents_to_use[0].get('id') if agents_to_use else None,
257
+ first_agent_name=agents_to_use[0].get('name') if agents_to_use else None,
258
+ has_skills=len(agents_to_use[0].get('skills', [])) if agents_to_use else 0,
259
+ has_exec_env=bool(agents_to_use[0].get('execution_environment')) if agents_to_use else False)
260
+
261
+ return agents_to_use, teams_to_use
262
+
263
+
264
+ def _infer_agent_specialty(name: str, description: Optional[str]) -> str:
265
+ """
266
+ Infer agent specialty from name and description for better context.
267
+ """
268
+ name_lower = name.lower()
269
+ desc_lower = (description or "").lower()
270
+
271
+ # Check for specific specialties
272
+ if "devops" in name_lower or "devops" in desc_lower:
273
+ return "Infrastructure, deployments, cloud operations, monitoring"
274
+ elif "security" in name_lower or "ciso" in name_lower or "security" in desc_lower:
275
+ return "Security audits, compliance, vulnerability scanning, IAM"
276
+ elif "data" in name_lower or "analytics" in desc_lower:
277
+ return "Data analysis, ETL, reporting, database operations"
278
+ elif "backend" in name_lower or "api" in desc_lower:
279
+ return "API development, backend services, database integration"
280
+ elif "frontend" in name_lower or "ui" in desc_lower:
281
+ return "UI development, React/Vue/Angular, responsive design"
282
+ elif "full" in name_lower or "fullstack" in name_lower:
283
+ return "End-to-end development, frontend + backend + infrastructure"
284
+ elif "test" in name_lower or "qa" in desc_lower:
285
+ return "Testing, quality assurance, test automation"
286
+ else:
287
+ return "General automation, scripting, API integration, cloud operations"
288
+
289
+
290
+ def format_sse_message(event: str, data: dict) -> str:
291
+ """Format data as Server-Sent Event message"""
292
+ import json
293
+ return f"event: {event}\ndata: {json.dumps(data)}\n\n"