kubiya-control-plane-api 0.9.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (479) hide show
  1. control_plane_api/LICENSE +676 -0
  2. control_plane_api/README.md +350 -0
  3. control_plane_api/__init__.py +4 -0
  4. control_plane_api/__version__.py +8 -0
  5. control_plane_api/alembic/README +1 -0
  6. control_plane_api/alembic/env.py +121 -0
  7. control_plane_api/alembic/script.py.mako +28 -0
  8. control_plane_api/alembic/versions/2613c65c3dbe_initial_database_setup.py +32 -0
  9. control_plane_api/alembic/versions/2df520d4927d_merge_heads.py +28 -0
  10. control_plane_api/alembic/versions/43abf98d6a01_add_paused_status_to_executions.py +73 -0
  11. control_plane_api/alembic/versions/6289854264cb_merge_multiple_heads.py +28 -0
  12. control_plane_api/alembic/versions/6a4d4dc3d8dc_generate_execution_transitions.py +50 -0
  13. control_plane_api/alembic/versions/87d11cf0a783_add_disconnected_status_to_worker_.py +44 -0
  14. control_plane_api/alembic/versions/add_ephemeral_queue_support.py +85 -0
  15. control_plane_api/alembic/versions/add_model_type_to_llm_models.py +31 -0
  16. control_plane_api/alembic/versions/add_plan_executions_table.py +114 -0
  17. control_plane_api/alembic/versions/add_trace_span_tables.py +154 -0
  18. control_plane_api/alembic/versions/add_user_info_to_traces.py +36 -0
  19. control_plane_api/alembic/versions/adjusting_foreign_keys.py +32 -0
  20. control_plane_api/alembic/versions/b4983d976db2_initial_tables.py +1128 -0
  21. control_plane_api/alembic/versions/d181a3b40e71_rename_custom_metadata_to_metadata_in_.py +50 -0
  22. control_plane_api/alembic/versions/df9117888e82_add_missing_columns.py +82 -0
  23. control_plane_api/alembic/versions/f25de6ad895a_missing_migrations.py +34 -0
  24. control_plane_api/alembic/versions/f71305fb69b9_fix_ephemeral_queue_deletion_foreign_key.py +54 -0
  25. control_plane_api/alembic/versions/mark_local_exec_queues_as_ephemeral.py +68 -0
  26. control_plane_api/alembic.ini +148 -0
  27. control_plane_api/api/index.py +12 -0
  28. control_plane_api/app/__init__.py +11 -0
  29. control_plane_api/app/activities/__init__.py +20 -0
  30. control_plane_api/app/activities/agent_activities.py +384 -0
  31. control_plane_api/app/activities/plan_generation_activities.py +499 -0
  32. control_plane_api/app/activities/team_activities.py +424 -0
  33. control_plane_api/app/activities/temporal_cloud_activities.py +588 -0
  34. control_plane_api/app/config/__init__.py +35 -0
  35. control_plane_api/app/config/api_config.py +469 -0
  36. control_plane_api/app/config/config_loader.py +224 -0
  37. control_plane_api/app/config/model_pricing.py +323 -0
  38. control_plane_api/app/config/storage_config.py +159 -0
  39. control_plane_api/app/config.py +115 -0
  40. control_plane_api/app/controllers/__init__.py +0 -0
  41. control_plane_api/app/controllers/execution_environment_controller.py +1315 -0
  42. control_plane_api/app/database.py +135 -0
  43. control_plane_api/app/exceptions.py +408 -0
  44. control_plane_api/app/lib/__init__.py +11 -0
  45. control_plane_api/app/lib/environment.py +65 -0
  46. control_plane_api/app/lib/event_bus/__init__.py +17 -0
  47. control_plane_api/app/lib/event_bus/base.py +136 -0
  48. control_plane_api/app/lib/event_bus/manager.py +335 -0
  49. control_plane_api/app/lib/event_bus/providers/__init__.py +6 -0
  50. control_plane_api/app/lib/event_bus/providers/http_provider.py +166 -0
  51. control_plane_api/app/lib/event_bus/providers/nats_provider.py +324 -0
  52. control_plane_api/app/lib/event_bus/providers/redis_provider.py +233 -0
  53. control_plane_api/app/lib/event_bus/providers/websocket_provider.py +497 -0
  54. control_plane_api/app/lib/job_executor.py +330 -0
  55. control_plane_api/app/lib/kubiya_client.py +293 -0
  56. control_plane_api/app/lib/litellm_pricing.py +166 -0
  57. control_plane_api/app/lib/mcp_validation.py +163 -0
  58. control_plane_api/app/lib/nats/__init__.py +13 -0
  59. control_plane_api/app/lib/nats/credentials_manager.py +288 -0
  60. control_plane_api/app/lib/nats/listener.py +374 -0
  61. control_plane_api/app/lib/planning_prompt_builder.py +153 -0
  62. control_plane_api/app/lib/planning_tools/__init__.py +41 -0
  63. control_plane_api/app/lib/planning_tools/agents.py +409 -0
  64. control_plane_api/app/lib/planning_tools/agno_toolkit.py +836 -0
  65. control_plane_api/app/lib/planning_tools/base.py +119 -0
  66. control_plane_api/app/lib/planning_tools/cognitive_memory_tools.py +403 -0
  67. control_plane_api/app/lib/planning_tools/context_graph_tools.py +545 -0
  68. control_plane_api/app/lib/planning_tools/environments.py +218 -0
  69. control_plane_api/app/lib/planning_tools/knowledge.py +204 -0
  70. control_plane_api/app/lib/planning_tools/models.py +93 -0
  71. control_plane_api/app/lib/planning_tools/planning_service.py +646 -0
  72. control_plane_api/app/lib/planning_tools/resources.py +242 -0
  73. control_plane_api/app/lib/planning_tools/teams.py +334 -0
  74. control_plane_api/app/lib/policy_enforcer_client.py +1016 -0
  75. control_plane_api/app/lib/redis_client.py +803 -0
  76. control_plane_api/app/lib/sqlalchemy_utils.py +486 -0
  77. control_plane_api/app/lib/state_transition_tools/__init__.py +7 -0
  78. control_plane_api/app/lib/state_transition_tools/execution_context.py +388 -0
  79. control_plane_api/app/lib/storage/__init__.py +20 -0
  80. control_plane_api/app/lib/storage/base_provider.py +274 -0
  81. control_plane_api/app/lib/storage/provider_factory.py +157 -0
  82. control_plane_api/app/lib/storage/vercel_blob_provider.py +468 -0
  83. control_plane_api/app/lib/supabase.py +71 -0
  84. control_plane_api/app/lib/supabase_utils.py +138 -0
  85. control_plane_api/app/lib/task_planning/__init__.py +138 -0
  86. control_plane_api/app/lib/task_planning/agent_factory.py +308 -0
  87. control_plane_api/app/lib/task_planning/agents.py +389 -0
  88. control_plane_api/app/lib/task_planning/cache.py +218 -0
  89. control_plane_api/app/lib/task_planning/entity_resolver.py +273 -0
  90. control_plane_api/app/lib/task_planning/helpers.py +293 -0
  91. control_plane_api/app/lib/task_planning/hooks.py +474 -0
  92. control_plane_api/app/lib/task_planning/models.py +503 -0
  93. control_plane_api/app/lib/task_planning/plan_validator.py +166 -0
  94. control_plane_api/app/lib/task_planning/planning_workflow.py +2911 -0
  95. control_plane_api/app/lib/task_planning/runner.py +656 -0
  96. control_plane_api/app/lib/task_planning/streaming_hook.py +213 -0
  97. control_plane_api/app/lib/task_planning/workflow.py +424 -0
  98. control_plane_api/app/lib/templating/__init__.py +88 -0
  99. control_plane_api/app/lib/templating/compiler.py +278 -0
  100. control_plane_api/app/lib/templating/engine.py +178 -0
  101. control_plane_api/app/lib/templating/parsers/__init__.py +29 -0
  102. control_plane_api/app/lib/templating/parsers/base.py +96 -0
  103. control_plane_api/app/lib/templating/parsers/env.py +85 -0
  104. control_plane_api/app/lib/templating/parsers/graph.py +112 -0
  105. control_plane_api/app/lib/templating/parsers/secret.py +87 -0
  106. control_plane_api/app/lib/templating/parsers/simple.py +81 -0
  107. control_plane_api/app/lib/templating/resolver.py +366 -0
  108. control_plane_api/app/lib/templating/types.py +214 -0
  109. control_plane_api/app/lib/templating/validator.py +201 -0
  110. control_plane_api/app/lib/temporal_client.py +232 -0
  111. control_plane_api/app/lib/temporal_credentials_cache.py +178 -0
  112. control_plane_api/app/lib/temporal_credentials_service.py +203 -0
  113. control_plane_api/app/lib/validation/__init__.py +24 -0
  114. control_plane_api/app/lib/validation/runtime_validation.py +388 -0
  115. control_plane_api/app/main.py +531 -0
  116. control_plane_api/app/middleware/__init__.py +10 -0
  117. control_plane_api/app/middleware/auth.py +645 -0
  118. control_plane_api/app/middleware/exception_handler.py +267 -0
  119. control_plane_api/app/middleware/prometheus_middleware.py +173 -0
  120. control_plane_api/app/middleware/rate_limiting.py +384 -0
  121. control_plane_api/app/middleware/request_id.py +202 -0
  122. control_plane_api/app/models/__init__.py +40 -0
  123. control_plane_api/app/models/agent.py +90 -0
  124. control_plane_api/app/models/analytics.py +206 -0
  125. control_plane_api/app/models/associations.py +107 -0
  126. control_plane_api/app/models/auth_user.py +73 -0
  127. control_plane_api/app/models/context.py +161 -0
  128. control_plane_api/app/models/custom_integration.py +99 -0
  129. control_plane_api/app/models/environment.py +64 -0
  130. control_plane_api/app/models/execution.py +125 -0
  131. control_plane_api/app/models/execution_transition.py +50 -0
  132. control_plane_api/app/models/job.py +159 -0
  133. control_plane_api/app/models/llm_model.py +78 -0
  134. control_plane_api/app/models/orchestration.py +66 -0
  135. control_plane_api/app/models/plan_execution.py +102 -0
  136. control_plane_api/app/models/presence.py +49 -0
  137. control_plane_api/app/models/project.py +61 -0
  138. control_plane_api/app/models/project_management.py +85 -0
  139. control_plane_api/app/models/session.py +29 -0
  140. control_plane_api/app/models/skill.py +155 -0
  141. control_plane_api/app/models/system_tables.py +43 -0
  142. control_plane_api/app/models/task_planning.py +372 -0
  143. control_plane_api/app/models/team.py +86 -0
  144. control_plane_api/app/models/trace.py +257 -0
  145. control_plane_api/app/models/user_profile.py +54 -0
  146. control_plane_api/app/models/worker.py +221 -0
  147. control_plane_api/app/models/workflow.py +161 -0
  148. control_plane_api/app/models/workspace.py +50 -0
  149. control_plane_api/app/observability/__init__.py +177 -0
  150. control_plane_api/app/observability/context_logging.py +475 -0
  151. control_plane_api/app/observability/decorators.py +337 -0
  152. control_plane_api/app/observability/local_span_processor.py +702 -0
  153. control_plane_api/app/observability/metrics.py +303 -0
  154. control_plane_api/app/observability/middleware.py +246 -0
  155. control_plane_api/app/observability/optional.py +115 -0
  156. control_plane_api/app/observability/tracing.py +382 -0
  157. control_plane_api/app/policies/README.md +149 -0
  158. control_plane_api/app/policies/approved_users.rego +62 -0
  159. control_plane_api/app/policies/business_hours.rego +51 -0
  160. control_plane_api/app/policies/rate_limiting.rego +100 -0
  161. control_plane_api/app/policies/tool_enforcement/README.md +336 -0
  162. control_plane_api/app/policies/tool_enforcement/bash_command_validation.rego +71 -0
  163. control_plane_api/app/policies/tool_enforcement/business_hours_enforcement.rego +82 -0
  164. control_plane_api/app/policies/tool_enforcement/mcp_tool_allowlist.rego +58 -0
  165. control_plane_api/app/policies/tool_enforcement/production_safeguards.rego +80 -0
  166. control_plane_api/app/policies/tool_enforcement/role_based_tool_access.rego +44 -0
  167. control_plane_api/app/policies/tool_restrictions.rego +86 -0
  168. control_plane_api/app/routers/__init__.py +4 -0
  169. control_plane_api/app/routers/agents.py +382 -0
  170. control_plane_api/app/routers/agents_v2.py +1598 -0
  171. control_plane_api/app/routers/analytics.py +1310 -0
  172. control_plane_api/app/routers/auth.py +59 -0
  173. control_plane_api/app/routers/client_config.py +57 -0
  174. control_plane_api/app/routers/context_graph.py +561 -0
  175. control_plane_api/app/routers/context_manager.py +577 -0
  176. control_plane_api/app/routers/custom_integrations.py +490 -0
  177. control_plane_api/app/routers/enforcer.py +132 -0
  178. control_plane_api/app/routers/environment_context.py +252 -0
  179. control_plane_api/app/routers/environments.py +761 -0
  180. control_plane_api/app/routers/execution_environment.py +847 -0
  181. control_plane_api/app/routers/executions/__init__.py +28 -0
  182. control_plane_api/app/routers/executions/router.py +286 -0
  183. control_plane_api/app/routers/executions/services/__init__.py +22 -0
  184. control_plane_api/app/routers/executions/services/demo_worker_health.py +156 -0
  185. control_plane_api/app/routers/executions/services/status_service.py +420 -0
  186. control_plane_api/app/routers/executions/services/test_worker_health.py +480 -0
  187. control_plane_api/app/routers/executions/services/worker_health.py +514 -0
  188. control_plane_api/app/routers/executions/streaming/__init__.py +22 -0
  189. control_plane_api/app/routers/executions/streaming/deduplication.py +352 -0
  190. control_plane_api/app/routers/executions/streaming/event_buffer.py +353 -0
  191. control_plane_api/app/routers/executions/streaming/event_formatter.py +964 -0
  192. control_plane_api/app/routers/executions/streaming/history_loader.py +588 -0
  193. control_plane_api/app/routers/executions/streaming/live_source.py +693 -0
  194. control_plane_api/app/routers/executions/streaming/streamer.py +849 -0
  195. control_plane_api/app/routers/executions.py +4888 -0
  196. control_plane_api/app/routers/health.py +165 -0
  197. control_plane_api/app/routers/health_v2.py +394 -0
  198. control_plane_api/app/routers/integration_templates.py +496 -0
  199. control_plane_api/app/routers/integrations.py +287 -0
  200. control_plane_api/app/routers/jobs.py +1809 -0
  201. control_plane_api/app/routers/metrics.py +517 -0
  202. control_plane_api/app/routers/models.py +82 -0
  203. control_plane_api/app/routers/models_v2.py +628 -0
  204. control_plane_api/app/routers/plan_executions.py +1481 -0
  205. control_plane_api/app/routers/plan_generation_async.py +304 -0
  206. control_plane_api/app/routers/policies.py +669 -0
  207. control_plane_api/app/routers/presence.py +234 -0
  208. control_plane_api/app/routers/projects.py +987 -0
  209. control_plane_api/app/routers/runners.py +379 -0
  210. control_plane_api/app/routers/runtimes.py +172 -0
  211. control_plane_api/app/routers/secrets.py +171 -0
  212. control_plane_api/app/routers/skills.py +1010 -0
  213. control_plane_api/app/routers/skills_definitions.py +140 -0
  214. control_plane_api/app/routers/storage.py +456 -0
  215. control_plane_api/app/routers/task_planning.py +611 -0
  216. control_plane_api/app/routers/task_queues.py +650 -0
  217. control_plane_api/app/routers/team_context.py +274 -0
  218. control_plane_api/app/routers/teams.py +1747 -0
  219. control_plane_api/app/routers/templates.py +248 -0
  220. control_plane_api/app/routers/traces.py +571 -0
  221. control_plane_api/app/routers/websocket_client.py +479 -0
  222. control_plane_api/app/routers/websocket_executions_status.py +437 -0
  223. control_plane_api/app/routers/websocket_gateway.py +323 -0
  224. control_plane_api/app/routers/websocket_traces.py +576 -0
  225. control_plane_api/app/routers/worker_queues.py +2555 -0
  226. control_plane_api/app/routers/worker_websocket.py +419 -0
  227. control_plane_api/app/routers/workers.py +1004 -0
  228. control_plane_api/app/routers/workflows.py +204 -0
  229. control_plane_api/app/runtimes/__init__.py +6 -0
  230. control_plane_api/app/runtimes/validation.py +344 -0
  231. control_plane_api/app/schemas/__init__.py +1 -0
  232. control_plane_api/app/schemas/job_schemas.py +302 -0
  233. control_plane_api/app/schemas/mcp_schemas.py +311 -0
  234. control_plane_api/app/schemas/template_schemas.py +133 -0
  235. control_plane_api/app/schemas/trace_schemas.py +168 -0
  236. control_plane_api/app/schemas/worker_queue_observability_schemas.py +165 -0
  237. control_plane_api/app/services/__init__.py +1 -0
  238. control_plane_api/app/services/agno_planning_strategy.py +233 -0
  239. control_plane_api/app/services/agno_service.py +838 -0
  240. control_plane_api/app/services/claude_code_planning_service.py +203 -0
  241. control_plane_api/app/services/context_graph_client.py +224 -0
  242. control_plane_api/app/services/custom_integration_service.py +415 -0
  243. control_plane_api/app/services/integration_resolution_service.py +345 -0
  244. control_plane_api/app/services/litellm_service.py +394 -0
  245. control_plane_api/app/services/plan_generator.py +79 -0
  246. control_plane_api/app/services/planning_strategy.py +66 -0
  247. control_plane_api/app/services/planning_strategy_factory.py +118 -0
  248. control_plane_api/app/services/policy_service.py +615 -0
  249. control_plane_api/app/services/state_transition_service.py +755 -0
  250. control_plane_api/app/services/storage_service.py +593 -0
  251. control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
  252. control_plane_api/app/services/toolsets/context_graph_skill.py +432 -0
  253. control_plane_api/app/services/trace_retention.py +354 -0
  254. control_plane_api/app/services/worker_queue_metrics_service.py +190 -0
  255. control_plane_api/app/services/workflow_cancellation_manager.py +135 -0
  256. control_plane_api/app/services/workflow_operations_service.py +611 -0
  257. control_plane_api/app/skills/__init__.py +100 -0
  258. control_plane_api/app/skills/base.py +239 -0
  259. control_plane_api/app/skills/builtin/__init__.py +37 -0
  260. control_plane_api/app/skills/builtin/agent_communication/__init__.py +8 -0
  261. control_plane_api/app/skills/builtin/agent_communication/skill.py +246 -0
  262. control_plane_api/app/skills/builtin/code_ingestion/__init__.py +4 -0
  263. control_plane_api/app/skills/builtin/code_ingestion/skill.py +267 -0
  264. control_plane_api/app/skills/builtin/cognitive_memory/__init__.py +4 -0
  265. control_plane_api/app/skills/builtin/cognitive_memory/skill.py +174 -0
  266. control_plane_api/app/skills/builtin/contextual_awareness/__init__.py +4 -0
  267. control_plane_api/app/skills/builtin/contextual_awareness/skill.py +387 -0
  268. control_plane_api/app/skills/builtin/data_visualization/__init__.py +4 -0
  269. control_plane_api/app/skills/builtin/data_visualization/skill.py +154 -0
  270. control_plane_api/app/skills/builtin/docker/__init__.py +4 -0
  271. control_plane_api/app/skills/builtin/docker/skill.py +104 -0
  272. control_plane_api/app/skills/builtin/file_generation/__init__.py +4 -0
  273. control_plane_api/app/skills/builtin/file_generation/skill.py +94 -0
  274. control_plane_api/app/skills/builtin/file_system/__init__.py +4 -0
  275. control_plane_api/app/skills/builtin/file_system/skill.py +110 -0
  276. control_plane_api/app/skills/builtin/knowledge_api/__init__.py +5 -0
  277. control_plane_api/app/skills/builtin/knowledge_api/skill.py +124 -0
  278. control_plane_api/app/skills/builtin/python/__init__.py +4 -0
  279. control_plane_api/app/skills/builtin/python/skill.py +92 -0
  280. control_plane_api/app/skills/builtin/remote_filesystem/__init__.py +5 -0
  281. control_plane_api/app/skills/builtin/remote_filesystem/skill.py +170 -0
  282. control_plane_api/app/skills/builtin/shell/__init__.py +4 -0
  283. control_plane_api/app/skills/builtin/shell/skill.py +161 -0
  284. control_plane_api/app/skills/builtin/slack/__init__.py +3 -0
  285. control_plane_api/app/skills/builtin/slack/skill.py +302 -0
  286. control_plane_api/app/skills/builtin/workflow_executor/__init__.py +4 -0
  287. control_plane_api/app/skills/builtin/workflow_executor/skill.py +469 -0
  288. control_plane_api/app/skills/business_intelligence.py +189 -0
  289. control_plane_api/app/skills/config.py +63 -0
  290. control_plane_api/app/skills/loaders/__init__.py +14 -0
  291. control_plane_api/app/skills/loaders/base.py +73 -0
  292. control_plane_api/app/skills/loaders/filesystem_loader.py +199 -0
  293. control_plane_api/app/skills/registry.py +125 -0
  294. control_plane_api/app/utils/helpers.py +12 -0
  295. control_plane_api/app/utils/workflow_executor.py +354 -0
  296. control_plane_api/app/workflows/__init__.py +11 -0
  297. control_plane_api/app/workflows/agent_execution.py +520 -0
  298. control_plane_api/app/workflows/agent_execution_with_skills.py +223 -0
  299. control_plane_api/app/workflows/namespace_provisioning.py +326 -0
  300. control_plane_api/app/workflows/plan_generation.py +254 -0
  301. control_plane_api/app/workflows/team_execution.py +442 -0
  302. control_plane_api/scripts/seed_models.py +240 -0
  303. control_plane_api/scripts/validate_existing_tool_names.py +492 -0
  304. control_plane_api/shared/__init__.py +8 -0
  305. control_plane_api/shared/version.py +17 -0
  306. control_plane_api/test_deduplication.py +274 -0
  307. control_plane_api/test_executor_deduplication_e2e.py +309 -0
  308. control_plane_api/test_job_execution_e2e.py +283 -0
  309. control_plane_api/test_real_integration.py +193 -0
  310. control_plane_api/version.py +38 -0
  311. control_plane_api/worker/__init__.py +0 -0
  312. control_plane_api/worker/activities/__init__.py +0 -0
  313. control_plane_api/worker/activities/agent_activities.py +1585 -0
  314. control_plane_api/worker/activities/approval_activities.py +234 -0
  315. control_plane_api/worker/activities/job_activities.py +199 -0
  316. control_plane_api/worker/activities/runtime_activities.py +1167 -0
  317. control_plane_api/worker/activities/skill_activities.py +282 -0
  318. control_plane_api/worker/activities/team_activities.py +479 -0
  319. control_plane_api/worker/agent_runtime_server.py +370 -0
  320. control_plane_api/worker/binary_manager.py +333 -0
  321. control_plane_api/worker/config/__init__.py +31 -0
  322. control_plane_api/worker/config/worker_config.py +273 -0
  323. control_plane_api/worker/control_plane_client.py +1491 -0
  324. control_plane_api/worker/examples/analytics_integration_example.py +362 -0
  325. control_plane_api/worker/health_monitor.py +159 -0
  326. control_plane_api/worker/metrics.py +237 -0
  327. control_plane_api/worker/models/__init__.py +1 -0
  328. control_plane_api/worker/models/error_events.py +105 -0
  329. control_plane_api/worker/models/inputs.py +89 -0
  330. control_plane_api/worker/runtimes/__init__.py +35 -0
  331. control_plane_api/worker/runtimes/agent_runtime/runtime.py +485 -0
  332. control_plane_api/worker/runtimes/agno/__init__.py +34 -0
  333. control_plane_api/worker/runtimes/agno/config.py +248 -0
  334. control_plane_api/worker/runtimes/agno/hooks.py +385 -0
  335. control_plane_api/worker/runtimes/agno/mcp_builder.py +195 -0
  336. control_plane_api/worker/runtimes/agno/runtime.py +1063 -0
  337. control_plane_api/worker/runtimes/agno/utils.py +163 -0
  338. control_plane_api/worker/runtimes/base.py +979 -0
  339. control_plane_api/worker/runtimes/claude_code/__init__.py +38 -0
  340. control_plane_api/worker/runtimes/claude_code/cleanup.py +184 -0
  341. control_plane_api/worker/runtimes/claude_code/client_pool.py +529 -0
  342. control_plane_api/worker/runtimes/claude_code/config.py +829 -0
  343. control_plane_api/worker/runtimes/claude_code/hooks.py +482 -0
  344. control_plane_api/worker/runtimes/claude_code/litellm_proxy.py +1702 -0
  345. control_plane_api/worker/runtimes/claude_code/mcp_builder.py +467 -0
  346. control_plane_api/worker/runtimes/claude_code/mcp_discovery.py +558 -0
  347. control_plane_api/worker/runtimes/claude_code/runtime.py +1546 -0
  348. control_plane_api/worker/runtimes/claude_code/tool_mapper.py +403 -0
  349. control_plane_api/worker/runtimes/claude_code/utils.py +149 -0
  350. control_plane_api/worker/runtimes/factory.py +173 -0
  351. control_plane_api/worker/runtimes/model_utils.py +107 -0
  352. control_plane_api/worker/runtimes/validation.py +93 -0
  353. control_plane_api/worker/services/__init__.py +1 -0
  354. control_plane_api/worker/services/agent_communication_tools.py +908 -0
  355. control_plane_api/worker/services/agent_executor.py +485 -0
  356. control_plane_api/worker/services/agent_executor_v2.py +793 -0
  357. control_plane_api/worker/services/analytics_collector.py +457 -0
  358. control_plane_api/worker/services/analytics_service.py +464 -0
  359. control_plane_api/worker/services/approval_tools.py +310 -0
  360. control_plane_api/worker/services/approval_tools_agno.py +207 -0
  361. control_plane_api/worker/services/cancellation_manager.py +177 -0
  362. control_plane_api/worker/services/code_ingestion_tools.py +465 -0
  363. control_plane_api/worker/services/contextual_awareness_tools.py +405 -0
  364. control_plane_api/worker/services/data_visualization.py +834 -0
  365. control_plane_api/worker/services/event_publisher.py +531 -0
  366. control_plane_api/worker/services/jira_tools.py +257 -0
  367. control_plane_api/worker/services/remote_filesystem_tools.py +498 -0
  368. control_plane_api/worker/services/runtime_analytics.py +328 -0
  369. control_plane_api/worker/services/session_service.py +365 -0
  370. control_plane_api/worker/services/skill_context_enhancement.py +181 -0
  371. control_plane_api/worker/services/skill_factory.py +471 -0
  372. control_plane_api/worker/services/system_prompt_enhancement.py +410 -0
  373. control_plane_api/worker/services/team_executor.py +715 -0
  374. control_plane_api/worker/services/team_executor_v2.py +1866 -0
  375. control_plane_api/worker/services/tool_enforcement.py +254 -0
  376. control_plane_api/worker/services/workflow_executor/__init__.py +52 -0
  377. control_plane_api/worker/services/workflow_executor/event_processor.py +287 -0
  378. control_plane_api/worker/services/workflow_executor/event_publisher.py +210 -0
  379. control_plane_api/worker/services/workflow_executor/executors/__init__.py +15 -0
  380. control_plane_api/worker/services/workflow_executor/executors/base.py +270 -0
  381. control_plane_api/worker/services/workflow_executor/executors/json_executor.py +50 -0
  382. control_plane_api/worker/services/workflow_executor/executors/python_executor.py +50 -0
  383. control_plane_api/worker/services/workflow_executor/models.py +142 -0
  384. control_plane_api/worker/services/workflow_executor_tools.py +1748 -0
  385. control_plane_api/worker/skills/__init__.py +12 -0
  386. control_plane_api/worker/skills/builtin/context_graph_search/README.md +213 -0
  387. control_plane_api/worker/skills/builtin/context_graph_search/__init__.py +5 -0
  388. control_plane_api/worker/skills/builtin/context_graph_search/agno_impl.py +808 -0
  389. control_plane_api/worker/skills/builtin/context_graph_search/skill.yaml +67 -0
  390. control_plane_api/worker/skills/builtin/contextual_awareness/__init__.py +4 -0
  391. control_plane_api/worker/skills/builtin/contextual_awareness/agno_impl.py +62 -0
  392. control_plane_api/worker/skills/builtin/data_visualization/agno_impl.py +18 -0
  393. control_plane_api/worker/skills/builtin/data_visualization/skill.yaml +84 -0
  394. control_plane_api/worker/skills/builtin/docker/agno_impl.py +65 -0
  395. control_plane_api/worker/skills/builtin/docker/skill.yaml +60 -0
  396. control_plane_api/worker/skills/builtin/file_generation/agno_impl.py +47 -0
  397. control_plane_api/worker/skills/builtin/file_generation/skill.yaml +64 -0
  398. control_plane_api/worker/skills/builtin/file_system/agno_impl.py +32 -0
  399. control_plane_api/worker/skills/builtin/file_system/skill.yaml +54 -0
  400. control_plane_api/worker/skills/builtin/knowledge_api/__init__.py +4 -0
  401. control_plane_api/worker/skills/builtin/knowledge_api/agno_impl.py +50 -0
  402. control_plane_api/worker/skills/builtin/knowledge_api/skill.yaml +66 -0
  403. control_plane_api/worker/skills/builtin/python/agno_impl.py +25 -0
  404. control_plane_api/worker/skills/builtin/python/skill.yaml +60 -0
  405. control_plane_api/worker/skills/builtin/schema_fix_mixin.py +260 -0
  406. control_plane_api/worker/skills/builtin/shell/agno_impl.py +31 -0
  407. control_plane_api/worker/skills/builtin/shell/skill.yaml +60 -0
  408. control_plane_api/worker/skills/builtin/slack/__init__.py +3 -0
  409. control_plane_api/worker/skills/builtin/slack/agno_impl.py +1282 -0
  410. control_plane_api/worker/skills/builtin/slack/skill.yaml +276 -0
  411. control_plane_api/worker/skills/builtin/workflow_executor/agno_impl.py +62 -0
  412. control_plane_api/worker/skills/builtin/workflow_executor/skill.yaml +79 -0
  413. control_plane_api/worker/skills/loaders/__init__.py +5 -0
  414. control_plane_api/worker/skills/loaders/base.py +23 -0
  415. control_plane_api/worker/skills/loaders/filesystem_loader.py +357 -0
  416. control_plane_api/worker/skills/registry.py +208 -0
  417. control_plane_api/worker/tests/__init__.py +1 -0
  418. control_plane_api/worker/tests/conftest.py +12 -0
  419. control_plane_api/worker/tests/e2e/__init__.py +0 -0
  420. control_plane_api/worker/tests/e2e/test_context_graph_real_api.py +338 -0
  421. control_plane_api/worker/tests/e2e/test_context_graph_templates_e2e.py +523 -0
  422. control_plane_api/worker/tests/e2e/test_enforcement_e2e.py +344 -0
  423. control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
  424. control_plane_api/worker/tests/e2e/test_single_execution_mode.py +656 -0
  425. control_plane_api/worker/tests/integration/__init__.py +0 -0
  426. control_plane_api/worker/tests/integration/test_builtin_skills_fixes.py +245 -0
  427. control_plane_api/worker/tests/integration/test_context_graph_search_integration.py +365 -0
  428. control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
  429. control_plane_api/worker/tests/integration/test_hook_enforcement_integration.py +579 -0
  430. control_plane_api/worker/tests/integration/test_scheduled_job_workflow.py +237 -0
  431. control_plane_api/worker/tests/integration/test_system_prompt_enhancement_integration.py +343 -0
  432. control_plane_api/worker/tests/unit/__init__.py +0 -0
  433. control_plane_api/worker/tests/unit/test_builtin_skill_autoload.py +396 -0
  434. control_plane_api/worker/tests/unit/test_context_graph_search.py +450 -0
  435. control_plane_api/worker/tests/unit/test_context_graph_templates.py +403 -0
  436. control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
  437. control_plane_api/worker/tests/unit/test_control_plane_client_jobs.py +345 -0
  438. control_plane_api/worker/tests/unit/test_job_activities.py +353 -0
  439. control_plane_api/worker/tests/unit/test_skill_context_enhancement.py +321 -0
  440. control_plane_api/worker/tests/unit/test_system_prompt_enhancement.py +415 -0
  441. control_plane_api/worker/tests/unit/test_tool_enforcement.py +324 -0
  442. control_plane_api/worker/utils/__init__.py +1 -0
  443. control_plane_api/worker/utils/chunk_batcher.py +330 -0
  444. control_plane_api/worker/utils/environment.py +65 -0
  445. control_plane_api/worker/utils/error_publisher.py +260 -0
  446. control_plane_api/worker/utils/event_batcher.py +256 -0
  447. control_plane_api/worker/utils/logging_config.py +335 -0
  448. control_plane_api/worker/utils/logging_helper.py +326 -0
  449. control_plane_api/worker/utils/parameter_validator.py +120 -0
  450. control_plane_api/worker/utils/retry_utils.py +60 -0
  451. control_plane_api/worker/utils/streaming_utils.py +665 -0
  452. control_plane_api/worker/utils/tool_validation.py +332 -0
  453. control_plane_api/worker/utils/workspace_manager.py +163 -0
  454. control_plane_api/worker/websocket_client.py +393 -0
  455. control_plane_api/worker/worker.py +1297 -0
  456. control_plane_api/worker/workflows/__init__.py +0 -0
  457. control_plane_api/worker/workflows/agent_execution.py +909 -0
  458. control_plane_api/worker/workflows/scheduled_job_wrapper.py +332 -0
  459. control_plane_api/worker/workflows/team_execution.py +611 -0
  460. kubiya_control_plane_api-0.9.15.dist-info/METADATA +354 -0
  461. kubiya_control_plane_api-0.9.15.dist-info/RECORD +479 -0
  462. kubiya_control_plane_api-0.9.15.dist-info/WHEEL +5 -0
  463. kubiya_control_plane_api-0.9.15.dist-info/entry_points.txt +5 -0
  464. kubiya_control_plane_api-0.9.15.dist-info/licenses/LICENSE +676 -0
  465. kubiya_control_plane_api-0.9.15.dist-info/top_level.txt +3 -0
  466. scripts/__init__.py +1 -0
  467. scripts/migrations.py +39 -0
  468. scripts/seed_worker_queues.py +128 -0
  469. scripts/setup_agent_runtime.py +142 -0
  470. worker_internal/__init__.py +1 -0
  471. worker_internal/planner/__init__.py +1 -0
  472. worker_internal/planner/activities.py +1499 -0
  473. worker_internal/planner/agent_tools.py +197 -0
  474. worker_internal/planner/event_models.py +148 -0
  475. worker_internal/planner/event_publisher.py +67 -0
  476. worker_internal/planner/models.py +199 -0
  477. worker_internal/planner/retry_logic.py +134 -0
  478. worker_internal/planner/worker.py +300 -0
  479. worker_internal/planner/workflows.py +970 -0
@@ -0,0 +1,485 @@
1
+ """Agent executor service - handles agent execution business logic"""
2
+
3
+ from typing import Dict, Any, Optional, List
4
+ from datetime import datetime, timezone
5
+ import structlog
6
+ import asyncio
7
+ import os
8
+
9
+ from agno.agent import Agent
10
+ from agno.models.litellm import LiteLLM
11
+
12
+ from control_plane_api.worker.control_plane_client import ControlPlaneClient
13
+ from control_plane_api.worker.services.session_service import SessionService
14
+ from control_plane_api.worker.services.cancellation_manager import CancellationManager
15
+ from control_plane_api.worker.services.skill_factory import SkillFactory
16
+ from control_plane_api.worker.utils.streaming_utils import StreamingHelper
17
+
18
+ logger = structlog.get_logger()
19
+
20
+
21
+ class AgentExecutorService:
22
+ """
23
+ Service for executing agents with full session management and cancellation support.
24
+
25
+ This service orchestrates:
26
+ - Session loading and restoration
27
+ - Agent creation with LiteLLM configuration
28
+ - Skill instantiation
29
+ - Streaming execution with real-time updates
30
+ - Session persistence
31
+ - Cancellation support via CancellationManager
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ control_plane: ControlPlaneClient,
37
+ session_service: SessionService,
38
+ cancellation_manager: CancellationManager
39
+ ):
40
+ self.control_plane = control_plane
41
+ self.session_service = session_service
42
+ self.cancellation_manager = cancellation_manager
43
+
44
+ async def execute(self, input: Any) -> Dict[str, Any]:
45
+ """
46
+ Execute an agent with full session management and streaming.
47
+
48
+ Args:
49
+ input: AgentExecutionInput with execution details
50
+
51
+ Returns:
52
+ Dict with response, usage, success flag, etc.
53
+ """
54
+ execution_id = input.execution_id
55
+
56
+ logger.info(
57
+ "agent_workflow_start",
58
+ execution_id=execution_id[:8],
59
+ agent_id=input.agent_id,
60
+ session_id=input.session_id
61
+ )
62
+
63
+ try:
64
+ # STEP 1: Load session history
65
+ session_history = self.session_service.load_session(
66
+ execution_id=execution_id,
67
+ session_id=input.session_id
68
+ )
69
+
70
+ if session_history:
71
+ print(f"āœ… Loaded {len(session_history)} messages from previous session\n")
72
+ else:
73
+ print("ā„¹ļø Starting new conversation\n")
74
+
75
+ # STEP 2: Build conversation context for Agno
76
+ conversation_context = self.session_service.build_conversation_context(session_history)
77
+
78
+ # STEP 3: Get LiteLLM configuration
79
+ litellm_api_base = os.getenv("LITELLM_API_BASE", "https://llm-proxy.kubiya.ai")
80
+ litellm_api_key = os.getenv("LITELLM_API_KEY")
81
+
82
+ if not litellm_api_key:
83
+ raise ValueError("LITELLM_API_KEY environment variable not set")
84
+
85
+ model = input.model_id or os.environ.get("LITELLM_DEFAULT_MODEL", "kubiya/claude-sonnet-4")
86
+
87
+ # STEP 4: Fetch and instantiate skills
88
+ skills = []
89
+ if input.agent_id:
90
+ print(f"šŸ”§ Fetching skills from Control Plane...")
91
+ try:
92
+ skill_configs = self.control_plane.get_skills(input.agent_id)
93
+ if skill_configs:
94
+ print(f"āœ… Resolved {len(skill_configs)} skills")
95
+ print(f" Types: {[t.get('type') for t in skill_configs]}")
96
+ print(f" Names: {[t.get('name') for t in skill_configs]}\n")
97
+
98
+ skills = SkillFactory.create_skills_from_list(skill_configs)
99
+
100
+ if skills:
101
+ print(f"āœ… Instantiated {len(skills)} skill(s)\n")
102
+ else:
103
+ print(f"āš ļø No skills found\n")
104
+ except Exception as e:
105
+ print(f"āŒ Error fetching skills: {str(e)}\n")
106
+ logger.error("skill_fetch_error", error=str(e))
107
+
108
+ # STEP 5: Create agent with streaming helper
109
+ print(f"\nšŸ¤– Creating Agno Agent:")
110
+ print(f" Model: {model}")
111
+ print(f" Skills: {len(skills)}")
112
+
113
+ # Create streaming helper for this execution
114
+ streaming_helper = StreamingHelper(
115
+ control_plane_client=self.control_plane,
116
+ execution_id=execution_id
117
+ )
118
+
119
+ # Create tool hook for real-time updates
120
+ def tool_hook(name: str = None, function_name: str = None, function=None, arguments: dict = None, **kwargs):
121
+ """Hook to capture tool execution for real-time streaming"""
122
+ tool_name = name or function_name or "unknown"
123
+ tool_args = arguments or {}
124
+
125
+ # Generate unique tool execution ID
126
+ import time
127
+ tool_execution_id = f"{tool_name}_{int(time.time() * 1000000)}"
128
+
129
+ print(f" šŸ”§ Tool Starting: {tool_name} (ID: {tool_execution_id})")
130
+
131
+ # Publish tool start event
132
+ streaming_helper.publish_tool_start(
133
+ tool_name=tool_name,
134
+ tool_execution_id=tool_execution_id,
135
+ tool_args=tool_args,
136
+ source="agent"
137
+ )
138
+
139
+ # Execute the tool
140
+ result = None
141
+ error = None
142
+ try:
143
+ if function and callable(function):
144
+ result = function(**tool_args) if tool_args else function()
145
+ else:
146
+ raise ValueError(f"Function not callable: {function}")
147
+
148
+ status = "success"
149
+ print(f" āœ… Tool Success: {tool_name}")
150
+
151
+ except Exception as e:
152
+ error = e
153
+ status = "failed"
154
+ print(f" āŒ Tool Failed: {tool_name} - {str(e)}")
155
+
156
+ # Publish tool completion event
157
+ streaming_helper.publish_tool_complete(
158
+ tool_name=tool_name,
159
+ tool_execution_id=tool_execution_id,
160
+ status=status,
161
+ output=str(result)[:1000] if result else None,
162
+ error=str(error) if error else None,
163
+ source="agent"
164
+ )
165
+
166
+ if error:
167
+ raise error
168
+
169
+ return result
170
+
171
+ # Create Agno Agent
172
+ agent = Agent(
173
+ name=f"Agent {input.agent_id}",
174
+ role=input.system_prompt or "You are a helpful AI assistant",
175
+ model=LiteLLM(
176
+ id=f"openai/{model}",
177
+ api_base=litellm_api_base,
178
+ api_key=litellm_api_key,
179
+ ),
180
+ tools=skills if skills else None,
181
+ tool_hooks=[tool_hook],
182
+ )
183
+
184
+ # STEP 6: Register for cancellation
185
+ self.cancellation_manager.register(
186
+ execution_id=execution_id,
187
+ instance=agent,
188
+ instance_type="agent"
189
+ )
190
+ print(f"āœ… Agent registered for cancellation support\n")
191
+
192
+ # Cache execution metadata in Redis
193
+ self.control_plane.cache_metadata(execution_id, "AGENT")
194
+
195
+ # STEP 7: Execute with streaming
196
+ print("⚔ Executing Agent Run with Streaming...\n")
197
+
198
+ # Generate deterministic message IDs for this turn (matches V2 executor pattern)
199
+ # This ensures streaming and persisted messages have the SAME message_id
200
+ turn_number = len(session_history) // 2 + 1
201
+ user_message_id = f"{execution_id}_user_{turn_number}"
202
+ message_id = f"{execution_id}_assistant_{turn_number}"
203
+
204
+ # Publish user message to stream immediately so it appears in chronological order
205
+ from datetime import datetime, timezone
206
+ user_message_timestamp = datetime.now(timezone.utc).isoformat()
207
+ self.control_plane.publish_event(
208
+ execution_id=execution_id,
209
+ event_type="message",
210
+ data={
211
+ "role": "user",
212
+ "content": input.prompt,
213
+ "timestamp": user_message_timestamp,
214
+ "message_id": user_message_id,
215
+ "user_id": input.user_id,
216
+ "user_name": getattr(input, "user_name", None),
217
+ "user_email": getattr(input, "user_email", None),
218
+ "user_avatar": getattr(input, "user_avatar", None),
219
+ }
220
+ )
221
+ print(f" šŸ“¤ Published user message to stream (ID: {user_message_id})")
222
+
223
+ def stream_agent_run():
224
+ """Run agent with streaming and collect response"""
225
+ # Create event loop for this thread (needed for async streaming)
226
+ loop = asyncio.new_event_loop()
227
+ asyncio.set_event_loop(loop)
228
+
229
+ async def _async_stream():
230
+ """Async wrapper for streaming execution"""
231
+ import time as time_module
232
+ last_heartbeat_time = time_module.time()
233
+ last_persistence_time = time_module.time()
234
+ heartbeat_interval = 10 # Send heartbeat every 10 seconds
235
+ persistence_interval = 60 # Persist to database every 60 seconds
236
+
237
+ try:
238
+ # Execute with conversation context
239
+ if conversation_context:
240
+ run_response = agent.run(
241
+ input.prompt,
242
+ stream=True,
243
+ messages=conversation_context,
244
+ )
245
+ else:
246
+ run_response = agent.run(input.prompt, stream=True)
247
+
248
+ # Process streaming chunks (sync iteration in async context)
249
+ for chunk in run_response:
250
+ # Periodic maintenance: heartbeats and persistence
251
+ current_time = time_module.time()
252
+
253
+ # Send heartbeat every 10s (Temporal liveness)
254
+ if current_time - last_heartbeat_time >= heartbeat_interval:
255
+ current_response = streaming_helper.get_full_response()
256
+ activity.heartbeat({
257
+ "status": "Streaming in progress...",
258
+ "response_length": len(current_response),
259
+ "execution_id": execution_id,
260
+ })
261
+ last_heartbeat_time = current_time
262
+
263
+ # Persist snapshot every 60s (resilience against crashes)
264
+ if current_time - last_persistence_time >= persistence_interval:
265
+ current_response = streaming_helper.get_full_response()
266
+ if current_response:
267
+ print(f"\nšŸ’¾ Periodic persistence ({len(current_response)} chars)...")
268
+ snapshot_messages = session_history + [{
269
+ "role": "assistant",
270
+ "content": current_response,
271
+ "timestamp": datetime.now(timezone.utc).isoformat(),
272
+ "message_id": message_id, # Include deterministic message_id
273
+ }]
274
+ try:
275
+ # Best effort - don't fail execution if persistence fails
276
+ self.session_service.persist_session(
277
+ execution_id=execution_id,
278
+ session_id=input.session_id or execution_id,
279
+ user_id=input.user_id,
280
+ messages=snapshot_messages,
281
+ metadata={
282
+ "agent_id": input.agent_id,
283
+ "organization_id": input.organization_id,
284
+ "snapshot": True,
285
+ }
286
+ )
287
+ print(f" āœ… Session snapshot persisted with message_id: {message_id}")
288
+ except Exception as e:
289
+ print(f" āš ļø Session persistence error: {str(e)} (non-fatal)")
290
+ last_persistence_time = current_time
291
+
292
+ # Handle run_id capture
293
+ streaming_helper.handle_run_id(
294
+ chunk=chunk,
295
+ on_run_id=lambda run_id: self.cancellation_manager.set_run_id(execution_id, run_id)
296
+ )
297
+
298
+ # Handle content chunk
299
+ streaming_helper.handle_content_chunk(
300
+ chunk=chunk,
301
+ message_id=message_id,
302
+ print_to_console=True
303
+ )
304
+
305
+ print() # New line after streaming
306
+ return run_response
307
+
308
+ except Exception as e:
309
+ print(f"\nāŒ Streaming error: {str(e)}")
310
+ # Fall back to non-streaming
311
+ if conversation_context:
312
+ return agent.run(input.prompt, stream=False, messages=conversation_context)
313
+ else:
314
+ return agent.run(input.prompt, stream=False)
315
+
316
+ # Run the async function in the event loop
317
+ try:
318
+ return loop.run_until_complete(_async_stream())
319
+ finally:
320
+ loop.close()
321
+
322
+ # Execute in thread pool (no timeout - user controls via STOP button)
323
+ # Wrap in try-except to handle Temporal cancellation
324
+ try:
325
+ result = await asyncio.to_thread(stream_agent_run)
326
+ except asyncio.CancelledError:
327
+ # Temporal cancelled the activity - cancel the running agent
328
+ print("\nšŸ›‘ Cancellation signal received - stopping agent execution...")
329
+ cancel_result = self.cancellation_manager.cancel(execution_id)
330
+ if cancel_result["success"]:
331
+ print(f"āœ… Agent execution cancelled successfully")
332
+ else:
333
+ print(f"āš ļø Cancellation completed with warning: {cancel_result.get('error', 'Unknown')}")
334
+ # Re-raise to let Temporal know we're cancelled
335
+ raise
336
+
337
+ print("āœ… Agent Execution Completed!")
338
+ full_response = streaming_helper.get_full_response()
339
+ print(f" Response Length: {len(full_response)} chars\n")
340
+
341
+ logger.info(
342
+ "agent_execution_completed",
343
+ execution_id=execution_id[:8],
344
+ response_length=len(full_response)
345
+ )
346
+
347
+ # Use the streamed response content
348
+ response_content = full_response if full_response else (result.content if hasattr(result, "content") else str(result))
349
+
350
+ # STEP 8: Extract usage metrics
351
+ usage = {}
352
+ if hasattr(result, "metrics") and result.metrics:
353
+ metrics = result.metrics
354
+ usage = {
355
+ "prompt_tokens": getattr(metrics, "input_tokens", 0),
356
+ "completion_tokens": getattr(metrics, "output_tokens", 0),
357
+ "total_tokens": getattr(metrics, "total_tokens", 0),
358
+ }
359
+ print(f"šŸ“Š Token Usage:")
360
+ print(f" Input: {usage.get('prompt_tokens', 0)}")
361
+ print(f" Output: {usage.get('completion_tokens', 0)}")
362
+ print(f" Total: {usage.get('total_tokens', 0)}\n")
363
+
364
+ # STEP 9: Persist complete session history
365
+ print("\nšŸ’¾ Persisting session history to Control Plane...")
366
+
367
+ # Build message_ids dict to pass to extract_messages_from_result
368
+ # This ensures persisted messages use the SAME IDs as streaming messages
369
+ message_ids_map = {
370
+ len(session_history): user_message_id, # User message index
371
+ len(session_history) + 1: message_id # Assistant message index
372
+ }
373
+
374
+ # Extract messages from Agno result to get accurate timestamps
375
+ # Agno tracks message timestamps as they're created (msg.created_at)
376
+ extracted_messages = self.session_service.extract_messages_from_result(
377
+ result=run_response,
378
+ user_id=input.user_id,
379
+ execution_id=execution_id,
380
+ message_ids=message_ids_map # Pass deterministic IDs
381
+ )
382
+ print(f" šŸ“Š Extracted {len(extracted_messages)} messages with deterministic IDs")
383
+
384
+ # Use extracted messages which have proper timestamps from Agno
385
+ # These include both user and assistant messages with accurate created_at times
386
+ new_messages = extracted_messages
387
+
388
+ # Fallback: if no messages extracted (shouldn't happen), create them manually
389
+ if not extracted_messages:
390
+ from datetime import datetime, timezone
391
+ current_timestamp = datetime.now(timezone.utc).isoformat()
392
+ print(" āš ļø No messages extracted from Agno result, creating manually")
393
+ new_messages = [
394
+ {
395
+ "role": "user",
396
+ "content": input.prompt,
397
+ "timestamp": current_timestamp,
398
+ "message_id": f"{execution_id}_user_{turn_number}",
399
+ "user_id": input.user_id,
400
+ "user_name": getattr(input, "user_name", None),
401
+ "user_email": getattr(input, "user_email", None),
402
+ },
403
+ {
404
+ "role": "assistant",
405
+ "content": response_content,
406
+ "timestamp": current_timestamp,
407
+ "message_id": message_id, # Use the same message_id as streaming
408
+ },
409
+ ]
410
+
411
+ # Extract tool messages from streaming helper
412
+ tool_messages = streaming_helper.get_tool_messages()
413
+ print(f" šŸ“Š Collected {len(tool_messages)} tool messages during streaming")
414
+
415
+ # Combine with previous history: session_history + new_messages + tool_messages
416
+ complete_session = session_history + new_messages + tool_messages
417
+
418
+ # CRITICAL: Deduplicate messages by message_id AND content to prevent duplicates
419
+ # Use session_service.deduplicate_messages() which has enhanced two-level deduplication
420
+ original_count = len(complete_session)
421
+ complete_session = self.session_service.deduplicate_messages(complete_session)
422
+
423
+ # CRITICAL: Sort by timestamp to ensure chronological order
424
+ # Tool messages happen DURING streaming, so they need to be interleaved with user/assistant messages
425
+ complete_session.sort(key=lambda msg: msg.get("timestamp", ""))
426
+ print(f" āœ… Messages deduplicated ({original_count} -> {len(complete_session)}) and sorted by timestamp")
427
+
428
+ if complete_session:
429
+ success = self.session_service.persist_session(
430
+ execution_id=execution_id,
431
+ session_id=input.session_id or execution_id,
432
+ user_id=input.user_id,
433
+ messages=complete_session,
434
+ metadata={
435
+ "agent_id": input.agent_id,
436
+ "organization_id": input.organization_id,
437
+ "turn_count": len(complete_session),
438
+ }
439
+ )
440
+
441
+ if success:
442
+ print(f" āœ… Session persisted ({len(complete_session)} total messages)")
443
+ else:
444
+ print(f" āš ļø Session persistence failed")
445
+ else:
446
+ print(" ā„¹ļø No messages to persist")
447
+
448
+ print("\n" + "="*80)
449
+ print("šŸ AGENT EXECUTION END")
450
+ print("="*80 + "\n")
451
+
452
+ # STEP 10: Cleanup
453
+ self.cancellation_manager.unregister(execution_id)
454
+
455
+ return {
456
+ "success": True,
457
+ "response": response_content,
458
+ "usage": usage,
459
+ "model": model,
460
+ "finish_reason": "stop",
461
+ }
462
+
463
+ except Exception as e:
464
+ # Cleanup on error
465
+ self.cancellation_manager.unregister(execution_id)
466
+
467
+ print("\n" + "="*80)
468
+ print("āŒ AGENT EXECUTION FAILED")
469
+ print("="*80)
470
+ print(f"Error: {str(e)}")
471
+ print("="*80 + "\n")
472
+
473
+ logger.error(
474
+ "agent_execution_failed",
475
+ execution_id=execution_id[:8],
476
+ error=str(e)
477
+ )
478
+
479
+ return {
480
+ "success": False,
481
+ "error": str(e),
482
+ "model": input.model_id,
483
+ "usage": None,
484
+ "finish_reason": "error",
485
+ }