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.
- control_plane_api/LICENSE +676 -0
- control_plane_api/README.md +350 -0
- control_plane_api/__init__.py +4 -0
- control_plane_api/__version__.py +8 -0
- control_plane_api/alembic/README +1 -0
- control_plane_api/alembic/env.py +121 -0
- control_plane_api/alembic/script.py.mako +28 -0
- control_plane_api/alembic/versions/2613c65c3dbe_initial_database_setup.py +32 -0
- control_plane_api/alembic/versions/2df520d4927d_merge_heads.py +28 -0
- control_plane_api/alembic/versions/43abf98d6a01_add_paused_status_to_executions.py +73 -0
- control_plane_api/alembic/versions/6289854264cb_merge_multiple_heads.py +28 -0
- control_plane_api/alembic/versions/6a4d4dc3d8dc_generate_execution_transitions.py +50 -0
- control_plane_api/alembic/versions/87d11cf0a783_add_disconnected_status_to_worker_.py +44 -0
- control_plane_api/alembic/versions/add_ephemeral_queue_support.py +85 -0
- control_plane_api/alembic/versions/add_model_type_to_llm_models.py +31 -0
- control_plane_api/alembic/versions/add_plan_executions_table.py +114 -0
- control_plane_api/alembic/versions/add_trace_span_tables.py +154 -0
- control_plane_api/alembic/versions/add_user_info_to_traces.py +36 -0
- control_plane_api/alembic/versions/adjusting_foreign_keys.py +32 -0
- control_plane_api/alembic/versions/b4983d976db2_initial_tables.py +1128 -0
- control_plane_api/alembic/versions/d181a3b40e71_rename_custom_metadata_to_metadata_in_.py +50 -0
- control_plane_api/alembic/versions/df9117888e82_add_missing_columns.py +82 -0
- control_plane_api/alembic/versions/f25de6ad895a_missing_migrations.py +34 -0
- control_plane_api/alembic/versions/f71305fb69b9_fix_ephemeral_queue_deletion_foreign_key.py +54 -0
- control_plane_api/alembic/versions/mark_local_exec_queues_as_ephemeral.py +68 -0
- control_plane_api/alembic.ini +148 -0
- control_plane_api/api/index.py +12 -0
- control_plane_api/app/__init__.py +11 -0
- control_plane_api/app/activities/__init__.py +20 -0
- control_plane_api/app/activities/agent_activities.py +384 -0
- control_plane_api/app/activities/plan_generation_activities.py +499 -0
- control_plane_api/app/activities/team_activities.py +424 -0
- control_plane_api/app/activities/temporal_cloud_activities.py +588 -0
- control_plane_api/app/config/__init__.py +35 -0
- control_plane_api/app/config/api_config.py +469 -0
- control_plane_api/app/config/config_loader.py +224 -0
- control_plane_api/app/config/model_pricing.py +323 -0
- control_plane_api/app/config/storage_config.py +159 -0
- control_plane_api/app/config.py +115 -0
- control_plane_api/app/controllers/__init__.py +0 -0
- control_plane_api/app/controllers/execution_environment_controller.py +1315 -0
- control_plane_api/app/database.py +135 -0
- control_plane_api/app/exceptions.py +408 -0
- control_plane_api/app/lib/__init__.py +11 -0
- control_plane_api/app/lib/environment.py +65 -0
- control_plane_api/app/lib/event_bus/__init__.py +17 -0
- control_plane_api/app/lib/event_bus/base.py +136 -0
- control_plane_api/app/lib/event_bus/manager.py +335 -0
- control_plane_api/app/lib/event_bus/providers/__init__.py +6 -0
- control_plane_api/app/lib/event_bus/providers/http_provider.py +166 -0
- control_plane_api/app/lib/event_bus/providers/nats_provider.py +324 -0
- control_plane_api/app/lib/event_bus/providers/redis_provider.py +233 -0
- control_plane_api/app/lib/event_bus/providers/websocket_provider.py +497 -0
- control_plane_api/app/lib/job_executor.py +330 -0
- control_plane_api/app/lib/kubiya_client.py +293 -0
- control_plane_api/app/lib/litellm_pricing.py +166 -0
- control_plane_api/app/lib/mcp_validation.py +163 -0
- control_plane_api/app/lib/nats/__init__.py +13 -0
- control_plane_api/app/lib/nats/credentials_manager.py +288 -0
- control_plane_api/app/lib/nats/listener.py +374 -0
- control_plane_api/app/lib/planning_prompt_builder.py +153 -0
- control_plane_api/app/lib/planning_tools/__init__.py +41 -0
- control_plane_api/app/lib/planning_tools/agents.py +409 -0
- control_plane_api/app/lib/planning_tools/agno_toolkit.py +836 -0
- control_plane_api/app/lib/planning_tools/base.py +119 -0
- control_plane_api/app/lib/planning_tools/cognitive_memory_tools.py +403 -0
- control_plane_api/app/lib/planning_tools/context_graph_tools.py +545 -0
- control_plane_api/app/lib/planning_tools/environments.py +218 -0
- control_plane_api/app/lib/planning_tools/knowledge.py +204 -0
- control_plane_api/app/lib/planning_tools/models.py +93 -0
- control_plane_api/app/lib/planning_tools/planning_service.py +646 -0
- control_plane_api/app/lib/planning_tools/resources.py +242 -0
- control_plane_api/app/lib/planning_tools/teams.py +334 -0
- control_plane_api/app/lib/policy_enforcer_client.py +1016 -0
- control_plane_api/app/lib/redis_client.py +803 -0
- control_plane_api/app/lib/sqlalchemy_utils.py +486 -0
- control_plane_api/app/lib/state_transition_tools/__init__.py +7 -0
- control_plane_api/app/lib/state_transition_tools/execution_context.py +388 -0
- control_plane_api/app/lib/storage/__init__.py +20 -0
- control_plane_api/app/lib/storage/base_provider.py +274 -0
- control_plane_api/app/lib/storage/provider_factory.py +157 -0
- control_plane_api/app/lib/storage/vercel_blob_provider.py +468 -0
- control_plane_api/app/lib/supabase.py +71 -0
- control_plane_api/app/lib/supabase_utils.py +138 -0
- control_plane_api/app/lib/task_planning/__init__.py +138 -0
- control_plane_api/app/lib/task_planning/agent_factory.py +308 -0
- control_plane_api/app/lib/task_planning/agents.py +389 -0
- control_plane_api/app/lib/task_planning/cache.py +218 -0
- control_plane_api/app/lib/task_planning/entity_resolver.py +273 -0
- control_plane_api/app/lib/task_planning/helpers.py +293 -0
- control_plane_api/app/lib/task_planning/hooks.py +474 -0
- control_plane_api/app/lib/task_planning/models.py +503 -0
- control_plane_api/app/lib/task_planning/plan_validator.py +166 -0
- control_plane_api/app/lib/task_planning/planning_workflow.py +2911 -0
- control_plane_api/app/lib/task_planning/runner.py +656 -0
- control_plane_api/app/lib/task_planning/streaming_hook.py +213 -0
- control_plane_api/app/lib/task_planning/workflow.py +424 -0
- control_plane_api/app/lib/templating/__init__.py +88 -0
- control_plane_api/app/lib/templating/compiler.py +278 -0
- control_plane_api/app/lib/templating/engine.py +178 -0
- control_plane_api/app/lib/templating/parsers/__init__.py +29 -0
- control_plane_api/app/lib/templating/parsers/base.py +96 -0
- control_plane_api/app/lib/templating/parsers/env.py +85 -0
- control_plane_api/app/lib/templating/parsers/graph.py +112 -0
- control_plane_api/app/lib/templating/parsers/secret.py +87 -0
- control_plane_api/app/lib/templating/parsers/simple.py +81 -0
- control_plane_api/app/lib/templating/resolver.py +366 -0
- control_plane_api/app/lib/templating/types.py +214 -0
- control_plane_api/app/lib/templating/validator.py +201 -0
- control_plane_api/app/lib/temporal_client.py +232 -0
- control_plane_api/app/lib/temporal_credentials_cache.py +178 -0
- control_plane_api/app/lib/temporal_credentials_service.py +203 -0
- control_plane_api/app/lib/validation/__init__.py +24 -0
- control_plane_api/app/lib/validation/runtime_validation.py +388 -0
- control_plane_api/app/main.py +531 -0
- control_plane_api/app/middleware/__init__.py +10 -0
- control_plane_api/app/middleware/auth.py +645 -0
- control_plane_api/app/middleware/exception_handler.py +267 -0
- control_plane_api/app/middleware/prometheus_middleware.py +173 -0
- control_plane_api/app/middleware/rate_limiting.py +384 -0
- control_plane_api/app/middleware/request_id.py +202 -0
- control_plane_api/app/models/__init__.py +40 -0
- control_plane_api/app/models/agent.py +90 -0
- control_plane_api/app/models/analytics.py +206 -0
- control_plane_api/app/models/associations.py +107 -0
- control_plane_api/app/models/auth_user.py +73 -0
- control_plane_api/app/models/context.py +161 -0
- control_plane_api/app/models/custom_integration.py +99 -0
- control_plane_api/app/models/environment.py +64 -0
- control_plane_api/app/models/execution.py +125 -0
- control_plane_api/app/models/execution_transition.py +50 -0
- control_plane_api/app/models/job.py +159 -0
- control_plane_api/app/models/llm_model.py +78 -0
- control_plane_api/app/models/orchestration.py +66 -0
- control_plane_api/app/models/plan_execution.py +102 -0
- control_plane_api/app/models/presence.py +49 -0
- control_plane_api/app/models/project.py +61 -0
- control_plane_api/app/models/project_management.py +85 -0
- control_plane_api/app/models/session.py +29 -0
- control_plane_api/app/models/skill.py +155 -0
- control_plane_api/app/models/system_tables.py +43 -0
- control_plane_api/app/models/task_planning.py +372 -0
- control_plane_api/app/models/team.py +86 -0
- control_plane_api/app/models/trace.py +257 -0
- control_plane_api/app/models/user_profile.py +54 -0
- control_plane_api/app/models/worker.py +221 -0
- control_plane_api/app/models/workflow.py +161 -0
- control_plane_api/app/models/workspace.py +50 -0
- control_plane_api/app/observability/__init__.py +177 -0
- control_plane_api/app/observability/context_logging.py +475 -0
- control_plane_api/app/observability/decorators.py +337 -0
- control_plane_api/app/observability/local_span_processor.py +702 -0
- control_plane_api/app/observability/metrics.py +303 -0
- control_plane_api/app/observability/middleware.py +246 -0
- control_plane_api/app/observability/optional.py +115 -0
- control_plane_api/app/observability/tracing.py +382 -0
- control_plane_api/app/policies/README.md +149 -0
- control_plane_api/app/policies/approved_users.rego +62 -0
- control_plane_api/app/policies/business_hours.rego +51 -0
- control_plane_api/app/policies/rate_limiting.rego +100 -0
- control_plane_api/app/policies/tool_enforcement/README.md +336 -0
- control_plane_api/app/policies/tool_enforcement/bash_command_validation.rego +71 -0
- control_plane_api/app/policies/tool_enforcement/business_hours_enforcement.rego +82 -0
- control_plane_api/app/policies/tool_enforcement/mcp_tool_allowlist.rego +58 -0
- control_plane_api/app/policies/tool_enforcement/production_safeguards.rego +80 -0
- control_plane_api/app/policies/tool_enforcement/role_based_tool_access.rego +44 -0
- control_plane_api/app/policies/tool_restrictions.rego +86 -0
- control_plane_api/app/routers/__init__.py +4 -0
- control_plane_api/app/routers/agents.py +382 -0
- control_plane_api/app/routers/agents_v2.py +1598 -0
- control_plane_api/app/routers/analytics.py +1310 -0
- control_plane_api/app/routers/auth.py +59 -0
- control_plane_api/app/routers/client_config.py +57 -0
- control_plane_api/app/routers/context_graph.py +561 -0
- control_plane_api/app/routers/context_manager.py +577 -0
- control_plane_api/app/routers/custom_integrations.py +490 -0
- control_plane_api/app/routers/enforcer.py +132 -0
- control_plane_api/app/routers/environment_context.py +252 -0
- control_plane_api/app/routers/environments.py +761 -0
- control_plane_api/app/routers/execution_environment.py +847 -0
- control_plane_api/app/routers/executions/__init__.py +28 -0
- control_plane_api/app/routers/executions/router.py +286 -0
- control_plane_api/app/routers/executions/services/__init__.py +22 -0
- control_plane_api/app/routers/executions/services/demo_worker_health.py +156 -0
- control_plane_api/app/routers/executions/services/status_service.py +420 -0
- control_plane_api/app/routers/executions/services/test_worker_health.py +480 -0
- control_plane_api/app/routers/executions/services/worker_health.py +514 -0
- control_plane_api/app/routers/executions/streaming/__init__.py +22 -0
- control_plane_api/app/routers/executions/streaming/deduplication.py +352 -0
- control_plane_api/app/routers/executions/streaming/event_buffer.py +353 -0
- control_plane_api/app/routers/executions/streaming/event_formatter.py +964 -0
- control_plane_api/app/routers/executions/streaming/history_loader.py +588 -0
- control_plane_api/app/routers/executions/streaming/live_source.py +693 -0
- control_plane_api/app/routers/executions/streaming/streamer.py +849 -0
- control_plane_api/app/routers/executions.py +4888 -0
- control_plane_api/app/routers/health.py +165 -0
- control_plane_api/app/routers/health_v2.py +394 -0
- control_plane_api/app/routers/integration_templates.py +496 -0
- control_plane_api/app/routers/integrations.py +287 -0
- control_plane_api/app/routers/jobs.py +1809 -0
- control_plane_api/app/routers/metrics.py +517 -0
- control_plane_api/app/routers/models.py +82 -0
- control_plane_api/app/routers/models_v2.py +628 -0
- control_plane_api/app/routers/plan_executions.py +1481 -0
- control_plane_api/app/routers/plan_generation_async.py +304 -0
- control_plane_api/app/routers/policies.py +669 -0
- control_plane_api/app/routers/presence.py +234 -0
- control_plane_api/app/routers/projects.py +987 -0
- control_plane_api/app/routers/runners.py +379 -0
- control_plane_api/app/routers/runtimes.py +172 -0
- control_plane_api/app/routers/secrets.py +171 -0
- control_plane_api/app/routers/skills.py +1010 -0
- control_plane_api/app/routers/skills_definitions.py +140 -0
- control_plane_api/app/routers/storage.py +456 -0
- control_plane_api/app/routers/task_planning.py +611 -0
- control_plane_api/app/routers/task_queues.py +650 -0
- control_plane_api/app/routers/team_context.py +274 -0
- control_plane_api/app/routers/teams.py +1747 -0
- control_plane_api/app/routers/templates.py +248 -0
- control_plane_api/app/routers/traces.py +571 -0
- control_plane_api/app/routers/websocket_client.py +479 -0
- control_plane_api/app/routers/websocket_executions_status.py +437 -0
- control_plane_api/app/routers/websocket_gateway.py +323 -0
- control_plane_api/app/routers/websocket_traces.py +576 -0
- control_plane_api/app/routers/worker_queues.py +2555 -0
- control_plane_api/app/routers/worker_websocket.py +419 -0
- control_plane_api/app/routers/workers.py +1004 -0
- control_plane_api/app/routers/workflows.py +204 -0
- control_plane_api/app/runtimes/__init__.py +6 -0
- control_plane_api/app/runtimes/validation.py +344 -0
- control_plane_api/app/schemas/__init__.py +1 -0
- control_plane_api/app/schemas/job_schemas.py +302 -0
- control_plane_api/app/schemas/mcp_schemas.py +311 -0
- control_plane_api/app/schemas/template_schemas.py +133 -0
- control_plane_api/app/schemas/trace_schemas.py +168 -0
- control_plane_api/app/schemas/worker_queue_observability_schemas.py +165 -0
- control_plane_api/app/services/__init__.py +1 -0
- control_plane_api/app/services/agno_planning_strategy.py +233 -0
- control_plane_api/app/services/agno_service.py +838 -0
- control_plane_api/app/services/claude_code_planning_service.py +203 -0
- control_plane_api/app/services/context_graph_client.py +224 -0
- control_plane_api/app/services/custom_integration_service.py +415 -0
- control_plane_api/app/services/integration_resolution_service.py +345 -0
- control_plane_api/app/services/litellm_service.py +394 -0
- control_plane_api/app/services/plan_generator.py +79 -0
- control_plane_api/app/services/planning_strategy.py +66 -0
- control_plane_api/app/services/planning_strategy_factory.py +118 -0
- control_plane_api/app/services/policy_service.py +615 -0
- control_plane_api/app/services/state_transition_service.py +755 -0
- control_plane_api/app/services/storage_service.py +593 -0
- control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
- control_plane_api/app/services/toolsets/context_graph_skill.py +432 -0
- control_plane_api/app/services/trace_retention.py +354 -0
- control_plane_api/app/services/worker_queue_metrics_service.py +190 -0
- control_plane_api/app/services/workflow_cancellation_manager.py +135 -0
- control_plane_api/app/services/workflow_operations_service.py +611 -0
- control_plane_api/app/skills/__init__.py +100 -0
- control_plane_api/app/skills/base.py +239 -0
- control_plane_api/app/skills/builtin/__init__.py +37 -0
- control_plane_api/app/skills/builtin/agent_communication/__init__.py +8 -0
- control_plane_api/app/skills/builtin/agent_communication/skill.py +246 -0
- control_plane_api/app/skills/builtin/code_ingestion/__init__.py +4 -0
- control_plane_api/app/skills/builtin/code_ingestion/skill.py +267 -0
- control_plane_api/app/skills/builtin/cognitive_memory/__init__.py +4 -0
- control_plane_api/app/skills/builtin/cognitive_memory/skill.py +174 -0
- control_plane_api/app/skills/builtin/contextual_awareness/__init__.py +4 -0
- control_plane_api/app/skills/builtin/contextual_awareness/skill.py +387 -0
- control_plane_api/app/skills/builtin/data_visualization/__init__.py +4 -0
- control_plane_api/app/skills/builtin/data_visualization/skill.py +154 -0
- control_plane_api/app/skills/builtin/docker/__init__.py +4 -0
- control_plane_api/app/skills/builtin/docker/skill.py +104 -0
- control_plane_api/app/skills/builtin/file_generation/__init__.py +4 -0
- control_plane_api/app/skills/builtin/file_generation/skill.py +94 -0
- control_plane_api/app/skills/builtin/file_system/__init__.py +4 -0
- control_plane_api/app/skills/builtin/file_system/skill.py +110 -0
- control_plane_api/app/skills/builtin/knowledge_api/__init__.py +5 -0
- control_plane_api/app/skills/builtin/knowledge_api/skill.py +124 -0
- control_plane_api/app/skills/builtin/python/__init__.py +4 -0
- control_plane_api/app/skills/builtin/python/skill.py +92 -0
- control_plane_api/app/skills/builtin/remote_filesystem/__init__.py +5 -0
- control_plane_api/app/skills/builtin/remote_filesystem/skill.py +170 -0
- control_plane_api/app/skills/builtin/shell/__init__.py +4 -0
- control_plane_api/app/skills/builtin/shell/skill.py +161 -0
- control_plane_api/app/skills/builtin/slack/__init__.py +3 -0
- control_plane_api/app/skills/builtin/slack/skill.py +302 -0
- control_plane_api/app/skills/builtin/workflow_executor/__init__.py +4 -0
- control_plane_api/app/skills/builtin/workflow_executor/skill.py +469 -0
- control_plane_api/app/skills/business_intelligence.py +189 -0
- control_plane_api/app/skills/config.py +63 -0
- control_plane_api/app/skills/loaders/__init__.py +14 -0
- control_plane_api/app/skills/loaders/base.py +73 -0
- control_plane_api/app/skills/loaders/filesystem_loader.py +199 -0
- control_plane_api/app/skills/registry.py +125 -0
- control_plane_api/app/utils/helpers.py +12 -0
- control_plane_api/app/utils/workflow_executor.py +354 -0
- control_plane_api/app/workflows/__init__.py +11 -0
- control_plane_api/app/workflows/agent_execution.py +520 -0
- control_plane_api/app/workflows/agent_execution_with_skills.py +223 -0
- control_plane_api/app/workflows/namespace_provisioning.py +326 -0
- control_plane_api/app/workflows/plan_generation.py +254 -0
- control_plane_api/app/workflows/team_execution.py +442 -0
- control_plane_api/scripts/seed_models.py +240 -0
- control_plane_api/scripts/validate_existing_tool_names.py +492 -0
- control_plane_api/shared/__init__.py +8 -0
- control_plane_api/shared/version.py +17 -0
- control_plane_api/test_deduplication.py +274 -0
- control_plane_api/test_executor_deduplication_e2e.py +309 -0
- control_plane_api/test_job_execution_e2e.py +283 -0
- control_plane_api/test_real_integration.py +193 -0
- control_plane_api/version.py +38 -0
- control_plane_api/worker/__init__.py +0 -0
- control_plane_api/worker/activities/__init__.py +0 -0
- control_plane_api/worker/activities/agent_activities.py +1585 -0
- control_plane_api/worker/activities/approval_activities.py +234 -0
- control_plane_api/worker/activities/job_activities.py +199 -0
- control_plane_api/worker/activities/runtime_activities.py +1167 -0
- control_plane_api/worker/activities/skill_activities.py +282 -0
- control_plane_api/worker/activities/team_activities.py +479 -0
- control_plane_api/worker/agent_runtime_server.py +370 -0
- control_plane_api/worker/binary_manager.py +333 -0
- control_plane_api/worker/config/__init__.py +31 -0
- control_plane_api/worker/config/worker_config.py +273 -0
- control_plane_api/worker/control_plane_client.py +1491 -0
- control_plane_api/worker/examples/analytics_integration_example.py +362 -0
- control_plane_api/worker/health_monitor.py +159 -0
- control_plane_api/worker/metrics.py +237 -0
- control_plane_api/worker/models/__init__.py +1 -0
- control_plane_api/worker/models/error_events.py +105 -0
- control_plane_api/worker/models/inputs.py +89 -0
- control_plane_api/worker/runtimes/__init__.py +35 -0
- control_plane_api/worker/runtimes/agent_runtime/runtime.py +485 -0
- control_plane_api/worker/runtimes/agno/__init__.py +34 -0
- control_plane_api/worker/runtimes/agno/config.py +248 -0
- control_plane_api/worker/runtimes/agno/hooks.py +385 -0
- control_plane_api/worker/runtimes/agno/mcp_builder.py +195 -0
- control_plane_api/worker/runtimes/agno/runtime.py +1063 -0
- control_plane_api/worker/runtimes/agno/utils.py +163 -0
- control_plane_api/worker/runtimes/base.py +979 -0
- control_plane_api/worker/runtimes/claude_code/__init__.py +38 -0
- control_plane_api/worker/runtimes/claude_code/cleanup.py +184 -0
- control_plane_api/worker/runtimes/claude_code/client_pool.py +529 -0
- control_plane_api/worker/runtimes/claude_code/config.py +829 -0
- control_plane_api/worker/runtimes/claude_code/hooks.py +482 -0
- control_plane_api/worker/runtimes/claude_code/litellm_proxy.py +1702 -0
- control_plane_api/worker/runtimes/claude_code/mcp_builder.py +467 -0
- control_plane_api/worker/runtimes/claude_code/mcp_discovery.py +558 -0
- control_plane_api/worker/runtimes/claude_code/runtime.py +1546 -0
- control_plane_api/worker/runtimes/claude_code/tool_mapper.py +403 -0
- control_plane_api/worker/runtimes/claude_code/utils.py +149 -0
- control_plane_api/worker/runtimes/factory.py +173 -0
- control_plane_api/worker/runtimes/model_utils.py +107 -0
- control_plane_api/worker/runtimes/validation.py +93 -0
- control_plane_api/worker/services/__init__.py +1 -0
- control_plane_api/worker/services/agent_communication_tools.py +908 -0
- control_plane_api/worker/services/agent_executor.py +485 -0
- control_plane_api/worker/services/agent_executor_v2.py +793 -0
- control_plane_api/worker/services/analytics_collector.py +457 -0
- control_plane_api/worker/services/analytics_service.py +464 -0
- control_plane_api/worker/services/approval_tools.py +310 -0
- control_plane_api/worker/services/approval_tools_agno.py +207 -0
- control_plane_api/worker/services/cancellation_manager.py +177 -0
- control_plane_api/worker/services/code_ingestion_tools.py +465 -0
- control_plane_api/worker/services/contextual_awareness_tools.py +405 -0
- control_plane_api/worker/services/data_visualization.py +834 -0
- control_plane_api/worker/services/event_publisher.py +531 -0
- control_plane_api/worker/services/jira_tools.py +257 -0
- control_plane_api/worker/services/remote_filesystem_tools.py +498 -0
- control_plane_api/worker/services/runtime_analytics.py +328 -0
- control_plane_api/worker/services/session_service.py +365 -0
- control_plane_api/worker/services/skill_context_enhancement.py +181 -0
- control_plane_api/worker/services/skill_factory.py +471 -0
- control_plane_api/worker/services/system_prompt_enhancement.py +410 -0
- control_plane_api/worker/services/team_executor.py +715 -0
- control_plane_api/worker/services/team_executor_v2.py +1866 -0
- control_plane_api/worker/services/tool_enforcement.py +254 -0
- control_plane_api/worker/services/workflow_executor/__init__.py +52 -0
- control_plane_api/worker/services/workflow_executor/event_processor.py +287 -0
- control_plane_api/worker/services/workflow_executor/event_publisher.py +210 -0
- control_plane_api/worker/services/workflow_executor/executors/__init__.py +15 -0
- control_plane_api/worker/services/workflow_executor/executors/base.py +270 -0
- control_plane_api/worker/services/workflow_executor/executors/json_executor.py +50 -0
- control_plane_api/worker/services/workflow_executor/executors/python_executor.py +50 -0
- control_plane_api/worker/services/workflow_executor/models.py +142 -0
- control_plane_api/worker/services/workflow_executor_tools.py +1748 -0
- control_plane_api/worker/skills/__init__.py +12 -0
- control_plane_api/worker/skills/builtin/context_graph_search/README.md +213 -0
- control_plane_api/worker/skills/builtin/context_graph_search/__init__.py +5 -0
- control_plane_api/worker/skills/builtin/context_graph_search/agno_impl.py +808 -0
- control_plane_api/worker/skills/builtin/context_graph_search/skill.yaml +67 -0
- control_plane_api/worker/skills/builtin/contextual_awareness/__init__.py +4 -0
- control_plane_api/worker/skills/builtin/contextual_awareness/agno_impl.py +62 -0
- control_plane_api/worker/skills/builtin/data_visualization/agno_impl.py +18 -0
- control_plane_api/worker/skills/builtin/data_visualization/skill.yaml +84 -0
- control_plane_api/worker/skills/builtin/docker/agno_impl.py +65 -0
- control_plane_api/worker/skills/builtin/docker/skill.yaml +60 -0
- control_plane_api/worker/skills/builtin/file_generation/agno_impl.py +47 -0
- control_plane_api/worker/skills/builtin/file_generation/skill.yaml +64 -0
- control_plane_api/worker/skills/builtin/file_system/agno_impl.py +32 -0
- control_plane_api/worker/skills/builtin/file_system/skill.yaml +54 -0
- control_plane_api/worker/skills/builtin/knowledge_api/__init__.py +4 -0
- control_plane_api/worker/skills/builtin/knowledge_api/agno_impl.py +50 -0
- control_plane_api/worker/skills/builtin/knowledge_api/skill.yaml +66 -0
- control_plane_api/worker/skills/builtin/python/agno_impl.py +25 -0
- control_plane_api/worker/skills/builtin/python/skill.yaml +60 -0
- control_plane_api/worker/skills/builtin/schema_fix_mixin.py +260 -0
- control_plane_api/worker/skills/builtin/shell/agno_impl.py +31 -0
- control_plane_api/worker/skills/builtin/shell/skill.yaml +60 -0
- control_plane_api/worker/skills/builtin/slack/__init__.py +3 -0
- control_plane_api/worker/skills/builtin/slack/agno_impl.py +1282 -0
- control_plane_api/worker/skills/builtin/slack/skill.yaml +276 -0
- control_plane_api/worker/skills/builtin/workflow_executor/agno_impl.py +62 -0
- control_plane_api/worker/skills/builtin/workflow_executor/skill.yaml +79 -0
- control_plane_api/worker/skills/loaders/__init__.py +5 -0
- control_plane_api/worker/skills/loaders/base.py +23 -0
- control_plane_api/worker/skills/loaders/filesystem_loader.py +357 -0
- control_plane_api/worker/skills/registry.py +208 -0
- control_plane_api/worker/tests/__init__.py +1 -0
- control_plane_api/worker/tests/conftest.py +12 -0
- control_plane_api/worker/tests/e2e/__init__.py +0 -0
- control_plane_api/worker/tests/e2e/test_context_graph_real_api.py +338 -0
- control_plane_api/worker/tests/e2e/test_context_graph_templates_e2e.py +523 -0
- control_plane_api/worker/tests/e2e/test_enforcement_e2e.py +344 -0
- control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
- control_plane_api/worker/tests/e2e/test_single_execution_mode.py +656 -0
- control_plane_api/worker/tests/integration/__init__.py +0 -0
- control_plane_api/worker/tests/integration/test_builtin_skills_fixes.py +245 -0
- control_plane_api/worker/tests/integration/test_context_graph_search_integration.py +365 -0
- control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
- control_plane_api/worker/tests/integration/test_hook_enforcement_integration.py +579 -0
- control_plane_api/worker/tests/integration/test_scheduled_job_workflow.py +237 -0
- control_plane_api/worker/tests/integration/test_system_prompt_enhancement_integration.py +343 -0
- control_plane_api/worker/tests/unit/__init__.py +0 -0
- control_plane_api/worker/tests/unit/test_builtin_skill_autoload.py +396 -0
- control_plane_api/worker/tests/unit/test_context_graph_search.py +450 -0
- control_plane_api/worker/tests/unit/test_context_graph_templates.py +403 -0
- control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
- control_plane_api/worker/tests/unit/test_control_plane_client_jobs.py +345 -0
- control_plane_api/worker/tests/unit/test_job_activities.py +353 -0
- control_plane_api/worker/tests/unit/test_skill_context_enhancement.py +321 -0
- control_plane_api/worker/tests/unit/test_system_prompt_enhancement.py +415 -0
- control_plane_api/worker/tests/unit/test_tool_enforcement.py +324 -0
- control_plane_api/worker/utils/__init__.py +1 -0
- control_plane_api/worker/utils/chunk_batcher.py +330 -0
- control_plane_api/worker/utils/environment.py +65 -0
- control_plane_api/worker/utils/error_publisher.py +260 -0
- control_plane_api/worker/utils/event_batcher.py +256 -0
- control_plane_api/worker/utils/logging_config.py +335 -0
- control_plane_api/worker/utils/logging_helper.py +326 -0
- control_plane_api/worker/utils/parameter_validator.py +120 -0
- control_plane_api/worker/utils/retry_utils.py +60 -0
- control_plane_api/worker/utils/streaming_utils.py +665 -0
- control_plane_api/worker/utils/tool_validation.py +332 -0
- control_plane_api/worker/utils/workspace_manager.py +163 -0
- control_plane_api/worker/websocket_client.py +393 -0
- control_plane_api/worker/worker.py +1297 -0
- control_plane_api/worker/workflows/__init__.py +0 -0
- control_plane_api/worker/workflows/agent_execution.py +909 -0
- control_plane_api/worker/workflows/scheduled_job_wrapper.py +332 -0
- control_plane_api/worker/workflows/team_execution.py +611 -0
- kubiya_control_plane_api-0.9.15.dist-info/METADATA +354 -0
- kubiya_control_plane_api-0.9.15.dist-info/RECORD +479 -0
- kubiya_control_plane_api-0.9.15.dist-info/WHEEL +5 -0
- kubiya_control_plane_api-0.9.15.dist-info/entry_points.txt +5 -0
- kubiya_control_plane_api-0.9.15.dist-info/licenses/LICENSE +676 -0
- kubiya_control_plane_api-0.9.15.dist-info/top_level.txt +3 -0
- scripts/__init__.py +1 -0
- scripts/migrations.py +39 -0
- scripts/seed_worker_queues.py +128 -0
- scripts/setup_agent_runtime.py +142 -0
- worker_internal/__init__.py +1 -0
- worker_internal/planner/__init__.py +1 -0
- worker_internal/planner/activities.py +1499 -0
- worker_internal/planner/agent_tools.py +197 -0
- worker_internal/planner/event_models.py +148 -0
- worker_internal/planner/event_publisher.py +67 -0
- worker_internal/planner/models.py +199 -0
- worker_internal/planner/retry_logic.py +134 -0
- worker_internal/planner/worker.py +300 -0
- 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
|