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,755 @@
1
+ """
2
+ State Transition Service
3
+
4
+ Provides intelligent state transition decisions for executions using an Agno AI agent.
5
+ Analyzes execution context and determines the appropriate next state with reasoning.
6
+ """
7
+
8
+ import os
9
+ import time
10
+ import asyncio
11
+ from typing import Dict, Any, Literal, Optional
12
+ from datetime import datetime, timezone
13
+ from pydantic import BaseModel, Field
14
+ import structlog
15
+
16
+ from agno.agent import Agent
17
+ from agno.models.litellm import LiteLLM
18
+ from control_plane_api.app.lib.state_transition_tools.execution_context import ExecutionContextTools
19
+ from control_plane_api.app.database import get_db
20
+ from control_plane_api.app.models.execution import Execution
21
+ from control_plane_api.app.models.execution_transition import ExecutionTransition
22
+ from sqlalchemy.orm import Session
23
+
24
+ logger = structlog.get_logger()
25
+
26
+
27
+ class StateTransitionDecision(BaseModel):
28
+ """
29
+ Structured output from the state transition AI agent
30
+ """
31
+
32
+ recommended_state: Literal["running", "waiting_for_input", "completed", "failed"] = Field(
33
+ description="The recommended state to transition to"
34
+ )
35
+
36
+ confidence: Literal["low", "medium", "high"] = Field(
37
+ description="Confidence level in this decision"
38
+ )
39
+
40
+ reasoning: str = Field(
41
+ description="Detailed explanation of why this state was chosen"
42
+ )
43
+
44
+ decision_factors: Dict[str, Any] = Field(
45
+ description="Key factors that influenced this decision",
46
+ default_factory=dict
47
+ )
48
+
49
+ should_continue_automatically: bool = Field(
50
+ description="Whether the execution should continue without user input",
51
+ default=False
52
+ )
53
+
54
+ estimated_user_action_needed: bool = Field(
55
+ description="Whether user action or input is likely needed",
56
+ default=False
57
+ )
58
+
59
+
60
+ class StateTransitionService:
61
+ """
62
+ Service for intelligent state transition decisions using Agno AI agent
63
+ """
64
+
65
+ def __init__(self, organization_id: Optional[str] = None):
66
+ """
67
+ Initialize state transition service
68
+
69
+ Args:
70
+ organization_id: Organization context for filtering
71
+ """
72
+ self.organization_id = organization_id
73
+
74
+ # Get LiteLLM configuration
75
+ self.litellm_api_url = (
76
+ os.getenv("LITELLM_API_URL")
77
+ or os.getenv("LITELLM_API_BASE")
78
+ or "https://llm-proxy.kubiya.ai"
79
+ ).strip()
80
+
81
+ self.litellm_api_key = os.getenv("LITELLM_API_KEY", "").strip()
82
+
83
+ if not self.litellm_api_key:
84
+ raise ValueError("LITELLM_API_KEY environment variable not set")
85
+
86
+ # Get model from env var or use default
87
+ self.model = os.getenv("STATE_TRANSITION_MODEL", "kubiya/claude-sonnet-4").strip()
88
+
89
+ # Get control plane URL for tools
90
+ self.control_plane_url = os.getenv("CONTROL_PLANE_API_URL", "http://localhost:8000")
91
+
92
+ logger.info(
93
+ "state_transition_service_initialized",
94
+ model=self.model,
95
+ litellm_api_url=self.litellm_api_url,
96
+ organization_id=organization_id,
97
+ )
98
+
99
+ def _create_transition_agent(self) -> Agent:
100
+ """
101
+ Create an Agno agent for state transition decisions
102
+
103
+ Returns:
104
+ Configured Agent instance
105
+ """
106
+ # Initialize context tools
107
+ context_tools = ExecutionContextTools(
108
+ base_url=self.control_plane_url,
109
+ organization_id=self.organization_id,
110
+ )
111
+
112
+ # Create agent with structured output
113
+ agent = Agent(
114
+ name="State Transition Analyzer",
115
+ role="Expert in analyzing execution states and determining optimal transitions",
116
+ model=LiteLLM(
117
+ id=f"openai/{self.model}",
118
+ api_base=self.litellm_api_url,
119
+ api_key=self.litellm_api_key,
120
+ ),
121
+ output_schema=StateTransitionDecision,
122
+ tools=[context_tools],
123
+ instructions=[
124
+ "You are an expert at analyzing execution states and determining optimal state transitions.",
125
+ "",
126
+ "**Your Task:**",
127
+ "Analyze the execution context and recommend the appropriate next state.",
128
+ "",
129
+ "**Available States:**",
130
+ "1. **completed**: Task is fully done",
131
+ " - finish_reason = 'stop' or 'end_turn'",
132
+ " - Response contains completion signals ('done', 'finished', 'completed', 'success')",
133
+ " - No pending tool calls or error conditions",
134
+ " - User's intent has been clearly satisfied",
135
+ " - No follow-up questions or clarifications needed",
136
+ "",
137
+ "2. **waiting_for_input**: Needs user input",
138
+ " - Asking questions or clarifications",
139
+ " - Ambiguous requirements need resolution",
140
+ " - Waiting for approval or feedback",
141
+ " - finish_reason = 'stop' but task not fully complete",
142
+ " - Agent explicitly asked user for input",
143
+ "",
144
+ "3. **failed**: Unrecoverable error",
145
+ " - finish_reason = 'error'",
146
+ " - Repeated tool failures (>3 consecutive failures)",
147
+ " - Error message indicates blocker (auth, permissions, not found)",
148
+ " - Cannot proceed without external intervention",
149
+ " - Use the check_error_recoverability tool to assess errors",
150
+ "",
151
+ "4. **running**: Continue automatically",
152
+ " - finish_reason = 'tool_use' (still actively working)",
153
+ " - Multi-step task in progress",
154
+ " - No user input needed yet",
155
+ " - Can make autonomous progress",
156
+ " - Agent is gathering information or executing tasks",
157
+ "",
158
+ "**Decision Process:**",
159
+ "1. Use get_execution_details() to understand the execution",
160
+ "2. Use get_recent_turns() to see the latest activity",
161
+ "3. Analyze the most recent turn's finish_reason",
162
+ "4. Check if there are errors with check_error_recoverability()",
163
+ "5. Review tool call patterns if needed with get_tool_call_patterns()",
164
+ "6. Make a confident decision based on all context",
165
+ "",
166
+ "**Important Guidelines:**",
167
+ "- Be decisive - don't overthink simple cases",
168
+ "- finish_reason='stop' usually means waiting_for_input or completed",
169
+ "- finish_reason='tool_use' usually means running (continue)",
170
+ "- finish_reason='error' usually means failed (unless recoverable)",
171
+ "- Look for completion signals in the response text",
172
+ "- If the agent asked a question, it's usually waiting_for_input",
173
+ "- If unsure between completed and waiting_for_input, prefer waiting_for_input (safer)",
174
+ "",
175
+ "**Output Requirements:**",
176
+ "- Provide clear, concise reasoning (2-4 sentences)",
177
+ "- Set confidence based on clarity of signals",
178
+ "- Include key decision factors (finish_reason, error status, completion signals, etc.)",
179
+ "- Be specific about why you chose this state",
180
+ ],
181
+ markdown=False,
182
+ add_history_to_context=False,
183
+ retries=2,
184
+ )
185
+
186
+ logger.info(
187
+ "state_transition_agent_created",
188
+ model=self.model,
189
+ tools_count=1,
190
+ )
191
+
192
+ return agent
193
+
194
+ async def analyze_and_transition(
195
+ self,
196
+ execution_id: str,
197
+ turn_number: int,
198
+ turn_data: Any,
199
+ ) -> StateTransitionDecision:
200
+ """
201
+ Analyze execution context and determine state transition
202
+
203
+ Args:
204
+ execution_id: The execution ID
205
+ turn_number: The turn number
206
+ turn_data: Turn metrics data
207
+
208
+ Returns:
209
+ StateTransitionDecision with recommendation and reasoning
210
+ """
211
+ start_time = time.time()
212
+
213
+ try:
214
+ logger.info(
215
+ "analyzing_state_transition",
216
+ execution_id=execution_id,
217
+ turn_number=turn_number,
218
+ finish_reason=turn_data.finish_reason if turn_data else None,
219
+ )
220
+
221
+ # Create agent
222
+ agent = self._create_transition_agent()
223
+
224
+ # Build analysis prompt
225
+ prompt = self._build_analysis_prompt(execution_id, turn_number, turn_data)
226
+
227
+ # Run agent (synchronous run in async wrapper)
228
+ response = await asyncio.to_thread(agent.run, prompt)
229
+
230
+ # Extract decision from response
231
+ decision = response.content if isinstance(response.content, StateTransitionDecision) else response.content
232
+
233
+ # Calculate decision time
234
+ decision_time_ms = int((time.time() - start_time) * 1000)
235
+
236
+ logger.info(
237
+ "state_transition_decision_made",
238
+ execution_id=execution_id,
239
+ turn_number=turn_number,
240
+ from_state="running",
241
+ to_state=decision.recommended_state,
242
+ confidence=decision.confidence,
243
+ decision_time_ms=decision_time_ms,
244
+ )
245
+
246
+ # Record transition in database
247
+ await self._record_transition(
248
+ execution_id=execution_id,
249
+ turn_number=turn_number,
250
+ from_state="running",
251
+ to_state=decision.recommended_state,
252
+ decision=decision,
253
+ decision_time_ms=decision_time_ms,
254
+ )
255
+
256
+ # Update execution status
257
+ await self._update_execution_status(
258
+ execution_id=execution_id,
259
+ new_status=decision.recommended_state,
260
+ )
261
+
262
+ return decision
263
+
264
+ except Exception as e:
265
+ decision_time_ms = int((time.time() - start_time) * 1000)
266
+
267
+ logger.error(
268
+ "state_transition_analysis_failed",
269
+ execution_id=execution_id,
270
+ turn_number=turn_number,
271
+ error=str(e),
272
+ decision_time_ms=decision_time_ms,
273
+ )
274
+
275
+ raise
276
+
277
+ def _build_analysis_prompt(
278
+ self,
279
+ execution_id: str,
280
+ turn_number: int,
281
+ turn_data: Any,
282
+ ) -> str:
283
+ """
284
+ Build the analysis prompt for the AI agent
285
+
286
+ Args:
287
+ execution_id: The execution ID
288
+ turn_number: The turn number
289
+ turn_data: Turn metrics data
290
+
291
+ Returns:
292
+ Formatted prompt string
293
+ """
294
+ finish_reason = turn_data.finish_reason if turn_data else "unknown"
295
+ error_message = turn_data.error_message if turn_data else None
296
+ response_preview = turn_data.response_preview if turn_data else None
297
+ tools_called = turn_data.tools_called_count if turn_data else 0
298
+
299
+ prompt = f"""
300
+ # State Transition Analysis Request
301
+
302
+ ## Execution Information
303
+ - Execution ID: {execution_id}
304
+ - Turn Number: {turn_number}
305
+ - Finish Reason: {finish_reason}
306
+ - Tools Called This Turn: {tools_called}
307
+
308
+ ## Recent Turn Details
309
+ """
310
+
311
+ if response_preview:
312
+ prompt += f"\n**Response Preview:**\n{response_preview[:500]}\n"
313
+
314
+ if error_message:
315
+ prompt += f"\n**Error Message:**\n{error_message[:300]}\n"
316
+
317
+ prompt += """
318
+
319
+ ## Your Task
320
+
321
+ Analyze this execution and determine the appropriate next state.
322
+
323
+ **Steps:**
324
+ 1. Call get_execution_details() to understand the execution context
325
+ 2. Call get_recent_turns() to see the recent activity pattern
326
+ 3. If there's an error, call check_error_recoverability() to assess it
327
+ 4. Based on all context, recommend the next state with clear reasoning
328
+
329
+ **Focus on:**
330
+ - The finish_reason is a critical signal
331
+ - Look for completion indicators in the response
332
+ - Check if the agent is asking questions
333
+ - Assess error recoverability if present
334
+ - Consider if the task can continue autonomously
335
+
336
+ Be decisive and provide a clear recommendation.
337
+ """
338
+
339
+ return prompt
340
+
341
+ async def _append_system_message_to_session(
342
+ self,
343
+ execution_id: str,
344
+ to_state: str,
345
+ reasoning: str,
346
+ confidence: str,
347
+ db: Session = None,
348
+ ) -> None:
349
+ """
350
+ Append a system message to the session history about the state transition
351
+
352
+ Args:
353
+ execution_id: The execution ID
354
+ to_state: The new state
355
+ reasoning: The AI reasoning for the transition
356
+ confidence: The confidence level
357
+ db: Optional database session (if already open)
358
+ """
359
+ await _append_system_message_to_session_helper(
360
+ db=db,
361
+ execution_id=execution_id,
362
+ to_state=to_state,
363
+ reasoning=reasoning,
364
+ confidence=confidence,
365
+ organization_id=self.organization_id,
366
+ )
367
+
368
+ async def _record_transition(
369
+ self,
370
+ execution_id: str,
371
+ turn_number: int,
372
+ from_state: str,
373
+ to_state: str,
374
+ decision: StateTransitionDecision,
375
+ decision_time_ms: int,
376
+ ) -> None:
377
+ """
378
+ Record state transition in database using SQLAlchemy
379
+
380
+ Args:
381
+ execution_id: The execution ID
382
+ turn_number: The turn number
383
+ from_state: The previous state
384
+ to_state: The new state
385
+ decision: The AI decision object
386
+ decision_time_ms: Time taken to make decision
387
+ """
388
+ try:
389
+ # Get database session in async context
390
+ from control_plane_api.app.database import get_session_local
391
+ SessionLocal = get_session_local()
392
+ db = SessionLocal()
393
+
394
+ try:
395
+ # Get organization from execution
396
+ execution = db.query(Execution).filter(Execution.id == execution_id).first()
397
+
398
+ if not execution:
399
+ logger.warning("execution_not_found_for_transition", execution_id=execution_id)
400
+ return
401
+
402
+ organization_id = execution.organization_id
403
+
404
+ # Create transition record
405
+ transition_record = ExecutionTransition(
406
+ organization_id=organization_id,
407
+ execution_id=execution_id,
408
+ turn_number=turn_number,
409
+ from_state=from_state,
410
+ to_state=to_state,
411
+ reasoning=decision.reasoning,
412
+ confidence=decision.confidence,
413
+ decision_factors=decision.decision_factors,
414
+ ai_model=self.model,
415
+ decision_time_ms=decision_time_ms,
416
+ is_manual_override=False,
417
+ )
418
+
419
+ db.add(transition_record)
420
+ db.commit()
421
+ db.refresh(transition_record)
422
+
423
+ logger.info(
424
+ "transition_recorded",
425
+ execution_id=execution_id,
426
+ turn_number=turn_number,
427
+ transition_id=str(transition_record.id),
428
+ )
429
+
430
+ # Add system message to session history
431
+ await self._append_system_message_to_session(
432
+ execution_id=execution_id,
433
+ to_state=to_state,
434
+ reasoning=decision.reasoning,
435
+ confidence=decision.confidence,
436
+ db=db,
437
+ )
438
+ finally:
439
+ db.close()
440
+
441
+ except Exception as e:
442
+ logger.error(
443
+ "record_transition_failed",
444
+ execution_id=execution_id,
445
+ error=str(e),
446
+ )
447
+ # Don't raise - recording failure shouldn't block state transition
448
+
449
+ async def _update_execution_status(
450
+ self,
451
+ execution_id: str,
452
+ new_status: str,
453
+ ) -> None:
454
+ """
455
+ Update execution status in database using SQLAlchemy
456
+
457
+ Args:
458
+ execution_id: The execution ID
459
+ new_status: The new status to set
460
+ """
461
+ try:
462
+ from control_plane_api.app.database import get_session_local
463
+ SessionLocal = get_session_local()
464
+ db = SessionLocal()
465
+
466
+ try:
467
+ execution = db.query(Execution).filter(Execution.id == execution_id).first()
468
+
469
+ if execution:
470
+ execution.status = new_status.lower()
471
+
472
+ # Add completed_at if transitioning to completed or failed
473
+ if new_status in ["completed", "failed"]:
474
+ execution.completed_at = datetime.now(timezone.utc)
475
+
476
+ db.commit()
477
+
478
+ logger.info(
479
+ "execution_status_updated",
480
+ execution_id=execution_id,
481
+ new_status=new_status,
482
+ )
483
+
484
+ # Push status event to Redis for live streaming
485
+ try:
486
+ from control_plane_api.app.lib.redis_client import get_redis_client
487
+ import json
488
+
489
+ redis_client = get_redis_client()
490
+ if redis_client:
491
+ status_event = {
492
+ "event_type": "status",
493
+ "status": new_status.lower(),
494
+ "execution_id": execution_id,
495
+ "source": "state_transition_service",
496
+ "timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
497
+ }
498
+
499
+ redis_key = f"execution:{execution_id}:events"
500
+
501
+ # Use asyncio.create_task for async Redis push
502
+ import asyncio
503
+ loop = asyncio.get_event_loop()
504
+ if loop.is_running():
505
+ asyncio.create_task(redis_client.lpush(redis_key, json.dumps(status_event)))
506
+ else:
507
+ loop.run_until_complete(redis_client.lpush(redis_key, json.dumps(status_event)))
508
+
509
+ logger.info(
510
+ "status_event_pushed_to_redis",
511
+ execution_id=execution_id,
512
+ status=new_status,
513
+ )
514
+ except Exception as redis_error:
515
+ logger.warning(
516
+ "redis_status_push_failed",
517
+ execution_id=execution_id,
518
+ status=new_status,
519
+ error=str(redis_error),
520
+ )
521
+ # Don't raise - Redis push failure shouldn't block state transition
522
+ else:
523
+ logger.warning(
524
+ "execution_not_found_for_status_update",
525
+ execution_id=execution_id,
526
+ new_status=new_status,
527
+ )
528
+ finally:
529
+ db.close()
530
+
531
+ except Exception as e:
532
+ logger.error(
533
+ "update_execution_status_failed",
534
+ execution_id=execution_id,
535
+ new_status=new_status,
536
+ error=str(e),
537
+ )
538
+ # Don't raise - status update failure shouldn't block the transition
539
+
540
+
541
+ async def update_execution_state_safe(
542
+ execution_id: str,
543
+ state: str,
544
+ reasoning: str,
545
+ ) -> None:
546
+ """
547
+ Safely update execution state with fallback reasoning using SQLAlchemy
548
+
549
+ Used when AI decision fails or times out.
550
+
551
+ Args:
552
+ execution_id: The execution ID
553
+ state: The state to set
554
+ reasoning: Why this fallback state was chosen
555
+ """
556
+ try:
557
+ from control_plane_api.app.database import get_session_local
558
+ SessionLocal = get_session_local()
559
+ db = SessionLocal()
560
+
561
+ try:
562
+ # Get execution
563
+ execution = db.query(Execution).filter(Execution.id == execution_id).first()
564
+
565
+ if not execution:
566
+ logger.warning("execution_not_found_for_safe_update", execution_id=execution_id)
567
+ return
568
+
569
+ # Update execution status
570
+ execution.status = state.lower()
571
+
572
+ if state in ["completed", "failed"]:
573
+ execution.completed_at = datetime.now(timezone.utc)
574
+
575
+ db.commit()
576
+
577
+ logger.info(
578
+ "fallback_state_update_committed",
579
+ execution_id=execution_id,
580
+ state=state,
581
+ )
582
+
583
+ # Push status event to Redis for live streaming
584
+ try:
585
+ from control_plane_api.app.lib.redis_client import get_redis_client
586
+ import json
587
+ import asyncio
588
+
589
+ redis_client = get_redis_client()
590
+ if redis_client:
591
+ status_event = {
592
+ "event_type": "status",
593
+ "status": state.lower(),
594
+ "execution_id": execution_id,
595
+ "source": "state_transition_fallback",
596
+ "timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
597
+ }
598
+
599
+ redis_key = f"execution:{execution_id}:events"
600
+
601
+ # Use asyncio for async Redis push
602
+ loop = asyncio.get_event_loop()
603
+ if loop.is_running():
604
+ asyncio.create_task(redis_client.lpush(redis_key, json.dumps(status_event)))
605
+ else:
606
+ loop.run_until_complete(redis_client.lpush(redis_key, json.dumps(status_event)))
607
+
608
+ logger.info(
609
+ "fallback_status_event_pushed_to_redis",
610
+ execution_id=execution_id,
611
+ status=state,
612
+ )
613
+ except Exception as redis_error:
614
+ logger.warning(
615
+ "fallback_redis_status_push_failed",
616
+ execution_id=execution_id,
617
+ status=state,
618
+ error=str(redis_error),
619
+ )
620
+ # Don't raise - Redis push failure shouldn't block fallback state transition
621
+
622
+ # Record fallback transition
623
+ transition_record = ExecutionTransition(
624
+ organization_id=execution.organization_id,
625
+ execution_id=execution_id,
626
+ turn_number=0, # Unknown turn number
627
+ from_state="running",
628
+ to_state=state,
629
+ reasoning=f"FALLBACK: {reasoning}",
630
+ confidence="low",
631
+ decision_factors={"fallback": True, "reason": reasoning},
632
+ ai_model="fallback",
633
+ decision_time_ms=0,
634
+ is_manual_override=False,
635
+ )
636
+
637
+ db.add(transition_record)
638
+ db.commit()
639
+
640
+ # Add system message to session history
641
+ await _append_system_message_to_session_helper(
642
+ db=db,
643
+ execution_id=execution_id,
644
+ to_state=state,
645
+ reasoning=f"FALLBACK: {reasoning}",
646
+ confidence="low",
647
+ organization_id=execution.organization_id,
648
+ )
649
+
650
+ logger.info(
651
+ "fallback_state_update",
652
+ execution_id=execution_id,
653
+ state=state,
654
+ reasoning=reasoning,
655
+ )
656
+ finally:
657
+ db.close()
658
+
659
+ except Exception as e:
660
+ logger.error(
661
+ "fallback_state_update_failed",
662
+ execution_id=execution_id,
663
+ state=state,
664
+ error=str(e),
665
+ )
666
+
667
+
668
+ async def _append_system_message_to_session_helper(
669
+ db: Session,
670
+ execution_id: str,
671
+ to_state: str,
672
+ reasoning: str,
673
+ confidence: str,
674
+ organization_id: Optional[str] = None,
675
+ ) -> None:
676
+ """
677
+ Helper function to append system message to session using SQLAlchemy (used by fallback too)
678
+
679
+ Args:
680
+ db: SQLAlchemy database session
681
+ execution_id: The execution ID
682
+ to_state: The new state
683
+ reasoning: The reasoning for the transition
684
+ confidence: The confidence level
685
+ organization_id: Optional organization ID for filtering
686
+ """
687
+ try:
688
+ # Check if Session model exists, if not skip (sessions may not be migrated yet)
689
+ try:
690
+ from control_plane_api.app.models.session import Session as SessionModel
691
+ except ImportError:
692
+ logger.debug(
693
+ "session_model_not_available",
694
+ execution_id=execution_id,
695
+ note="Skipping session message append - Session model not yet migrated"
696
+ )
697
+ return
698
+
699
+ # Get the current session
700
+ if organization_id:
701
+ session = db.query(SessionModel).filter(
702
+ SessionModel.execution_id == execution_id,
703
+ SessionModel.organization_id == organization_id
704
+ ).first()
705
+ else:
706
+ # Fallback to execution_id only if organization_id not provided (backward compatibility)
707
+ session = db.query(SessionModel).filter(SessionModel.execution_id == execution_id).first()
708
+
709
+ if not session:
710
+ logger.warning(
711
+ "session_not_found_for_transition_message",
712
+ execution_id=execution_id
713
+ )
714
+ return
715
+
716
+ messages = session.messages or []
717
+
718
+ # Create system message about the transition
719
+ state_emoji = {
720
+ "completed": "✅",
721
+ "waiting_for_input": "⏸️",
722
+ "failed": "❌",
723
+ "running": "▶️"
724
+ }.get(to_state, "🔄")
725
+
726
+ state_display = to_state.replace("_", " ").title()
727
+
728
+ system_message = {
729
+ "role": "system",
730
+ "content": f"{state_emoji} **State Transition: {state_display}**\n\n{reasoning}\n\n*Confidence: {confidence}*",
731
+ "timestamp": datetime.now(timezone.utc).isoformat(),
732
+ "message_id": f"{execution_id}_system_transition_{int(datetime.now(timezone.utc).timestamp() * 1000000)}",
733
+ }
734
+
735
+ # Append the system message
736
+ messages.append(system_message)
737
+
738
+ # Update the session
739
+ session.messages = messages
740
+ session.updated_at = datetime.now(timezone.utc)
741
+ db.commit()
742
+
743
+ logger.info(
744
+ "system_message_appended_to_session",
745
+ execution_id=execution_id,
746
+ to_state=to_state,
747
+ )
748
+
749
+ except Exception as e:
750
+ logger.error(
751
+ "append_system_message_failed",
752
+ execution_id=execution_id,
753
+ error=str(e),
754
+ )
755
+ # Don't raise - message failure shouldn't block state transition