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,529 @@
1
+ """
2
+ Session-based client pool for Claude Code runtime.
3
+
4
+ This module provides efficient client pooling to avoid re-initializing
5
+ Claude Code clients for every execution. Clients are pooled by session_id
6
+ and reused across followup messages in the same conversation.
7
+
8
+ Key Features:
9
+ - One client per session_id (reused across followup messages)
10
+ - Automatic TTL-based cleanup of idle clients
11
+ - LRU eviction when pool reaches capacity
12
+ - Thread-safe with asyncio locks
13
+ - Health checks before reusing clients
14
+ - Comprehensive logging for debugging
15
+
16
+ Performance Impact:
17
+ - Before: 3-5s initialization per message (including followups)
18
+ - After: 3-5s first message, 0.1-0.5s followups (70-80% reduction!)
19
+ """
20
+
21
+ from dataclasses import dataclass
22
+ from typing import Dict, Any, Optional, Callable, Tuple
23
+ import structlog
24
+ import asyncio
25
+ import time
26
+ import os
27
+ from collections import OrderedDict
28
+
29
+ logger = structlog.get_logger(__name__)
30
+
31
+
32
+ @dataclass
33
+ class SessionClient:
34
+ """
35
+ Represents a pooled Claude Code client for a session.
36
+
37
+ Tracks usage statistics and lifecycle information for monitoring
38
+ and cleanup decisions.
39
+ """
40
+ client: Any # ClaudeSDKClient instance
41
+ options: Any # ClaudeAgentOptions instance
42
+ session_id: str
43
+ created_at: float
44
+ last_used: float
45
+ execution_count: int = 0
46
+ is_connected: bool = True
47
+
48
+ def mark_used(self) -> None:
49
+ """Update last_used timestamp and increment execution count."""
50
+ self.last_used = time.time()
51
+ self.execution_count += 1
52
+
53
+
54
+ class ClaudeCodeClientPool:
55
+ """
56
+ Session-based client pool for Claude Code runtime.
57
+
58
+ Maintains a pool of connected Claude Code clients, keyed by session_id.
59
+ Clients are reused across followup executions in the same session,
60
+ dramatically reducing initialization overhead.
61
+
62
+ Example:
63
+ pool = ClaudeCodeClientPool(max_pool_size=100, client_ttl_seconds=86400)
64
+
65
+ # First execution (cache miss - slow)
66
+ client, options = await pool.get_or_create_client(session_id, context)
67
+
68
+ # Followup execution (cache hit - fast!)
69
+ client, options = await pool.get_or_create_client(session_id, context)
70
+
71
+ # Release when done (keeps in pool for reuse)
72
+ await pool.release_client(session_id)
73
+ """
74
+
75
+ def __init__(
76
+ self,
77
+ max_pool_size: int = 100,
78
+ client_ttl_seconds: int = 86400, # 24 hours
79
+ cleanup_interval_seconds: int = 300, # 5 minutes
80
+ ):
81
+ """
82
+ Initialize the client pool.
83
+
84
+ Args:
85
+ max_pool_size: Maximum number of clients to pool (LRU eviction when exceeded)
86
+ client_ttl_seconds: How long to keep idle clients before cleanup
87
+ cleanup_interval_seconds: How often to run cleanup task
88
+ """
89
+ # Pool storage (OrderedDict for LRU behavior)
90
+ self._pool: OrderedDict[str, SessionClient] = OrderedDict()
91
+
92
+ # Per-session locks for thread-safe access
93
+ self._locks: Dict[str, asyncio.Lock] = {}
94
+
95
+ # Global pool lock for structural changes
96
+ self._pool_lock = asyncio.Lock()
97
+
98
+ # Configuration
99
+ self._max_pool_size = max_pool_size
100
+ self._client_ttl_seconds = client_ttl_seconds
101
+ self._cleanup_interval_seconds = cleanup_interval_seconds
102
+
103
+ # Cleanup task
104
+ self._cleanup_task: Optional[asyncio.Task] = None
105
+
106
+ # Statistics
107
+ self._stats = {
108
+ "cache_hits": 0,
109
+ "cache_misses": 0,
110
+ "clients_created": 0,
111
+ "clients_evicted": 0,
112
+ "clients_expired": 0,
113
+ "total_reuses": 0,
114
+ }
115
+
116
+ logger.info(
117
+ "claude_code_client_pool_initialized",
118
+ max_pool_size=max_pool_size,
119
+ client_ttl_hours=client_ttl_seconds / 3600,
120
+ cleanup_interval_minutes=cleanup_interval_seconds / 60,
121
+ )
122
+
123
+ async def start_cleanup_task(self) -> None:
124
+ """Start background cleanup task for expired clients."""
125
+ if self._cleanup_task is None:
126
+ self._cleanup_task = asyncio.create_task(self._cleanup_loop())
127
+ logger.info("client_pool_cleanup_task_started")
128
+
129
+ async def stop_cleanup_task(self) -> None:
130
+ """Stop background cleanup task."""
131
+ if self._cleanup_task:
132
+ self._cleanup_task.cancel()
133
+ try:
134
+ await self._cleanup_task
135
+ except asyncio.CancelledError:
136
+ pass
137
+ self._cleanup_task = None
138
+ logger.info("client_pool_cleanup_task_stopped")
139
+
140
+ async def _cleanup_loop(self) -> None:
141
+ """Background task that periodically cleans up expired clients."""
142
+ while True:
143
+ try:
144
+ await asyncio.sleep(self._cleanup_interval_seconds)
145
+ await self.cleanup_expired()
146
+ except asyncio.CancelledError:
147
+ logger.info("cleanup_loop_cancelled")
148
+ break
149
+ except Exception as e:
150
+ logger.error(
151
+ "cleanup_loop_error",
152
+ error=str(e),
153
+ error_type=type(e).__name__,
154
+ exc_info=True
155
+ )
156
+
157
+ def _get_session_lock(self, session_id: str) -> asyncio.Lock:
158
+ """Get or create lock for a session."""
159
+ if session_id not in self._locks:
160
+ self._locks[session_id] = asyncio.Lock()
161
+ return self._locks[session_id]
162
+
163
+ async def get_or_create_client(
164
+ self,
165
+ session_id: str,
166
+ context: Any, # RuntimeExecutionContext
167
+ event_callback: Optional[Callable] = None,
168
+ runtime: Optional[Any] = None, # ClaudeCodeRuntime instance
169
+ ) -> Tuple[Any, Any]:
170
+ """
171
+ Get existing client for session or create new one.
172
+
173
+ This is the main entry point for getting a client. On cache hit,
174
+ returns immediately with existing client (FAST ⚡). On cache miss,
175
+ builds options and creates new client (SLOW, first time only).
176
+
177
+ Args:
178
+ session_id: Session identifier (typically execution_id for 1:1 mapping)
179
+ context: RuntimeExecutionContext with execution details
180
+ event_callback: Optional callback for real-time events
181
+ runtime: Optional ClaudeCodeRuntime instance for MCP caching
182
+
183
+ Returns:
184
+ Tuple of (ClaudeSDKClient, ClaudeAgentOptions)
185
+ """
186
+ # Ensure cleanup task is running
187
+ if self._cleanup_task is None:
188
+ await self.start_cleanup_task()
189
+
190
+ # Get session-specific lock
191
+ lock = self._get_session_lock(session_id)
192
+
193
+ async with lock:
194
+ # Check if client exists in pool
195
+ if session_id in self._pool:
196
+ session_client = self._pool[session_id]
197
+
198
+ # Move to end for LRU (most recently used)
199
+ self._pool.move_to_end(session_id)
200
+
201
+ # Update usage stats
202
+ session_client.mark_used()
203
+ self._stats["cache_hits"] += 1
204
+ self._stats["total_reuses"] += 1
205
+
206
+ logger.info(
207
+ "client_pool_cache_hit",
208
+ session_id=session_id[:16],
209
+ execution_count=session_client.execution_count,
210
+ age_seconds=int(time.time() - session_client.created_at),
211
+ last_used_seconds_ago=int(time.time() - session_client.last_used),
212
+ pool_size=len(self._pool),
213
+ cache_hit_rate=self._get_cache_hit_rate(),
214
+ note="⚡ FAST: Reusing existing client (no initialization overhead)"
215
+ )
216
+
217
+ return session_client.client, session_client.options
218
+
219
+ # Cache miss - need to create new client
220
+ self._stats["cache_misses"] += 1
221
+
222
+ logger.info(
223
+ "client_pool_cache_miss",
224
+ session_id=session_id[:16],
225
+ pool_size=len(self._pool),
226
+ cache_hit_rate=self._get_cache_hit_rate(),
227
+ note="🐌 SLOW: Creating new client (first time for this session)"
228
+ )
229
+
230
+ # Check if we need to evict (LRU) to make room
231
+ if len(self._pool) >= self._max_pool_size:
232
+ await self._evict_lru()
233
+
234
+ # Create new client
235
+ client, options = await self._create_client(
236
+ session_id, context, event_callback, runtime
237
+ )
238
+
239
+ # Add to pool
240
+ session_client = SessionClient(
241
+ client=client,
242
+ options=options,
243
+ session_id=session_id,
244
+ created_at=time.time(),
245
+ last_used=time.time(),
246
+ execution_count=1,
247
+ is_connected=True,
248
+ )
249
+
250
+ self._pool[session_id] = session_client
251
+ self._stats["clients_created"] += 1
252
+
253
+ logger.info(
254
+ "client_added_to_pool",
255
+ session_id=session_id[:16],
256
+ pool_size=len(self._pool),
257
+ max_pool_size=self._max_pool_size,
258
+ )
259
+
260
+ return client, options
261
+
262
+ async def _create_client(
263
+ self,
264
+ session_id: str,
265
+ context: Any,
266
+ event_callback: Optional[Callable],
267
+ runtime: Optional[Any],
268
+ ) -> Tuple[Any, Any]:
269
+ """
270
+ Create new Claude Code client with options.
271
+
272
+ This is the expensive operation we're trying to minimize by pooling.
273
+ """
274
+ from claude_agent_sdk import ClaudeSDKClient
275
+ from .config import build_claude_options
276
+
277
+ logger.info(
278
+ "creating_new_claude_code_client",
279
+ session_id=session_id[:16],
280
+ note="Building options and connecting to SDK (expensive operation)"
281
+ )
282
+
283
+ start_time = time.time()
284
+
285
+ # Build options (includes MCP discovery, hooks, permissions, etc.)
286
+ options, active_tools, started_tools, completed_tools = await build_claude_options(
287
+ context, event_callback, runtime
288
+ )
289
+
290
+ # Create and connect client
291
+ client = ClaudeSDKClient(options=options)
292
+ await client.connect()
293
+
294
+ elapsed = time.time() - start_time
295
+
296
+ logger.info(
297
+ "claude_code_client_created",
298
+ session_id=session_id[:16],
299
+ elapsed_seconds=f"{elapsed:.2f}",
300
+ note=f"Client initialization took {elapsed:.2f}s"
301
+ )
302
+
303
+ return client, options
304
+
305
+ async def release_client(self, session_id: str) -> None:
306
+ """
307
+ Release a client back to the pool (marks as available but keeps alive).
308
+
309
+ The client remains in the pool for reuse by future executions
310
+ in the same session.
311
+
312
+ Args:
313
+ session_id: Session identifier
314
+ """
315
+ if session_id in self._pool:
316
+ logger.debug(
317
+ "client_released_to_pool",
318
+ session_id=session_id[:16],
319
+ note="Client remains in pool for reuse"
320
+ )
321
+ else:
322
+ logger.warning(
323
+ "release_client_not_in_pool",
324
+ session_id=session_id[:16]
325
+ )
326
+
327
+ async def remove_client(self, session_id: str, reason: str = "explicit_removal") -> None:
328
+ """
329
+ Explicitly remove and cleanup a client from the pool.
330
+
331
+ This disconnects the client and removes it from the pool.
332
+ Use this when you know the session is complete and won't have more followups.
333
+
334
+ Args:
335
+ session_id: Session identifier
336
+ reason: Reason for removal (for logging)
337
+ """
338
+ async with self._pool_lock:
339
+ if session_id in self._pool:
340
+ session_client = self._pool[session_id]
341
+
342
+ # Cleanup client
343
+ await self._cleanup_client(session_client)
344
+
345
+ # Remove from pool
346
+ del self._pool[session_id]
347
+
348
+ # Remove lock
349
+ if session_id in self._locks:
350
+ del self._locks[session_id]
351
+
352
+ logger.info(
353
+ "client_removed_from_pool",
354
+ session_id=session_id[:16],
355
+ reason=reason,
356
+ age_seconds=int(time.time() - session_client.created_at),
357
+ total_executions=session_client.execution_count,
358
+ )
359
+
360
+ async def _evict_lru(self) -> None:
361
+ """
362
+ Evict least recently used client to make room for new one.
363
+
364
+ This is called when the pool is full and we need to add a new client.
365
+ """
366
+ if not self._pool:
367
+ return
368
+
369
+ # Get LRU (first item in OrderedDict)
370
+ lru_session_id, lru_client = next(iter(self._pool.items()))
371
+
372
+ logger.warning(
373
+ "client_pool_eviction_lru",
374
+ evicted_session_id=lru_session_id[:16],
375
+ age_seconds=int(time.time() - lru_client.created_at),
376
+ total_executions=lru_client.execution_count,
377
+ pool_size=len(self._pool),
378
+ max_pool_size=self._max_pool_size,
379
+ reason="Pool full - evicting LRU client"
380
+ )
381
+
382
+ # Cleanup and remove
383
+ await self._cleanup_client(lru_client)
384
+ del self._pool[lru_session_id]
385
+
386
+ if lru_session_id in self._locks:
387
+ del self._locks[lru_session_id]
388
+
389
+ self._stats["clients_evicted"] += 1
390
+
391
+ async def cleanup_expired(self) -> None:
392
+ """
393
+ Remove expired clients based on TTL.
394
+
395
+ This is called periodically by the cleanup task and can also be
396
+ called manually.
397
+ """
398
+ current_time = time.time()
399
+ expired_sessions = []
400
+
401
+ async with self._pool_lock:
402
+ for session_id, session_client in self._pool.items():
403
+ age = current_time - session_client.last_used
404
+ if age > self._client_ttl_seconds:
405
+ expired_sessions.append(session_id)
406
+
407
+ if expired_sessions:
408
+ logger.info(
409
+ "cleaning_up_expired_clients",
410
+ expired_count=len(expired_sessions),
411
+ total_pool_size=len(self._pool),
412
+ )
413
+
414
+ for session_id in expired_sessions:
415
+ await self.remove_client(session_id, reason="ttl_expired")
416
+ self._stats["clients_expired"] += 1
417
+
418
+ logger.info(
419
+ "expired_clients_cleaned",
420
+ cleaned_count=len(expired_sessions),
421
+ new_pool_size=len(self._pool),
422
+ )
423
+
424
+ async def _cleanup_client(self, session_client: SessionClient) -> None:
425
+ """
426
+ Cleanup a client (disconnect and free resources).
427
+
428
+ This uses the same cleanup logic as the runtime's cleanup_sdk_client.
429
+ """
430
+ from .cleanup import cleanup_sdk_client
431
+
432
+ try:
433
+ cleanup_sdk_client(
434
+ session_client.client,
435
+ session_client.session_id,
436
+ logger
437
+ )
438
+ except Exception as e:
439
+ logger.error(
440
+ "client_cleanup_error",
441
+ session_id=session_client.session_id[:16],
442
+ error=str(e),
443
+ error_type=type(e).__name__,
444
+ )
445
+
446
+ def _get_cache_hit_rate(self) -> float:
447
+ """Calculate cache hit rate as percentage."""
448
+ total = self._stats["cache_hits"] + self._stats["cache_misses"]
449
+ if total == 0:
450
+ return 0.0
451
+ return (self._stats["cache_hits"] / total) * 100
452
+
453
+ def get_stats(self) -> Dict[str, Any]:
454
+ """
455
+ Get pool statistics for monitoring.
456
+
457
+ Returns:
458
+ Dict with cache hit/miss rates, pool size, etc.
459
+ """
460
+ return {
461
+ **self._stats,
462
+ "pool_size": len(self._pool),
463
+ "max_pool_size": self._max_pool_size,
464
+ "cache_hit_rate_percent": round(self._get_cache_hit_rate(), 2),
465
+ "average_reuses_per_client": (
466
+ round(self._stats["total_reuses"] / max(self._stats["clients_created"], 1), 2)
467
+ ),
468
+ }
469
+
470
+ def log_stats(self) -> None:
471
+ """Log current pool statistics."""
472
+ stats = self.get_stats()
473
+ logger.info(
474
+ "client_pool_statistics",
475
+ **stats,
476
+ note="Performance metrics for client pooling"
477
+ )
478
+
479
+ async def shutdown(self) -> None:
480
+ """
481
+ Shutdown the pool and cleanup all clients.
482
+
483
+ Call this when the application is shutting down.
484
+ """
485
+ logger.info("client_pool_shutting_down", pool_size=len(self._pool))
486
+
487
+ # Stop cleanup task
488
+ await self.stop_cleanup_task()
489
+
490
+ # Cleanup all clients
491
+ async with self._pool_lock:
492
+ for session_id in list(self._pool.keys()):
493
+ await self.remove_client(session_id, reason="pool_shutdown")
494
+
495
+ # Log final stats
496
+ self.log_stats()
497
+
498
+ logger.info("client_pool_shutdown_complete")
499
+
500
+
501
+ # Global pool instance (lazy initialization)
502
+ _global_pool: Optional[ClaudeCodeClientPool] = None
503
+ _pool_lock = asyncio.Lock()
504
+
505
+
506
+ async def get_global_pool() -> ClaudeCodeClientPool:
507
+ """
508
+ Get or create the global client pool instance.
509
+
510
+ This is a singleton pattern to ensure one pool per worker process.
511
+ """
512
+ global _global_pool
513
+
514
+ async with _pool_lock:
515
+ if _global_pool is None:
516
+ # Read configuration from environment
517
+ max_pool_size = int(os.getenv("CLAUDE_CODE_MAX_POOL_SIZE", "100"))
518
+ client_ttl = int(os.getenv("CLAUDE_CODE_CLIENT_TTL", "86400")) # 24 hours
519
+ cleanup_interval = int(os.getenv("CLAUDE_CODE_CLEANUP_INTERVAL", "300")) # 5 minutes
520
+
521
+ _global_pool = ClaudeCodeClientPool(
522
+ max_pool_size=max_pool_size,
523
+ client_ttl_seconds=client_ttl,
524
+ cleanup_interval_seconds=cleanup_interval,
525
+ )
526
+
527
+ logger.info("global_client_pool_created")
528
+
529
+ return _global_pool