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,1866 @@
1
+ """
2
+ Team executor service with runtime abstraction support.
3
+
4
+ This version supports both Agno-based teams and Claude Code SDK runtime teams.
5
+ """
6
+
7
+ from typing import Dict, Any, Optional, List
8
+ from datetime import datetime, timezone
9
+ import structlog
10
+ import asyncio
11
+ import os
12
+ import time
13
+
14
+ from control_plane_api.worker.control_plane_client import ControlPlaneClient
15
+ from control_plane_api.worker.services.session_service import SessionService
16
+ from control_plane_api.worker.services.cancellation_manager import CancellationManager
17
+ from control_plane_api.worker.services.analytics_service import AnalyticsService
18
+ from control_plane_api.worker.services.runtime_analytics import submit_runtime_analytics
19
+ from control_plane_api.worker.runtimes import (
20
+ RuntimeFactory,
21
+ RuntimeType,
22
+ RuntimeExecutionContext,
23
+ )
24
+ from control_plane_api.worker.utils.streaming_utils import StreamingHelper
25
+ from control_plane_api.app.lib.templating.types import TemplateContext
26
+ from control_plane_api.app.lib.templating.resolver import resolve_templates
27
+ from control_plane_api.worker.utils.logging_config import sanitize_value
28
+
29
+ logger = structlog.get_logger()
30
+
31
+
32
+ class TeamExecutorServiceV2:
33
+ """
34
+ Service for executing teams using runtime abstraction.
35
+
36
+ This service orchestrates team execution by:
37
+ 1. Loading session history
38
+ 2. Determining runtime type (Agno or Claude Code)
39
+ 3. Delegating execution to appropriate runtime
40
+ 4. Persisting session after execution
41
+
42
+ For Claude Code runtime:
43
+ - Team leader uses Claude Code SDK with Task tool
44
+ - Team members are executed as subagents via Task tool
45
+ - Streaming and tool hooks supported
46
+
47
+ For Agno runtime:
48
+ - Uses existing Agno Team implementation
49
+ - Full multi-agent coordination support
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ control_plane: ControlPlaneClient,
55
+ session_service: SessionService,
56
+ cancellation_manager: CancellationManager,
57
+ ):
58
+ """
59
+ Initialize the team executor service.
60
+
61
+ Args:
62
+ control_plane: Control Plane API client
63
+ session_service: Session management service
64
+ cancellation_manager: Execution cancellation manager
65
+ """
66
+ self.control_plane = control_plane
67
+ self.session_service = session_service
68
+ self.cancellation_manager = cancellation_manager
69
+ self.runtime_factory = RuntimeFactory()
70
+ self.streaming_helper = None # Will be set during execution for tool message tracking
71
+
72
+ # Initialize analytics service for tracking LLM usage, tool calls, etc.
73
+ control_plane_url = os.getenv("CONTROL_PLANE_URL", "http://localhost:8000")
74
+ api_key = os.getenv("KUBIYA_API_KEY", "")
75
+ self.analytics_service = AnalyticsService(control_plane_url, api_key)
76
+
77
+ async def execute(self, input: Any) -> Dict[str, Any]:
78
+ """
79
+ Execute a team using the configured runtime.
80
+
81
+ Args:
82
+ input: TeamExecutionInput with execution details
83
+
84
+ Returns:
85
+ Dict with response, usage, success flag, runtime_type, etc.
86
+ """
87
+ execution_id = input.execution_id
88
+
89
+ logger.info(
90
+ "team_workflow_start",
91
+ execution_id=execution_id,
92
+ team_id=input.team_id,
93
+ organization_id=input.organization_id,
94
+ agent_count=len(input.agents),
95
+ session_id=input.session_id,
96
+ prompt_preview=input.prompt[:100] + "..." if len(input.prompt) > 100 else input.prompt
97
+ )
98
+
99
+ try:
100
+ # Capture timestamp at start of execution for accurate user message timestamp
101
+ from datetime import datetime, timezone
102
+ user_message_timestamp = datetime.now(timezone.utc).isoformat()
103
+
104
+ # STEP 1: Load session history
105
+ logger.info("loading_session_history", session_id=input.session_id)
106
+ session_history = self.session_service.load_session(
107
+ execution_id=execution_id,
108
+ session_id=input.session_id
109
+ )
110
+
111
+ if session_history:
112
+ logger.info("session_history_loaded", message_count=len(session_history))
113
+ else:
114
+ logger.info("starting_new_conversation", session_id=input.session_id)
115
+
116
+ # STEP 2: Determine runtime type
117
+ # Priority: input.runtime_type (if explicitly set) > team_config.runtime > "default"
118
+ runtime_type_str = getattr(input, "runtime_type", "default")
119
+ team_config = getattr(input, "team_config", {}) or {}
120
+
121
+ # Debug: Log what we received
122
+ logger.debug("runtime_type_input", input_runtime_type=runtime_type_str)
123
+ logger.debug("team_config_keys", keys=list(team_config.keys()))
124
+ if "runtime" in team_config:
125
+ logger.debug("team_config_runtime", runtime=team_config.get('runtime'))
126
+
127
+ # If runtime_type is still "default", check team_config.runtime
128
+ if runtime_type_str == "default" and "runtime" in team_config:
129
+ runtime_type_str = team_config.get("runtime", "default")
130
+ logger.debug("using_team_config_runtime", runtime=runtime_type_str)
131
+
132
+ runtime_type = self.runtime_factory.parse_runtime_type(runtime_type_str)
133
+
134
+ logger.info(
135
+ "runtime_type_selected",
136
+ runtime_type=runtime_type.value,
137
+ framework=self._get_framework_name(runtime_type)
138
+ )
139
+
140
+ logger.info(
141
+ "runtime_selected",
142
+ execution_id=execution_id[:8],
143
+ runtime=runtime_type.value,
144
+ )
145
+
146
+ # STEP 3: Publish user message to stream before execution
147
+ # This ensures chronological ordering in UI
148
+ turn_number = len(session_history) // 2 + 1
149
+ user_message_id = f"{execution_id}_user_{turn_number}"
150
+ self.control_plane.publish_event(
151
+ execution_id=execution_id,
152
+ event_type="message",
153
+ data={
154
+ "role": "user",
155
+ "content": input.prompt,
156
+ "timestamp": user_message_timestamp,
157
+ "message_id": user_message_id,
158
+ "user_id": input.user_id,
159
+ "user_name": getattr(input, "user_name", None),
160
+ "user_email": getattr(input, "user_email", None),
161
+ "user_avatar": getattr(input, "user_avatar", None),
162
+ }
163
+ )
164
+ logger.debug("user_message_published", message_id=user_message_id)
165
+
166
+ # STEP 4: Detect execution mode and route appropriately
167
+ execution_mode = self._detect_execution_mode(input, runtime_type)
168
+
169
+ logger.info(
170
+ "execution_mode_detected",
171
+ execution_id=execution_id[:8],
172
+ mode=execution_mode,
173
+ runtime_type=runtime_type.value,
174
+ )
175
+
176
+ # Route based on detected execution mode
177
+ if execution_mode == "agent_runtime_native":
178
+ result = await self._execute_via_agent_runtime(input, session_history)
179
+ elif execution_mode in ("claude_code_native", "claude_code_task"):
180
+ result = await self._execute_with_claude_code(input, session_history, runtime_type)
181
+ else: # agno
182
+ # Fall back to Agno-based team execution
183
+ from control_plane_api.worker.services.team_executor import TeamExecutorService
184
+
185
+ agno_executor = TeamExecutorService(
186
+ self.control_plane,
187
+ self.session_service,
188
+ self.cancellation_manager
189
+ )
190
+ return await agno_executor.execute(input)
191
+
192
+ logger.info("team_execution_completed")
193
+ logger.info("execution_response_length", length=len(result['response']))
194
+ logger.info("execution_success_status", success=result['success'])
195
+
196
+ logger.info(
197
+ "team_execution_completed",
198
+ execution_id=execution_id[:8],
199
+ success=result["success"],
200
+ response_length=len(result["response"]),
201
+ )
202
+
203
+ # STEP 4: Persist session
204
+ if result["success"] and result["response"]:
205
+ logger.info("persisting_session_history")
206
+
207
+ turn_number = len(session_history) // 2 + 1
208
+
209
+ # Finalize streaming to transition to post-tool phase
210
+ if self.streaming_helper:
211
+ self.streaming_helper.finalize_streaming()
212
+
213
+ # Build user message
214
+ user_message = {
215
+ "role": "user",
216
+ "content": input.prompt,
217
+ "timestamp": user_message_timestamp, # Use timestamp from start
218
+ "message_id": f"{execution_id}_user_{turn_number}",
219
+ "user_id": input.user_id,
220
+ "user_name": getattr(input, "user_name", None),
221
+ "user_email": getattr(input, "user_email", None),
222
+ }
223
+
224
+ # Build structured messages using StreamingHelper
225
+ # This properly splits assistant messages around tool usage
226
+ if self.streaming_helper:
227
+ new_messages = self.streaming_helper.build_structured_messages(
228
+ execution_id=execution_id,
229
+ turn_number=turn_number,
230
+ user_message=user_message,
231
+ )
232
+
233
+ logger.debug("structured_messages_built", count=len(new_messages))
234
+ if self.streaming_helper.has_any_tools:
235
+ logger.debug("assistant_message_split_into_phases")
236
+ assistant_parts = self.streaming_helper.get_assistant_message_parts()
237
+ for part in assistant_parts:
238
+ logger.debug("message_part", phase=part['phase'], length=len(part['content']))
239
+ else:
240
+ # Fallback if no streaming helper (shouldn't happen)
241
+ assistant_message_timestamp = datetime.now(timezone.utc).isoformat()
242
+ new_messages = [
243
+ user_message,
244
+ {
245
+ "role": "assistant",
246
+ "content": result["response"],
247
+ "timestamp": assistant_message_timestamp,
248
+ "message_id": f"{execution_id}_assistant_{turn_number}",
249
+ },
250
+ ]
251
+
252
+ # Combine with previous history
253
+ complete_session = session_history + new_messages
254
+
255
+ # CRITICAL: Deduplicate messages by message_id AND content to prevent duplicates
256
+ # Use session_service.deduplicate_messages() which has enhanced two-level deduplication
257
+ original_count = len(complete_session)
258
+ complete_session = self.session_service.deduplicate_messages(complete_session)
259
+
260
+ # CRITICAL: Sort by timestamp to ensure chronological order
261
+ # Tool messages happen DURING streaming, so they need to be interleaved with user/assistant messages
262
+ complete_session.sort(key=lambda msg: msg.get("timestamp", ""))
263
+ logger.info("messages_deduplicated", before=original_count, after=len(complete_session))
264
+
265
+ success = self.session_service.persist_session(
266
+ execution_id=execution_id,
267
+ session_id=input.session_id or execution_id,
268
+ user_id=input.user_id,
269
+ messages=complete_session,
270
+ metadata={
271
+ "team_id": input.team_id,
272
+ "organization_id": input.organization_id,
273
+ "runtime_type": runtime_type.value,
274
+ "turn_count": len(complete_session),
275
+ "member_count": len(input.agents),
276
+ },
277
+ )
278
+
279
+ if success:
280
+ logger.info("session_persisted", total_messages=len(complete_session))
281
+ else:
282
+ logger.warning("session_persistence_failed")
283
+
284
+ # STEP 5: Print usage metrics
285
+ if result.get("usage"):
286
+ logger.info("token_usage_summary",
287
+ prompt_tokens=result['usage'].get('prompt_tokens', 0),
288
+ completion_tokens=result['usage'].get('completion_tokens', 0),
289
+ total_tokens=result['usage'].get('total_tokens', 0))
290
+
291
+ # Banner removed - using structured logging
292
+ logger.info("team_execution_end")
293
+ return result
294
+
295
+ except Exception as e:
296
+ logger.error("team_execution_failed")
297
+ # Banner removed - using structured logging
298
+ logger.error("execution_error", error=str(e))
299
+ logger.error(
300
+ "team_execution_failed",
301
+ execution_id=execution_id[:8],
302
+ error=str(e)
303
+ )
304
+
305
+ return {
306
+ "success": False,
307
+ "error": str(e),
308
+ "model": input.model_id,
309
+ "usage": {},
310
+ "finish_reason": "error",
311
+ "runtime_type": runtime_type_str if "runtime_type_str" in locals() else "unknown",
312
+ }
313
+
314
+ async def _execute_with_claude_code(
315
+ self, input: Any, session_history: List[Dict], runtime_type: RuntimeType
316
+ ) -> Dict[str, Any]:
317
+ """
318
+ Execute team using Claude Code SDK.
319
+
320
+ Strategy (V2 with native subagents):
321
+ - If all members are Claude Code → use native SDK subagents (optimal)
322
+ - Otherwise → use Task tool delegation (current implementation)
323
+
324
+ Args:
325
+ input: TeamExecutionInput
326
+ session_history: Previous conversation messages
327
+
328
+ Returns:
329
+ Result dict
330
+ """
331
+ execution_id = input.execution_id
332
+
333
+ # Check if we can use native subagent support
334
+ if input.agents and self._all_members_are_claude_code(input.agents):
335
+ logger.info("native_subagent_path_detected")
336
+ logger.info(
337
+ "using_native_subagents",
338
+ execution_id=execution_id[:8],
339
+ member_count=len(input.agents),
340
+ )
341
+ return await self._execute_claude_code_team_with_native_subagents(input, session_history)
342
+
343
+ # Fall back to Task tool delegation for mixed or non-Claude Code teams
344
+ logger.info("creating_claude_code_team_leader")
345
+ logger.info(
346
+ "using_task_tool_delegation",
347
+ execution_id=execution_id[:8],
348
+ member_count=len(input.agents) if input.agents else 0,
349
+ )
350
+
351
+ # Create runtime instance
352
+ runtime = self.runtime_factory.create_runtime(
353
+ runtime_type=RuntimeType.CLAUDE_CODE,
354
+ control_plane_client=self.control_plane,
355
+ cancellation_manager=self.cancellation_manager,
356
+ )
357
+
358
+ logger.info("runtime_created", info=runtime.get_runtime_info())
359
+
360
+ # STEP 1: Build team context for system prompt
361
+ team_context = self._build_team_context(input.agents)
362
+
363
+ system_prompt = f"""You are the team leader coordinating a team of specialized AI agents.
364
+
365
+ Your team members:
366
+ {team_context}
367
+
368
+ When you need a team member to perform a task:
369
+ 1. Use the Task tool to delegate work to the appropriate agent
370
+ 2. Provide clear instructions in the subagent_type parameter
371
+ 3. Wait for their response before continuing
372
+ 4. Synthesize the results into a cohesive answer
373
+
374
+ Your goal is to coordinate the team effectively to solve the user's request.
375
+ """
376
+
377
+ logger.info("team_context", member_count=len(input.agents))
378
+ # Logged in team_context above
379
+ for agent in input.agents:
380
+ logger.debug("team_member_info", name=agent.get('name'), role=agent.get('role', 'No role specified')[:60])
381
+ # Empty print removed
382
+
383
+ # STEP 2: Get skills for team leader (must include Task tool)
384
+ logger.info("fetching_skills_from_control_plane")
385
+ skills = []
386
+ try:
387
+ # Get skills from first agent (team leader)
388
+ if input.agents:
389
+ leader_id = input.agents[0].get("id")
390
+ if leader_id:
391
+ skill_configs = self.control_plane.get_skills(leader_id)
392
+ if skill_configs:
393
+ logger.info("skills_resolved", count=len(skill_configs))
394
+ else:
395
+ skill_configs = []
396
+
397
+ # Always include built-in context_graph_search skill
398
+ builtin_skill_types = {'context_graph_search'}
399
+ existing_skill_types = {cfg.get('type') for cfg in skill_configs}
400
+
401
+ for builtin_type in builtin_skill_types:
402
+ if builtin_type not in existing_skill_types:
403
+ builtin_config = {
404
+ 'name': builtin_type,
405
+ 'type': builtin_type,
406
+ 'enabled': True,
407
+ 'configuration': {}
408
+ }
409
+ skill_configs.append(builtin_config)
410
+ logger.info("builtin_skill_auto_included", skill_type=builtin_type)
411
+
412
+ if skill_configs:
413
+ from control_plane_api.worker.services.skill_factory import SkillFactory
414
+
415
+ # Create factory instance for the current runtime
416
+ logger.debug("creating_skill_factory", runtime_type=runtime_type.value)
417
+ skill_factory = SkillFactory(runtime_type=runtime_type.value) # Use actual runtime
418
+ skill_factory.initialize()
419
+
420
+ skills = skill_factory.create_skills_from_list(skill_configs, execution_id=input.execution_id)
421
+
422
+ if skills:
423
+ logger.info("skills_instantiated", count=len(skills))
424
+ except Exception as e:
425
+ logger.warning("skill_fetch_error", error=str(e))
426
+ logger.error("skill_fetch_error", error=str(e))
427
+
428
+ # Always ensure Task tool is available for delegation
429
+ task_skill = {"type": "task", "name": "Task"}
430
+ if task_skill not in skills:
431
+ skills.append(task_skill)
432
+ logger.info("task_tool_added_for_coordination")
433
+
434
+ # STEP 3: Inject environment variables into MCP servers (runtime-agnostic)
435
+ logger.info("fetching_resolved_environment")
436
+ from control_plane_api.worker.activities.runtime_activities import inject_env_vars_into_mcp_servers
437
+ team_config = getattr(input, "team_config", {}) or {}
438
+ mcp_servers_with_env = inject_env_vars_into_mcp_servers(
439
+ mcp_servers=getattr(input, "mcp_servers", None),
440
+ agent_config=team_config,
441
+ runtime_config=team_config.get("runtime_config"),
442
+ control_plane_client=self.control_plane,
443
+ team_id=input.team_id,
444
+ )
445
+
446
+ # STEP 4: Compile system prompt templates
447
+ logger.info("compiling_system_prompt_templates")
448
+ compiled_system_prompt = system_prompt
449
+ if system_prompt:
450
+ try:
451
+ # Build template context with available variables
452
+ template_context = TemplateContext(
453
+ variables=getattr(input, "user_metadata", None) or {},
454
+ secrets=team_config.get("secrets", {}),
455
+ env_vars=dict(os.environ), # Make all env vars available
456
+ # Context graph API configuration for {{.graph.node-id}} templates
457
+ graph_api_base=os.environ.get("CONTEXT_GRAPH_API_BASE", "https://graph.kubiya.ai"),
458
+ graph_api_key=os.environ.get("KUBIYA_API_KEY"),
459
+ graph_org_id=os.environ.get("KUBIYA_ORG_ID") or input.organization_id
460
+ )
461
+
462
+ # Resolve templates in system prompt
463
+ compiled_system_prompt = resolve_templates(
464
+ system_prompt,
465
+ template_context,
466
+ strict=False, # Don't fail on missing variables
467
+ skip_on_error=True # Return original if compilation fails
468
+ )
469
+
470
+ if compiled_system_prompt != system_prompt:
471
+ logger.info(
472
+ "team_system_prompt_templates_compiled",
473
+ original_length=len(system_prompt),
474
+ compiled_length=len(compiled_system_prompt)
475
+ )
476
+ logger.info("system_prompt_templates_compiled")
477
+ else:
478
+ logger.info("no_templates_in_system_prompt")
479
+
480
+ except Exception as e:
481
+ logger.warning(
482
+ "team_system_prompt_template_compilation_failed",
483
+ error=str(e),
484
+ exc_info=True
485
+ )
486
+ logger.warning("system_prompt_compilation_failed", error=str(e))
487
+ # Use original system prompt if compilation fails
488
+ compiled_system_prompt = system_prompt
489
+
490
+ # STEP 4.5: Enhance system prompt with complete execution environment context
491
+ execution_context_info = self._build_execution_context_info(
492
+ runtime_config=team_config.get("runtime_config", {}),
493
+ skills=skills,
494
+ mcp_servers=mcp_servers_with_env,
495
+ team_config=team_config
496
+ )
497
+ if execution_context_info:
498
+ if compiled_system_prompt:
499
+ compiled_system_prompt = compiled_system_prompt + "\n\n" + execution_context_info
500
+ else:
501
+ compiled_system_prompt = execution_context_info
502
+ logger.info("system_prompt_enhanced_with_context")
503
+
504
+ # STEP 5: Build execution context
505
+ logger.info("building_execution_context")
506
+ context = RuntimeExecutionContext(
507
+ execution_id=execution_id,
508
+ agent_id=input.team_id, # Use team_id as agent_id
509
+ organization_id=input.organization_id,
510
+ prompt=input.prompt,
511
+ system_prompt=compiled_system_prompt,
512
+ conversation_history=session_history,
513
+ model_id=input.model_id,
514
+ model_config=getattr(input, "model_config", None),
515
+ agent_config=team_config,
516
+ skills=skills,
517
+ mcp_servers=mcp_servers_with_env, # Use MCP servers with injected env vars
518
+ user_metadata=getattr(input, "user_metadata", None),
519
+ runtime_config=team_config.get("runtime_config"),
520
+ )
521
+ logger.info("execution_context_ready")
522
+
523
+ # STEP 5: Execute via runtime with streaming
524
+ logger.info("executing_team_via_claude_code")
525
+
526
+ # Track turn start time for analytics
527
+ turn_start_time = time.time()
528
+ turn_number = len(session_history) // 2 + 1 # Approximate turn number
529
+
530
+ # Create streaming helper for tracking tool messages (used in both streaming and non-streaming)
531
+ self.streaming_helper = StreamingHelper(
532
+ control_plane_client=self.control_plane, execution_id=input.execution_id
533
+ )
534
+
535
+ if runtime.supports_streaming():
536
+ result = await self._execute_streaming(runtime, context, input, self.streaming_helper)
537
+ else:
538
+ exec_result = await runtime.execute(context)
539
+ from datetime import datetime, timezone
540
+ result = {
541
+ "success": exec_result.success,
542
+ "response": exec_result.response,
543
+ "response_timestamp": datetime.now(timezone.utc).isoformat(),
544
+ "usage": exec_result.usage,
545
+ "model": exec_result.model or input.model_id,
546
+ "finish_reason": exec_result.finish_reason or "stop",
547
+ "tool_messages": exec_result.tool_messages or [],
548
+ "runtime_type": "claude_code",
549
+ "error": exec_result.error,
550
+ "team_member_count": len(input.agents),
551
+ }
552
+
553
+ # Track turn end time
554
+ turn_end_time = time.time()
555
+
556
+ # Submit analytics (non-blocking, fire-and-forget)
557
+ if result.get("success") and result.get("usage"):
558
+ try:
559
+ # Convert result dict to RuntimeExecutionResult for analytics
560
+ from runtimes.base import RuntimeExecutionResult
561
+ runtime_result = RuntimeExecutionResult(
562
+ response=result["response"],
563
+ usage=result["usage"],
564
+ success=result["success"],
565
+ finish_reason=result.get("finish_reason", "stop"),
566
+ model=result.get("model"),
567
+ tool_messages=result.get("tool_messages", []),
568
+ error=result.get("error"),
569
+ )
570
+
571
+ # Submit analytics in the background (doesn't block execution)
572
+ await submit_runtime_analytics(
573
+ result=runtime_result,
574
+ execution_id=execution_id,
575
+ turn_number=turn_number,
576
+ turn_start_time=turn_start_time,
577
+ turn_end_time=turn_end_time,
578
+ analytics_service=self.analytics_service,
579
+ )
580
+ logger.info(
581
+ "team_analytics_submitted",
582
+ execution_id=execution_id[:8],
583
+ tokens=result["usage"].get("total_tokens", 0),
584
+ )
585
+ except Exception as analytics_error:
586
+ # Analytics failures should not break execution
587
+ logger.warning(
588
+ "team_analytics_submission_failed",
589
+ execution_id=execution_id[:8],
590
+ error=str(analytics_error),
591
+ )
592
+
593
+ return result
594
+
595
+ async def _execute_streaming(
596
+ self, runtime, context: RuntimeExecutionContext, input: Any, streaming_helper: StreamingHelper
597
+ ) -> Dict[str, Any]:
598
+ """
599
+ Execute with streaming and publish events to Control Plane.
600
+
601
+ Args:
602
+ runtime: Runtime instance
603
+ context: Execution context
604
+ input: Original input for additional metadata
605
+ streaming_helper: StreamingHelper instance for tracking events
606
+
607
+ Returns:
608
+ Result dict
609
+ """
610
+ # streaming_helper is now passed as parameter instead of created here
611
+ from temporalio import activity
612
+
613
+ accumulated_response = ""
614
+ final_result = None
615
+
616
+ # Define event callback for publishing to Control Plane
617
+ def event_callback(event: Dict):
618
+ """Callback to publish events to Control Plane SSE"""
619
+ event_type = event.get("type")
620
+
621
+ if event_type == "content_chunk":
622
+ # Publish content chunk
623
+ streaming_helper.publish_content_chunk(
624
+ content=event.get("content", ""),
625
+ message_id=event.get("message_id", context.execution_id),
626
+ )
627
+ elif event_type == "tool_start":
628
+ # Publish tool start
629
+ streaming_helper.publish_tool_start(
630
+ tool_name=event.get("tool_name"),
631
+ tool_execution_id=event.get("tool_execution_id"),
632
+ tool_args=event.get("tool_args", {}),
633
+ source="team_leader",
634
+ )
635
+ elif event_type == "tool_complete":
636
+ # Publish tool completion
637
+ streaming_helper.publish_tool_complete(
638
+ tool_name=event.get("tool_name"),
639
+ tool_execution_id=event.get("tool_execution_id"),
640
+ status=event.get("status", "success"),
641
+ output=event.get("output"),
642
+ error=event.get("error"),
643
+ source="team_leader",
644
+ )
645
+
646
+ # Stream execution
647
+ # Note: Temporal will raise asyncio.CancelledError when workflow cancels
648
+ # No need to check explicitly - cancellation happens automatically
649
+ async for chunk in runtime.stream_execute(context, event_callback):
650
+ if chunk.response:
651
+ accumulated_response += chunk.response
652
+ # Streaming output handled by response - removed print
653
+
654
+ # Keep final result for metadata
655
+ if chunk.usage or chunk.finish_reason:
656
+ final_result = chunk
657
+
658
+ # Empty print removed # New line after streaming
659
+
660
+ # Return final result with accumulated response
661
+ from datetime import datetime, timezone
662
+ response_timestamp = datetime.now(timezone.utc).isoformat()
663
+
664
+ if final_result:
665
+ return {
666
+ "success": final_result.success,
667
+ "response": accumulated_response,
668
+ "response_timestamp": response_timestamp,
669
+ "usage": final_result.usage,
670
+ "model": final_result.model or input.model_id,
671
+ "finish_reason": final_result.finish_reason or "stop",
672
+ "tool_messages": final_result.tool_messages or [],
673
+ "runtime_type": "claude_code",
674
+ "error": final_result.error,
675
+ "team_member_count": len(input.agents),
676
+ }
677
+ else:
678
+ return {
679
+ "success": True,
680
+ "response": accumulated_response,
681
+ "response_timestamp": response_timestamp,
682
+ "usage": {},
683
+ "model": input.model_id,
684
+ "finish_reason": "stop",
685
+ "tool_messages": [],
686
+ "runtime_type": "claude_code",
687
+ "team_member_count": len(input.agents),
688
+ }
689
+
690
+ async def _execute_claude_code_team_with_native_subagents(
691
+ self, input: Any, session_history: List[Dict]
692
+ ) -> Dict[str, Any]:
693
+ """
694
+ Execute Claude Code team using SDK's native subagent support.
695
+
696
+ This is the optimal path when:
697
+ - Leader runtime = claude_code
698
+ - All member runtimes = claude_code
699
+
700
+ Args:
701
+ input: TeamExecutionInput
702
+ session_history: Previous conversation messages
703
+
704
+ Returns:
705
+ Result dict
706
+ """
707
+ execution_id = input.execution_id
708
+
709
+ logger.info("using_native_sdk_subagents")
710
+
711
+ # STEP 1: Build agents dictionary for SDK
712
+ agents_config = {}
713
+ failed_members = []
714
+ skipped_members = []
715
+
716
+ for member in input.agents:
717
+ member_id = member.get('id')
718
+ member_name = member.get('name', 'Unknown')
719
+
720
+ if not member_id:
721
+ logger.warning(
722
+ "member_missing_id_skipped",
723
+ execution_id=execution_id[:8],
724
+ member_name=member_name,
725
+ )
726
+ logger.warning("skipping_member_no_id", member_name=member_name)
727
+ skipped_members.append(member_name)
728
+ continue
729
+
730
+ # Fetch full member configuration from Control Plane
731
+ try:
732
+ member_full_config = self.control_plane.get_agent(member_id)
733
+
734
+ if not member_full_config:
735
+ logger.warning(
736
+ "member_config_not_found",
737
+ execution_id=execution_id[:8],
738
+ member_id=member_id,
739
+ member_name=member_name,
740
+ )
741
+ logger.warning("member_config_not_found", member_name=member_name, member_id=member_id)
742
+ failed_members.append(member_name)
743
+ continue
744
+
745
+ # Map skills to tools
746
+ skill_ids = member_full_config.get('skill_ids', [])
747
+ mapped_tools = self._map_skills_to_claude_tools(skill_ids, member_name)
748
+
749
+ # Convert to Claude Code agent format
750
+ agents_config[member_id] = {
751
+ 'description': f"{member.get('role', member.get('name'))}. Use for: {member.get('capabilities', '')}",
752
+ 'prompt': member_full_config.get('system_prompt', ''),
753
+ 'tools': mapped_tools,
754
+ 'model': self._map_model_to_sdk_format(member_full_config.get('model_id', 'inherit')),
755
+ }
756
+
757
+ logger.info("member_configured", member_name=member_name, model=agents_config[member_id]['model'], tools_count=len(agents_config[member_id]['tools']))
758
+
759
+ logger.info(
760
+ "native_subagent_registered",
761
+ execution_id=execution_id[:8],
762
+ member_name=member_name,
763
+ member_id=member_id,
764
+ model=agents_config[member_id]['model'],
765
+ tool_count=len(agents_config[member_id]['tools']),
766
+ skill_count=len(skill_ids),
767
+ )
768
+ except Exception as e:
769
+ logger.error(
770
+ "failed_to_load_member_config",
771
+ execution_id=execution_id[:8],
772
+ member_id=member_id,
773
+ member_name=member_name,
774
+ error=str(e),
775
+ error_type=type(e).__name__,
776
+ )
777
+ logger.error("member_load_failed", member_name=member_name, member_id=member_id, error=str(e)[:100])
778
+ failed_members.append(member_name)
779
+ continue
780
+
781
+ # Validate that we have at least one configured member
782
+ if not agents_config:
783
+ error_msg = f"No team members could be configured. Failed: {failed_members}, Skipped: {skipped_members}"
784
+ logger.error(
785
+ "no_members_configured_for_native_subagents",
786
+ execution_id=execution_id[:8],
787
+ total_members=len(input.agents),
788
+ failed_count=len(failed_members),
789
+ skipped_count=len(skipped_members),
790
+ )
791
+ logger.error("configuration_error", error=error_msg)
792
+ raise ValueError(error_msg)
793
+
794
+ logger.info("native_subagents_configured", count=len(agents_config))
795
+ if failed_members:
796
+ logger.warning('some_members_failed_to_configure', failed_members=failed_members)
797
+ logger.warning(
798
+ "some_members_failed_to_configure",
799
+ execution_id=execution_id[:8],
800
+ failed_members=failed_members,
801
+ configured_count=len(agents_config),
802
+ )
803
+ if skipped_members:
804
+ logger.warning('some_members_skipped_missing_id', skipped_members=skipped_members)
805
+ # Empty print removed
806
+
807
+ # STEP 2: Build team leader system prompt
808
+ system_prompt = self._build_team_leader_prompt(input.agents)
809
+
810
+ # STEP 3: Get team configuration
811
+ team_config = getattr(input, "team_config", {}) or {}
812
+
813
+ # STEP 4: Inject environment variables into MCP servers
814
+ logger.info("fetching_resolved_environment")
815
+ from control_plane_api.worker.activities.runtime_activities import inject_env_vars_into_mcp_servers
816
+ mcp_servers_with_env = inject_env_vars_into_mcp_servers(
817
+ mcp_servers=getattr(input, "mcp_servers", None),
818
+ agent_config=team_config,
819
+ runtime_config=team_config.get("runtime_config"),
820
+ control_plane_client=self.control_plane,
821
+ team_id=input.team_id,
822
+ )
823
+
824
+ # AUTO-INCLUDE BUILT-IN MCP SERVERS FOR ALL SUBAGENTS
825
+ # Ensure context-graph-search is available to all team members
826
+ if not mcp_servers_with_env:
827
+ mcp_servers_with_env = {}
828
+
829
+ if 'context-graph-search' not in mcp_servers_with_env:
830
+ # Add built-in context-graph-search MCP server
831
+ from control_plane_api.worker.services.skill_factory import SkillFactory
832
+ skill_factory = SkillFactory(runtime_type="claude_code")
833
+ skill_factory.initialize()
834
+
835
+ # Create context-graph-search skill config
836
+ builtin_skill_config = {
837
+ 'name': 'context_graph_search',
838
+ 'type': 'context_graph_search',
839
+ 'enabled': True,
840
+ 'configuration': {}
841
+ }
842
+
843
+ # Create skill and convert to MCP server
844
+ try:
845
+ skills = skill_factory.create_skills_from_list([builtin_skill_config], execution_id=execution_id)
846
+ if skills:
847
+ # Build MCP servers from skills
848
+ from control_plane_api.worker.runtimes.claude_code import build_mcp_servers
849
+ builtin_mcp_servers = build_mcp_servers(skills, execution_id)
850
+ if builtin_mcp_servers and 'context-graph-search' in builtin_mcp_servers:
851
+ mcp_servers_with_env['context-graph-search'] = builtin_mcp_servers['context-graph-search']
852
+ logger.info("builtin_mcp_server_auto_included", server="context-graph-search")
853
+ logger.info(
854
+ "context_graph_search_mcp_auto_included",
855
+ execution_id=execution_id[:8],
856
+ )
857
+ except Exception as e:
858
+ logger.warning(
859
+ "failed_to_auto_include_context_graph_search_mcp",
860
+ execution_id=execution_id[:8],
861
+ error=str(e),
862
+ )
863
+
864
+ # STEP 5: Compile system prompt templates
865
+ logger.info("compiling_system_prompt_templates")
866
+ compiled_system_prompt = system_prompt
867
+ if system_prompt:
868
+ try:
869
+ # Build template context with available variables
870
+ template_context = TemplateContext(
871
+ variables=getattr(input, "user_metadata", None) or {},
872
+ secrets=team_config.get("secrets", {}),
873
+ env_vars=dict(os.environ), # Make all env vars available
874
+ # Context graph API configuration for {{.graph.node-id}} templates
875
+ graph_api_base=os.environ.get("CONTEXT_GRAPH_API_BASE", "https://graph.kubiya.ai"),
876
+ graph_api_key=os.environ.get("KUBIYA_API_KEY"),
877
+ graph_org_id=os.environ.get("KUBIYA_ORG_ID") or input.organization_id
878
+ )
879
+
880
+ # Resolve templates in system prompt
881
+ compiled_system_prompt = resolve_templates(
882
+ system_prompt,
883
+ template_context,
884
+ strict=False, # Don't fail on missing variables
885
+ skip_on_error=True # Return original if compilation fails
886
+ )
887
+
888
+ if compiled_system_prompt != system_prompt:
889
+ logger.info(
890
+ "native_team_system_prompt_templates_compiled",
891
+ original_length=len(system_prompt),
892
+ compiled_length=len(compiled_system_prompt)
893
+ )
894
+ logger.info("system_prompt_templates_compiled")
895
+ else:
896
+ logger.info("no_templates_in_system_prompt")
897
+
898
+ except Exception as e:
899
+ logger.warning(
900
+ "native_team_system_prompt_template_compilation_failed",
901
+ error=str(e),
902
+ exc_info=True
903
+ )
904
+ logger.warning("system_prompt_compilation_failed", error=str(e))
905
+ # Use original system prompt if compilation fails
906
+ compiled_system_prompt = system_prompt
907
+
908
+ # STEP 5.5: Enhance system prompt with complete execution environment context
909
+ execution_context_info = self._build_execution_context_info(
910
+ runtime_config=team_config.get("runtime_config", {}),
911
+ skills=[], # Native subagents path doesn't use leader skills
912
+ mcp_servers=mcp_servers_with_env,
913
+ team_config=team_config
914
+ )
915
+ if execution_context_info:
916
+ if compiled_system_prompt:
917
+ compiled_system_prompt = compiled_system_prompt + "\n\n" + execution_context_info
918
+ else:
919
+ compiled_system_prompt = execution_context_info
920
+ logger.info("system_prompt_enhanced_with_context")
921
+
922
+ # STEP 6: Build leader context with agents config
923
+ logger.info("building_execution_context_with_native_subagents")
924
+ context = RuntimeExecutionContext(
925
+ execution_id=execution_id,
926
+ agent_id=input.team_id,
927
+ organization_id=input.organization_id,
928
+ prompt=input.prompt,
929
+ system_prompt=compiled_system_prompt,
930
+ conversation_history=session_history,
931
+ model_id=input.model_id,
932
+ model_config=getattr(input, "model_config", None),
933
+ agent_config={
934
+ **team_config,
935
+ 'runtime_config': {
936
+ 'agents': agents_config # Pass to Claude Code SDK
937
+ }
938
+ },
939
+ skills=[], # Leader doesn't need extra skills, subagents have their own
940
+ mcp_servers=mcp_servers_with_env,
941
+ user_metadata=getattr(input, "user_metadata", None),
942
+ runtime_config=team_config.get("runtime_config"),
943
+ )
944
+ logger.info("execution_context_ready_with_native_subagents")
945
+
946
+ # STEP 6: Create runtime and execute
947
+ runtime = self.runtime_factory.create_runtime(
948
+ runtime_type=RuntimeType.CLAUDE_CODE,
949
+ control_plane_client=self.control_plane,
950
+ cancellation_manager=self.cancellation_manager,
951
+ )
952
+
953
+ logger.info("executing_with_native_sdk_subagents")
954
+
955
+ # Track turn start time for analytics
956
+ turn_start_time = time.time()
957
+ turn_number = len(session_history) // 2 + 1
958
+
959
+ # Create streaming helper for tracking tool messages
960
+ self.streaming_helper = StreamingHelper(
961
+ control_plane_client=self.control_plane, execution_id=input.execution_id
962
+ )
963
+
964
+ # Execute - SDK handles subagent routing automatically!
965
+ if runtime.supports_streaming():
966
+ result = await self._execute_streaming(runtime, context, input, self.streaming_helper)
967
+ else:
968
+ exec_result = await runtime.execute(context)
969
+ from datetime import datetime, timezone
970
+ result = {
971
+ "success": exec_result.success,
972
+ "response": exec_result.response,
973
+ "response_timestamp": datetime.now(timezone.utc).isoformat(),
974
+ "usage": exec_result.usage,
975
+ "model": exec_result.model or input.model_id,
976
+ "finish_reason": exec_result.finish_reason or "stop",
977
+ "tool_messages": exec_result.tool_messages or [],
978
+ "runtime_type": "claude_code",
979
+ "error": exec_result.error,
980
+ "team_member_count": len(input.agents),
981
+ }
982
+
983
+ # Track turn end time
984
+ turn_end_time = time.time()
985
+
986
+ # Submit analytics
987
+ if result.get("success") and result.get("usage"):
988
+ try:
989
+ from runtimes.base import RuntimeExecutionResult
990
+ runtime_result = RuntimeExecutionResult(
991
+ response=result["response"],
992
+ usage=result["usage"],
993
+ success=result["success"],
994
+ finish_reason=result.get("finish_reason", "stop"),
995
+ model=result.get("model"),
996
+ tool_messages=result.get("tool_messages", []),
997
+ error=result.get("error"),
998
+ )
999
+
1000
+ await submit_runtime_analytics(
1001
+ result=runtime_result,
1002
+ execution_id=execution_id,
1003
+ turn_number=turn_number,
1004
+ turn_start_time=turn_start_time,
1005
+ turn_end_time=turn_end_time,
1006
+ analytics_service=self.analytics_service,
1007
+ )
1008
+ logger.info(
1009
+ "native_subagent_team_analytics_submitted",
1010
+ execution_id=execution_id[:8],
1011
+ tokens=result["usage"].get("total_tokens", 0),
1012
+ )
1013
+ except Exception as analytics_error:
1014
+ logger.warning(
1015
+ "native_subagent_team_analytics_failed",
1016
+ execution_id=execution_id[:8],
1017
+ error=str(analytics_error),
1018
+ )
1019
+
1020
+ return result
1021
+
1022
+ def _map_skills_to_claude_tools(self, skill_ids: List[str], member_name: str = None) -> List[str]:
1023
+ """
1024
+ Map skill IDs to Claude Code tool names.
1025
+
1026
+ Args:
1027
+ skill_ids: List of skill IDs from agent config
1028
+ member_name: Optional member name for logging context
1029
+
1030
+ Returns:
1031
+ List of Claude Code tool names
1032
+ """
1033
+ if not skill_ids:
1034
+ logger.info(
1035
+ "no_skills_for_member_using_defaults",
1036
+ member_name=member_name or "unknown",
1037
+ default_tools=['Read', 'Write', 'Bash'],
1038
+ )
1039
+ return ['Read', 'Write', 'Bash']
1040
+
1041
+ # Fetch skill configurations
1042
+ tools = set()
1043
+ unmapped_skills = []
1044
+ failed_skills = []
1045
+ skill_type_counts = {}
1046
+
1047
+ for skill_id in skill_ids:
1048
+ try:
1049
+ skill_config = self.control_plane.get_skill(skill_id)
1050
+ if not skill_config:
1051
+ logger.warning(
1052
+ "skill_config_not_found",
1053
+ skill_id=skill_id,
1054
+ member_name=member_name or "unknown",
1055
+ )
1056
+ failed_skills.append(skill_id)
1057
+ continue
1058
+
1059
+ skill_type = skill_config.get('type', '').lower()
1060
+ skill_name = skill_config.get('name', skill_id)
1061
+
1062
+ # Track skill types for analytics
1063
+ skill_type_counts[skill_type] = skill_type_counts.get(skill_type, 0) + 1
1064
+
1065
+ # Map skill types to Claude Code tool names
1066
+ mapped = True
1067
+ if skill_type in ['file_system', 'filesystem', 'file']:
1068
+ tools.update(['Read', 'Write', 'Edit', 'Glob'])
1069
+ elif skill_type in ['shell', 'bash', 'command']:
1070
+ tools.add('Bash')
1071
+ elif skill_type in ['web', 'http', 'api']:
1072
+ tools.update(['WebFetch', 'WebSearch'])
1073
+ elif skill_type in ['data_visualization', 'visualization', 'plotting']:
1074
+ tools.update(['Read', 'Write']) # Needs read for data access, write for output
1075
+ elif skill_type in ['python', 'code', 'scripting']:
1076
+ tools.add('Bash') # Python via bash
1077
+ elif skill_type in ['grep', 'search', 'find']:
1078
+ tools.update(['Grep', 'Glob'])
1079
+ elif skill_type in ['task', 'workflow', 'delegation']:
1080
+ tools.add('Task')
1081
+ elif skill_type in ['planning', 'todo']:
1082
+ tools.update(['TodoWrite', 'Task'])
1083
+ elif skill_type in ['notebook', 'jupyter']:
1084
+ tools.update(['Read', 'Write', 'NotebookEdit'])
1085
+ elif skill_type in ['docker', 'container']:
1086
+ tools.add('Bash') # Docker commands via bash
1087
+ elif skill_type in ['git', 'version_control']:
1088
+ tools.add('Bash') # Git commands via bash
1089
+ elif skill_type in ['database', 'sql']:
1090
+ tools.add('Bash') # SQL commands via bash
1091
+ else:
1092
+ # Unknown skill type - log it for future mapping
1093
+ mapped = False
1094
+ unmapped_skills.append((skill_name, skill_type))
1095
+ logger.info(
1096
+ "unmapped_skill_type_encountered",
1097
+ skill_id=skill_id,
1098
+ skill_name=skill_name,
1099
+ skill_type=skill_type,
1100
+ member_name=member_name or "unknown",
1101
+ note="Consider adding mapping for this skill type",
1102
+ )
1103
+
1104
+ if mapped:
1105
+ logger.debug(
1106
+ "skill_mapped_successfully",
1107
+ skill_id=skill_id,
1108
+ skill_name=skill_name,
1109
+ skill_type=skill_type,
1110
+ member_name=member_name or "unknown",
1111
+ )
1112
+
1113
+ except Exception as e:
1114
+ logger.warning(
1115
+ "failed_to_map_skill",
1116
+ skill_id=skill_id,
1117
+ member_name=member_name or "unknown",
1118
+ error=str(e),
1119
+ error_type=type(e).__name__,
1120
+ )
1121
+ failed_skills.append(skill_id)
1122
+ continue
1123
+
1124
+ # If no tools mapped, provide basic defaults
1125
+ if not tools:
1126
+ logger.warning(
1127
+ "no_tools_mapped_using_defaults",
1128
+ member_name=member_name or "unknown",
1129
+ total_skills=len(skill_ids),
1130
+ unmapped_count=len(unmapped_skills),
1131
+ failed_count=len(failed_skills),
1132
+ default_tools=['Read', 'Write', 'Bash'],
1133
+ )
1134
+ tools = {'Read', 'Write', 'Bash'}
1135
+
1136
+ # Log mapping summary
1137
+ logger.info(
1138
+ "skill_mapping_completed",
1139
+ member_name=member_name or "unknown",
1140
+ total_skills=len(skill_ids),
1141
+ mapped_tools=list(tools),
1142
+ tool_count=len(tools),
1143
+ skill_types=skill_type_counts,
1144
+ unmapped_count=len(unmapped_skills),
1145
+ failed_count=len(failed_skills),
1146
+ )
1147
+
1148
+ if unmapped_skills:
1149
+ logger.info(
1150
+ "unmapped_skills_summary",
1151
+ member_name=member_name or "unknown",
1152
+ unmapped_skills=[f"{name} ({stype})" for name, stype in unmapped_skills],
1153
+ )
1154
+
1155
+ return list(tools)
1156
+
1157
+ def _map_model_to_sdk_format(self, model_id: str) -> str:
1158
+ """
1159
+ Map model ID to SDK format (sonnet/opus/haiku/inherit).
1160
+
1161
+ Args:
1162
+ model_id: Full model ID (e.g., "kubiya/claude-sonnet-4")
1163
+
1164
+ Returns:
1165
+ SDK model format string
1166
+ """
1167
+ if not model_id or model_id == 'inherit':
1168
+ return 'inherit'
1169
+
1170
+ model_lower = model_id.lower()
1171
+
1172
+ if 'sonnet' in model_lower:
1173
+ return 'sonnet'
1174
+ elif 'opus' in model_lower:
1175
+ return 'opus'
1176
+ elif 'haiku' in model_lower:
1177
+ return 'haiku'
1178
+
1179
+ # Default to inherit (use leader's model)
1180
+ return 'inherit'
1181
+
1182
+ def _build_team_leader_prompt(self, agents: List[Dict]) -> str:
1183
+ """
1184
+ Build system prompt for team leader with native subagents.
1185
+
1186
+ Args:
1187
+ agents: List of agent configurations
1188
+
1189
+ Returns:
1190
+ Formatted system prompt
1191
+ """
1192
+ member_descriptions = []
1193
+
1194
+ for agent in agents:
1195
+ name = agent.get('name', 'Agent')
1196
+ role = agent.get('role', 'Team member')
1197
+ agent_id = agent.get('id', 'unknown')
1198
+ member_descriptions.append(
1199
+ f"- **{name}** (ID: {agent_id}): {role}"
1200
+ )
1201
+
1202
+ return f"""You are the team leader coordinating a team of specialized AI agents.
1203
+
1204
+ Your team members:
1205
+ {chr(10).join(member_descriptions)}
1206
+
1207
+ Claude will automatically invoke the appropriate team member based on the task.
1208
+ Each member has their own:
1209
+ - Specialized system prompt and expertise
1210
+ - Dedicated tools and capabilities
1211
+ - Separate context (won't see each other's work)
1212
+ - Own model configuration
1213
+
1214
+ Coordinate effectively to solve the user's request. The SDK will handle routing tasks to the right team member.
1215
+ """
1216
+
1217
+ def _all_members_are_claude_code(self, agents: List[Dict]) -> bool:
1218
+ """
1219
+ Check if all team members use Claude Code runtime.
1220
+
1221
+ Optimized to batch-fetch agent configs to avoid N+1 queries.
1222
+
1223
+ Args:
1224
+ agents: List of agent configurations
1225
+
1226
+ Returns:
1227
+ True if all members are Claude Code runtime
1228
+ """
1229
+ if not agents:
1230
+ logger.warning("no_agents_to_check_runtime")
1231
+ return False
1232
+
1233
+ agent_ids = [agent.get('id') for agent in agents if agent.get('id')]
1234
+
1235
+ if len(agent_ids) != len(agents):
1236
+ logger.warning(
1237
+ "some_agents_missing_ids_in_runtime_check",
1238
+ total_agents=len(agents),
1239
+ agents_with_ids=len(agent_ids),
1240
+ )
1241
+ # If some agents don't have IDs, we can't verify them
1242
+ return False
1243
+
1244
+ # Batch fetch all agent configs to avoid N+1 queries
1245
+ agent_configs = {}
1246
+ failed_fetches = []
1247
+
1248
+ logger.info(
1249
+ "batch_fetching_agent_configs_for_runtime_check",
1250
+ agent_count=len(agent_ids),
1251
+ agent_ids=agent_ids,
1252
+ )
1253
+
1254
+ # Try to fetch all agent configs
1255
+ for agent_id in agent_ids:
1256
+ try:
1257
+ agent_config = self.control_plane.get_agent(agent_id)
1258
+ if agent_config:
1259
+ agent_configs[agent_id] = agent_config
1260
+ else:
1261
+ logger.warning(
1262
+ "agent_config_not_found_in_runtime_check",
1263
+ agent_id=agent_id,
1264
+ )
1265
+ failed_fetches.append(agent_id)
1266
+ except Exception as e:
1267
+ logger.warning(
1268
+ "failed_to_fetch_agent_config_in_runtime_check",
1269
+ agent_id=agent_id,
1270
+ error=str(e),
1271
+ error_type=type(e).__name__,
1272
+ )
1273
+ failed_fetches.append(agent_id)
1274
+
1275
+ # If we couldn't fetch all configs, we can't verify runtime consistency
1276
+ if failed_fetches:
1277
+ logger.warning(
1278
+ "cannot_verify_all_agent_runtimes",
1279
+ failed_fetches=failed_fetches,
1280
+ fetched_count=len(agent_configs),
1281
+ total_count=len(agent_ids),
1282
+ )
1283
+ return False
1284
+
1285
+ # Check if all fetched agents use claude_code runtime
1286
+ non_claude_code_agents = []
1287
+ runtime_distribution = {}
1288
+
1289
+ for agent_id, config in agent_configs.items():
1290
+ runtime = config.get('runtime', 'default')
1291
+ runtime_distribution[runtime] = runtime_distribution.get(runtime, 0) + 1
1292
+
1293
+ if runtime != 'claude_code':
1294
+ agent_name = next(
1295
+ (a.get('name', agent_id) for a in agents if a.get('id') == agent_id),
1296
+ agent_id
1297
+ )
1298
+ non_claude_code_agents.append({
1299
+ 'id': agent_id,
1300
+ 'name': agent_name,
1301
+ 'runtime': runtime,
1302
+ })
1303
+
1304
+ # Log runtime distribution for observability
1305
+ logger.info(
1306
+ "team_member_runtime_distribution",
1307
+ total_members=len(agent_configs),
1308
+ runtime_distribution=runtime_distribution,
1309
+ all_claude_code=len(non_claude_code_agents) == 0,
1310
+ )
1311
+
1312
+ if non_claude_code_agents:
1313
+ logger.info(
1314
+ "mixed_runtime_team_detected",
1315
+ claude_code_count=runtime_distribution.get('claude_code', 0),
1316
+ non_claude_code_count=len(non_claude_code_agents),
1317
+ non_claude_code_agents=[
1318
+ f"{a['name']} ({a['runtime']})" for a in non_claude_code_agents
1319
+ ],
1320
+ decision="will_use_task_tool_delegation",
1321
+ )
1322
+ return False
1323
+
1324
+ logger.info(
1325
+ "all_members_are_claude_code",
1326
+ member_count=len(agent_configs),
1327
+ decision="will_use_native_subagents",
1328
+ )
1329
+ return True
1330
+
1331
+ def _build_team_context(self, agents: List[Dict]) -> str:
1332
+ """
1333
+ Build team context description for system prompt.
1334
+
1335
+ Args:
1336
+ agents: List of agent configurations
1337
+
1338
+ Returns:
1339
+ Formatted team context string
1340
+ """
1341
+ context_lines = []
1342
+ for i, agent in enumerate(agents, 1):
1343
+ name = agent.get("name", f"Agent {i}")
1344
+ role = agent.get("role", "No role specified")
1345
+ agent_id = agent.get("id", "unknown")
1346
+
1347
+ context_lines.append(
1348
+ f"{i}. **{name}** (ID: {agent_id})\n"
1349
+ f" Role: {role}\n"
1350
+ )
1351
+
1352
+ return "\n".join(context_lines)
1353
+
1354
+ def _build_execution_context_info(
1355
+ self,
1356
+ runtime_config: Dict[str, Any],
1357
+ skills: List[Any],
1358
+ mcp_servers: Optional[Dict[str, Any]],
1359
+ team_config: Dict[str, Any]
1360
+ ) -> str:
1361
+ """
1362
+ Build comprehensive execution environment context for system prompt.
1363
+
1364
+ This provides the team with awareness of:
1365
+ - Available environment variables (secrets, integrations, config)
1366
+ - Available skills/tools
1367
+ - MCP servers
1368
+ - Runtime configuration
1369
+
1370
+ Args:
1371
+ runtime_config: Runtime configuration with env vars
1372
+ skills: List of available skills
1373
+ mcp_servers: Dictionary of MCP server configurations
1374
+ team_config: Team configuration
1375
+
1376
+ Returns:
1377
+ Formatted execution context string for system prompt
1378
+ """
1379
+ context_parts = []
1380
+ context_parts.append("---")
1381
+ context_parts.append("")
1382
+ context_parts.append("# 🔧 Execution Environment Context")
1383
+ context_parts.append("")
1384
+ context_parts.append("You are running in a managed execution environment with the following resources available:")
1385
+ context_parts.append("")
1386
+
1387
+ # 1. Environment Variables
1388
+ if runtime_config and "env" in runtime_config:
1389
+ available_env_vars = runtime_config["env"]
1390
+
1391
+ # Categorize environment variables
1392
+ secrets = [k for k in available_env_vars.keys() if any(
1393
+ keyword in k.lower()
1394
+ for keyword in ["secret", "password", "credential", "api_key", "private_key"]
1395
+ ) and k not in ["KUBIYA_API_KEY", "KUBIYA_API_BASE", "ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL"]]
1396
+
1397
+ integrations = [k for k in available_env_vars.keys() if any(
1398
+ prefix in k
1399
+ for prefix in ["GH_TOKEN", "GITHUB_", "JIRA_", "SLACK_", "AWS_", "GCP_", "AZURE_"]
1400
+ )]
1401
+
1402
+ inherited_vars = [k for k in available_env_vars.keys()
1403
+ if k not in secrets
1404
+ and k not in integrations
1405
+ and k not in ["KUBIYA_API_KEY", "KUBIYA_API_BASE", "ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL", "LITELLM_API_KEY", "LITELLM_API_BASE"]]
1406
+
1407
+ context_parts.append("## 📦 Environment Variables")
1408
+ context_parts.append("")
1409
+
1410
+ if secrets:
1411
+ context_parts.append("**Secrets & API Keys** (use these for authenticated operations):")
1412
+ for var in sorted(secrets):
1413
+ context_parts.append(f"- `${var}` - Secret/credential available as environment variable")
1414
+ context_parts.append("")
1415
+
1416
+ if integrations:
1417
+ context_parts.append("**Integration Tokens** (pre-configured service access):")
1418
+ for var in sorted(integrations):
1419
+ service = var.split("_")[0].title() if "_" in var else var
1420
+ context_parts.append(f"- `${var}` - {service} integration token")
1421
+ context_parts.append("")
1422
+
1423
+ if inherited_vars:
1424
+ context_parts.append("**Configuration Variables** (inherited from environment):")
1425
+ # Limit to first 10 to avoid clutter
1426
+ for var in sorted(inherited_vars)[:10]:
1427
+ context_parts.append(f"- `${var}`")
1428
+ if len(inherited_vars) > 10:
1429
+ context_parts.append(f"- ... and {len(inherited_vars) - 10} more")
1430
+ context_parts.append("")
1431
+
1432
+ if secrets or integrations or inherited_vars:
1433
+ context_parts.append("**Usage Examples:**")
1434
+ context_parts.append("```bash")
1435
+ context_parts.append("# Access in Bash commands")
1436
+ context_parts.append("echo $VARIABLE_NAME")
1437
+ context_parts.append("")
1438
+ if integrations:
1439
+ example_token = sorted(integrations)[0]
1440
+ if "GH" in example_token or "GITHUB" in example_token:
1441
+ context_parts.append("# Use with GitHub API")
1442
+ context_parts.append(f"curl -H \"Authorization: token ${example_token}\" https://api.github.com/user")
1443
+ elif "JIRA" in example_token:
1444
+ context_parts.append("# Use with Jira API")
1445
+ context_parts.append(f"curl -H \"Authorization: Bearer ${example_token}\" https://yourinstance.atlassian.net/rest/api/3/myself")
1446
+ context_parts.append("```")
1447
+ context_parts.append("")
1448
+
1449
+ # 2. Available Skills/Tools
1450
+ if skills:
1451
+ context_parts.append("## 🛠️ Available Skills/Tools")
1452
+ context_parts.append("")
1453
+ skill_names = []
1454
+ for skill in skills:
1455
+ if isinstance(skill, dict):
1456
+ skill_names.append(skill.get("name", skill.get("type", "Unknown")))
1457
+ else:
1458
+ skill_names.append(type(skill).__name__ if hasattr(skill, '__class__') else str(skill))
1459
+
1460
+ if skill_names:
1461
+ context_parts.append(f"You have access to {len(skill_names)} skill(s):")
1462
+ for skill_name in sorted(set(skill_names))[:15]: # Limit to 15 to avoid clutter
1463
+ context_parts.append(f"- `{skill_name}`")
1464
+ if len(set(skill_names)) > 15:
1465
+ context_parts.append(f"- ... and {len(set(skill_names)) - 15} more")
1466
+ context_parts.append("")
1467
+
1468
+ # 3. MCP Servers
1469
+ if mcp_servers:
1470
+ context_parts.append("## 🔌 MCP Servers")
1471
+ context_parts.append("")
1472
+ context_parts.append(f"You have access to {len(mcp_servers)} MCP server(s):")
1473
+ for server_name in sorted(mcp_servers.keys())[:10]: # Limit to 10
1474
+ context_parts.append(f"- `{server_name}`")
1475
+ if len(mcp_servers) > 10:
1476
+ context_parts.append(f"- ... and {len(mcp_servers) - 10} more")
1477
+ context_parts.append("")
1478
+ context_parts.append("**Note:** All environment variables listed above are automatically available to these MCP servers.")
1479
+ context_parts.append("")
1480
+
1481
+ # 4. Best Practices
1482
+ context_parts.append("## 💡 Best Practices")
1483
+ context_parts.append("")
1484
+ context_parts.append("- **Environment Variables**: All listed variables are ready to use - no configuration needed")
1485
+ context_parts.append("- **Secrets**: Never log or display secret values in responses")
1486
+ context_parts.append("- **Integration Tokens**: These provide pre-authorized access to external services")
1487
+ context_parts.append("- **MCP Tools**: Prefer using MCP tools over Bash when available for better reliability")
1488
+ context_parts.append("")
1489
+ context_parts.append("---")
1490
+
1491
+ logger.info(
1492
+ "execution_context_info_built",
1493
+ env_vars_count=len(runtime_config.get("env", {})) if runtime_config else 0,
1494
+ skills_count=len(skills) if skills else 0,
1495
+ mcp_servers_count=len(mcp_servers) if mcp_servers else 0
1496
+ )
1497
+
1498
+ return "\n".join(context_parts)
1499
+
1500
+ def _get_framework_name(self, runtime_type: RuntimeType) -> str:
1501
+ """
1502
+ Get friendly framework name for runtime type.
1503
+
1504
+ Args:
1505
+ runtime_type: Runtime type enum
1506
+
1507
+ Returns:
1508
+ Framework name string
1509
+ """
1510
+ mapping = {
1511
+ RuntimeType.DEFAULT: "Agno",
1512
+ RuntimeType.CLAUDE_CODE: "Claude Code SDK",
1513
+ RuntimeType.AGENT_RUNTIME: "Agent Runtime (Rust/GRPC)",
1514
+ }
1515
+ return mapping.get(runtime_type, "Unknown")
1516
+
1517
+ # ==================== NEW: Agent-Runtime Integration ====================
1518
+
1519
+ def _detect_execution_mode(self, input: Any, runtime_type: RuntimeType) -> str:
1520
+ """
1521
+ Detect which execution mode to use based on configuration and runtime type.
1522
+
1523
+ Execution modes:
1524
+ - "agent_runtime_native": Use agent-runtime GRPC with native subagents
1525
+ - "claude_code_native": Use Claude Code SDK with native subagents
1526
+ - "claude_code_task": Use Claude Code SDK with Task tool delegation
1527
+ - "agno": Use Agno-based team execution
1528
+
1529
+ Priority:
1530
+ 1. Explicit runtime=agent_runtime in team_config
1531
+ 2. ENABLE_AGENT_RUNTIME_TEAMS environment variable
1532
+ 3. Runtime type from input
1533
+ 4. Default to agno
1534
+
1535
+ Args:
1536
+ input: TeamExecutionInput
1537
+ runtime_type: Detected runtime type
1538
+
1539
+ Returns:
1540
+ Execution mode string
1541
+ """
1542
+ team_config = getattr(input, "team_config", {}) or {}
1543
+ runtime_config_str = team_config.get("runtime", "default")
1544
+
1545
+ # Check for explicit agent-runtime request
1546
+ if runtime_config_str == "agent_runtime":
1547
+ logger.info(
1548
+ "agent_runtime_explicitly_requested",
1549
+ execution_id=input.execution_id[:8],
1550
+ )
1551
+ return "agent_runtime_native"
1552
+
1553
+ # Check environment variable for agent-runtime teams
1554
+ if os.getenv("ENABLE_AGENT_RUNTIME_TEAMS", "false").lower() == "true":
1555
+ # Only use agent-runtime if all members are compatible
1556
+ if input.agents and self._all_members_are_compatible_with_agent_runtime(input.agents):
1557
+ logger.info(
1558
+ "agent_runtime_enabled_via_env",
1559
+ execution_id=input.execution_id[:8],
1560
+ member_count=len(input.agents),
1561
+ )
1562
+ return "agent_runtime_native"
1563
+ else:
1564
+ logger.warning(
1565
+ "agent_runtime_env_set_but_members_incompatible",
1566
+ execution_id=input.execution_id[:8],
1567
+ )
1568
+
1569
+ # Check if runtime type is explicitly AGENT_RUNTIME
1570
+ if runtime_type == RuntimeType.AGENT_RUNTIME:
1571
+ return "agent_runtime_native"
1572
+
1573
+ # Check for Claude Code native subagents
1574
+ if runtime_type == RuntimeType.CLAUDE_CODE:
1575
+ if input.agents and self._all_members_are_claude_code(input.agents):
1576
+ return "claude_code_native"
1577
+ else:
1578
+ return "claude_code_task"
1579
+
1580
+ # Default to Agno
1581
+ return "agno"
1582
+
1583
+ def _all_members_are_compatible_with_agent_runtime(self, agents: List[Dict]) -> bool:
1584
+ """
1585
+ Check if all team members are compatible with agent-runtime.
1586
+
1587
+ Currently, agent-runtime supports Claude-based models.
1588
+ This can be extended to check for specific runtime requirements.
1589
+
1590
+ Args:
1591
+ agents: List of agent configurations
1592
+
1593
+ Returns:
1594
+ True if all members can use agent-runtime
1595
+ """
1596
+ if not agents:
1597
+ return False
1598
+
1599
+ agent_ids = [agent.get('id') for agent in agents if agent.get('id')]
1600
+
1601
+ if len(agent_ids) != len(agents):
1602
+ logger.warning(
1603
+ "some_agents_missing_ids_in_compatibility_check",
1604
+ total_agents=len(agents),
1605
+ agents_with_ids=len(agent_ids),
1606
+ )
1607
+ return False
1608
+
1609
+ # For now, consider all agents compatible with agent-runtime
1610
+ # In the future, we can add more specific checks (model type, runtime requirements, etc.)
1611
+ logger.info(
1612
+ "agent_runtime_compatibility_check",
1613
+ agent_count=len(agents),
1614
+ compatible=True,
1615
+ )
1616
+
1617
+ return True
1618
+
1619
+ async def _execute_via_agent_runtime(
1620
+ self,
1621
+ input: Any,
1622
+ session_history: List[Dict]
1623
+ ) -> Dict[str, Any]:
1624
+ """
1625
+ Execute team using agent-runtime GRPC service with native subagents.
1626
+
1627
+ This method:
1628
+ 1. Builds agents configuration from team members
1629
+ 2. Creates RuntimeExecutionContext with agents field
1630
+ 3. Sets enable_native_subagents=True
1631
+ 4. Executes via agent-runtime with streaming
1632
+
1633
+ Args:
1634
+ input: TeamExecutionInput
1635
+ session_history: Previous conversation messages
1636
+
1637
+ Returns:
1638
+ Result dict with response, usage, success, etc.
1639
+ """
1640
+ execution_id = input.execution_id
1641
+
1642
+ logger.info(
1643
+ "executing_via_agent_runtime",
1644
+ execution_id=execution_id[:8],
1645
+ member_count=len(input.agents) if input.agents else 0,
1646
+ )
1647
+
1648
+ # STEP 1: Build agents configuration from team members
1649
+ team_config = getattr(input, "team_config", {}) or {}
1650
+ agents_config = await self._build_agents_config_for_agent_runtime(input.agents, team_config)
1651
+
1652
+ logger.info(
1653
+ "agents_config_built",
1654
+ execution_id=execution_id[:8],
1655
+ agent_count=len(agents_config),
1656
+ )
1657
+
1658
+ # STEP 2: Build team leader system prompt
1659
+ team_context = self._build_team_context(input.agents)
1660
+ system_prompt = f"""You are the team leader coordinating a team of specialized AI agents.
1661
+
1662
+ Your team members:
1663
+ {team_context}
1664
+
1665
+ You have native access to your team members. When you need a team member to perform a task, the runtime will automatically route the work to the appropriate agent based on their expertise.
1666
+
1667
+ Your goal is to coordinate the team effectively to solve the user's request.
1668
+ """
1669
+
1670
+ # STEP 3: Get skills for team leader
1671
+ logger.info("fetching_skills_for_team_leader")
1672
+ skills = []
1673
+ skill_configs = []
1674
+ try:
1675
+ if input.agents:
1676
+ leader_id = input.agents[0].get("id")
1677
+ if leader_id:
1678
+ skill_configs = self.control_plane.get_skills(leader_id)
1679
+ if skill_configs:
1680
+ logger.info("skills_resolved", count=len(skill_configs))
1681
+
1682
+ from control_plane_api.worker.services.skill_factory import SkillFactory
1683
+
1684
+ skill_factory = SkillFactory(runtime_type="agent_runtime")
1685
+ skill_factory.initialize()
1686
+ skills = skill_factory.create_skills_from_list(skill_configs, execution_id=execution_id)
1687
+
1688
+ if skills:
1689
+ logger.info("skills_instantiated", count=len(skills))
1690
+ except Exception as e:
1691
+ logger.warning("skill_fetch_error", error=str(e))
1692
+
1693
+ # STEP 4: Inject environment variables into MCP servers
1694
+ from control_plane_api.worker.activities.runtime_activities import inject_env_vars_into_mcp_servers
1695
+ mcp_servers_with_env = inject_env_vars_into_mcp_servers(
1696
+ mcp_servers=getattr(input, "mcp_servers", None),
1697
+ agent_config=team_config,
1698
+ runtime_config=team_config.get("runtime_config"),
1699
+ control_plane_client=self.control_plane,
1700
+ team_id=input.team_id,
1701
+ )
1702
+
1703
+ # STEP 5: Build RuntimeExecutionContext with agents configuration
1704
+ context = RuntimeExecutionContext(
1705
+ execution_id=execution_id,
1706
+ agent_id=input.team_id,
1707
+ organization_id=input.organization_id,
1708
+ prompt=input.prompt,
1709
+ system_prompt=system_prompt,
1710
+ conversation_history=self._simplify_session_to_conversation(session_history),
1711
+ session_messages=session_history, # Full messages with metadata
1712
+ session_id=input.session_id,
1713
+ model_id=input.model_id,
1714
+ model_config=getattr(input, "model_config", None),
1715
+ agent_config=team_config,
1716
+ skills=skill_configs, # Raw skill configs for agent-runtime
1717
+ mcp_servers=mcp_servers_with_env,
1718
+ user_metadata=getattr(input, "user_metadata", None),
1719
+ runtime_config=team_config.get("runtime_config"),
1720
+ agents=agents_config, # Sub-agent definitions
1721
+ enable_native_subagents=True,
1722
+ enable_session_persistence=bool(input.session_id),
1723
+ # Enforcement context
1724
+ user_email=input.user_email if hasattr(input, 'user_email') else None,
1725
+ user_id=input.user_id,
1726
+ team_id=input.team_id,
1727
+ environment=os.getenv("ENVIRONMENT", "production"),
1728
+ )
1729
+
1730
+ # STEP 6: Create agent-runtime instance
1731
+ logger.info("creating_agent_runtime_instance")
1732
+ runtime = await self.runtime_factory.create_runtime(
1733
+ runtime_type=RuntimeType.AGENT_RUNTIME,
1734
+ control_plane_client=self.control_plane,
1735
+ cancellation_manager=self.cancellation_manager,
1736
+ )
1737
+
1738
+ logger.info("agent_runtime_created", info=runtime.get_runtime_info())
1739
+
1740
+ # STEP 7: Execute with streaming
1741
+ self.streaming_helper = StreamingHelper(
1742
+ control_plane_client=self.control_plane,
1743
+ execution_id=execution_id
1744
+ )
1745
+
1746
+ if runtime.supports_streaming():
1747
+ logger.info("executing_with_streaming")
1748
+ result = await self._execute_streaming(
1749
+ runtime, context, input, self.streaming_helper
1750
+ )
1751
+ else:
1752
+ logger.info("executing_without_streaming")
1753
+ exec_result = await runtime.execute(context)
1754
+ result = self._convert_exec_result_to_dict(exec_result, input)
1755
+
1756
+ logger.info(
1757
+ "agent_runtime_execution_completed",
1758
+ execution_id=execution_id[:8],
1759
+ success=result.get("success"),
1760
+ )
1761
+
1762
+ return result
1763
+
1764
+ async def _build_agents_config_for_agent_runtime(
1765
+ self,
1766
+ agents: List[Dict],
1767
+ team_config: Dict
1768
+ ) -> Dict[str, Dict[str, Any]]:
1769
+ """
1770
+ Build agents configuration dictionary for agent-runtime.
1771
+
1772
+ Fetches agent configs from Control Plane and formats for GRPC.
1773
+
1774
+ Args:
1775
+ agents: List of team member definitions
1776
+ team_config: Team configuration dictionary
1777
+
1778
+ Returns:
1779
+ Dict mapping agent_id to agent definition
1780
+ """
1781
+ agents_config = {}
1782
+
1783
+ if not agents:
1784
+ logger.warning("no_agents_to_build_config")
1785
+ return agents_config
1786
+
1787
+ # Batch fetch agent configs to avoid N+1 queries
1788
+ agent_ids = [agent.get("id") for agent in agents if agent.get("id")]
1789
+
1790
+ logger.info(
1791
+ "batch_fetching_agent_configs_for_agent_runtime",
1792
+ agent_count=len(agent_ids),
1793
+ )
1794
+
1795
+ for agent_id in agent_ids:
1796
+ try:
1797
+ agent_config = self.control_plane.get_agent(agent_id)
1798
+ if not agent_config:
1799
+ logger.warning("agent_config_not_found", agent_id=agent_id)
1800
+ continue
1801
+
1802
+ # Find agent definition from input
1803
+ agent_def = next((a for a in agents if a.get("id") == agent_id), None)
1804
+ if not agent_def:
1805
+ logger.warning("agent_def_not_found_in_input", agent_id=agent_id)
1806
+ continue
1807
+
1808
+ # Get skills for this agent
1809
+ skill_names = []
1810
+ try:
1811
+ skill_configs = self.control_plane.get_skills(agent_id)
1812
+ if skill_configs:
1813
+ skill_names = [skill.get("name") for skill in skill_configs if skill.get("name")]
1814
+ except Exception as e:
1815
+ logger.warning("failed_to_fetch_skills", agent_id=agent_id, error=str(e))
1816
+
1817
+ # Build agent definition
1818
+ agents_config[agent_id] = {
1819
+ "description": agent_def.get("role", agent_config.get("description", "")),
1820
+ "prompt": agent_config.get("system_prompt", ""),
1821
+ "tools": skill_names,
1822
+ "model": agent_config.get("model_id", "inherit"),
1823
+ "config": agent_config.get("runtime_config", {}),
1824
+ }
1825
+
1826
+ logger.debug(
1827
+ "agent_config_built",
1828
+ agent_id=agent_id,
1829
+ tools_count=len(skill_names),
1830
+ )
1831
+
1832
+ except Exception as e:
1833
+ logger.error(
1834
+ "failed_to_build_agent_config",
1835
+ agent_id=agent_id,
1836
+ error=str(e),
1837
+ )
1838
+
1839
+ logger.info(
1840
+ "agents_config_built_successfully",
1841
+ agent_count=len(agents_config),
1842
+ )
1843
+
1844
+ return agents_config
1845
+
1846
+ def _simplify_session_to_conversation(
1847
+ self,
1848
+ session_messages: List[Dict]
1849
+ ) -> List[Dict[str, str]]:
1850
+ """
1851
+ Convert full session messages to simple conversation_history format.
1852
+
1853
+ Extracts only role and content for backward compatibility with runtimes
1854
+ that don't need full metadata.
1855
+
1856
+ Args:
1857
+ session_messages: Full messages with metadata
1858
+
1859
+ Returns:
1860
+ Simplified list with only role + content
1861
+ """
1862
+ return [
1863
+ {"role": msg.get("role"), "content": msg.get("content")}
1864
+ for msg in session_messages
1865
+ if msg.get("role") and msg.get("content")
1866
+ ]