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