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,838 @@
1
+ """
2
+ Agno Service
3
+
4
+ This service provides integration with Agno for agent execution with MCP server support.
5
+ Agno enables dynamic MCP configuration at runtime, allowing agents to use different
6
+ MCP servers based on their configuration.
7
+ """
8
+
9
+ import os
10
+ import json
11
+ import shlex
12
+ from pathlib import Path
13
+ from typing import Dict, List, Optional, Any, Iterator
14
+ import logging
15
+ from agno.agent import Agent
16
+ from agno.team import Team
17
+ from agno.models.litellm import LiteLLM
18
+ from agno.tools.mcp import MCPTools
19
+ from agno.run.team import TeamRunOutputEvent
20
+ from agno.db.postgres import PostgresDb
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class AgnoService:
26
+ """Service for executing agents with Agno and MCP support"""
27
+
28
+ def __init__(self):
29
+ """Initialize Agno service with persistent session storage using Supabase PostgreSQL"""
30
+ self.model_mapping = self._load_model_mapping()
31
+
32
+ # Get PostgreSQL connection string from environment
33
+ # This should be the direct database connection string from Supabase
34
+ db_url = os.environ.get("DATABASE_URL") or os.environ.get("SUPABASE_DB_URL")
35
+
36
+ if db_url:
37
+ self.db = PostgresDb(
38
+ db_url=db_url,
39
+ db_schema="agno", # Use "agno" schema for Agno session data
40
+ )
41
+
42
+ logger.info(
43
+ "Agno Service initialized with PostgreSQL session storage",
44
+ extra={
45
+ "model_mappings": len(self.model_mapping),
46
+ "db_schema": "agno",
47
+ }
48
+ )
49
+ else:
50
+ logger.warning(
51
+ "DATABASE_URL or SUPABASE_DB_URL not set, Agno sessions will not persist",
52
+ extra={"model_mappings": len(self.model_mapping)}
53
+ )
54
+ self.db = None
55
+
56
+ def _load_model_mapping(self) -> Dict[str, str]:
57
+ """
58
+ Load model mapping from models.json.
59
+ Maps kubiya/ prefix models to actual LiteLLM provider models.
60
+ """
61
+ try:
62
+ models_file = Path(__file__).parent.parent.parent / "models.json"
63
+ if models_file.exists():
64
+ with open(models_file, "r") as f:
65
+ mapping = json.load(f)
66
+ logger.info(f"Loaded model mapping from {models_file}", extra={"mappings": mapping})
67
+ return mapping
68
+ else:
69
+ logger.warning(f"Model mapping file not found at {models_file}, using empty mapping")
70
+ return {}
71
+ except Exception as e:
72
+ logger.error(f"Failed to load model mapping: {str(e)}")
73
+ return {}
74
+
75
+ def _resolve_model(self, model: str) -> str:
76
+ """
77
+ Resolve kubiya/ prefixed models to actual LiteLLM provider models.
78
+
79
+ Args:
80
+ model: Model identifier (e.g., "kubiya/claude-sonnet-4")
81
+
82
+ Returns:
83
+ Resolved model identifier (e.g., "anthropic/claude-sonnet-4-20250514")
84
+ """
85
+ if model in self.model_mapping:
86
+ resolved = self.model_mapping[model]
87
+ logger.info(f"Resolved model: {model} -> {resolved}")
88
+ return resolved
89
+
90
+ # If no mapping found, return as-is (for backward compatibility)
91
+ logger.info(f"No mapping found for model: {model}, using as-is")
92
+ return model
93
+
94
+ async def _build_mcp_tools_async(self, mcp_config: Dict[str, Any]) -> List[Any]:
95
+ """
96
+ Build and connect to MCP tools from agent configuration (async).
97
+
98
+ Args:
99
+ mcp_config: MCP servers configuration from agent.configuration.mcpServers
100
+
101
+ Returns:
102
+ List of connected MCP tool instances
103
+ """
104
+ mcp_tools = []
105
+
106
+ if not mcp_config:
107
+ logger.info("No MCP servers configured, agent will run without MCP tools")
108
+ return mcp_tools
109
+
110
+ logger.info(
111
+ f"Building MCP tools from {len(mcp_config)} MCP server configurations",
112
+ extra={
113
+ "mcp_server_count": len(mcp_config),
114
+ "mcp_server_ids": list(mcp_config.keys()),
115
+ }
116
+ )
117
+
118
+ for server_id, server_config in mcp_config.items():
119
+ try:
120
+ # Determine transport type
121
+ if "url" in server_config:
122
+ # SSE/HTTP transport
123
+ mcp_tool = MCPTools(
124
+ url=server_config["url"],
125
+ headers=server_config.get("headers", {}),
126
+ )
127
+ logger.info(
128
+ f"Configured MCP server '{server_id}' with SSE transport",
129
+ extra={"url": server_config["url"]}
130
+ )
131
+ else:
132
+ # stdio transport - build full command with args using proper shell escaping
133
+ command = server_config.get("command")
134
+ args = server_config.get("args", [])
135
+ env = server_config.get("env", {})
136
+
137
+ # Build full command string with proper shell escaping
138
+ # Using shlex.quote() to safely handle args with spaces and special characters
139
+ full_command = shlex.quote(command)
140
+
141
+ # Args can be a list or already a string
142
+ if isinstance(args, list) and args:
143
+ # Properly escape each argument
144
+ escaped_args = ' '.join(shlex.quote(str(arg)) for arg in args)
145
+ full_command = f"{shlex.quote(command)} {escaped_args}"
146
+ elif isinstance(args, str) and args:
147
+ # If args is a string, assume it's already properly formatted or escape it
148
+ full_command = f"{shlex.quote(command)} {shlex.quote(args)}"
149
+
150
+ mcp_tool = MCPTools(
151
+ command=full_command,
152
+ env=env,
153
+ )
154
+ logger.info(
155
+ f"Configured MCP server '{server_id}' with stdio transport",
156
+ extra={
157
+ "command": command,
158
+ "args": args,
159
+ "full_command": full_command
160
+ }
161
+ )
162
+
163
+ # Connect to the MCP server
164
+ await mcp_tool.connect()
165
+ mcp_tools.append(mcp_tool)
166
+ logger.info(f"Successfully connected to MCP server '{server_id}'")
167
+
168
+ except Exception as e:
169
+ import traceback
170
+ error_details = {
171
+ "server_id": server_id,
172
+ "error": str(e),
173
+ "error_type": type(e).__name__,
174
+ "traceback": traceback.format_exc(),
175
+ "config": {
176
+ "command": server_config.get("command") if "command" in server_config else None,
177
+ "url": server_config.get("url") if "url" in server_config else None,
178
+ }
179
+ }
180
+ logger.error(
181
+ f"Failed to configure MCP server '{server_id}': {str(e)}",
182
+ extra=error_details
183
+ )
184
+ # Continue with other servers even if one fails
185
+
186
+ logger.info(
187
+ f"Built {len(mcp_tools)} MCP tools from {len(mcp_config)} server configurations",
188
+ extra={
189
+ "mcp_tool_count": len(mcp_tools),
190
+ "total_servers_configured": len(mcp_config),
191
+ "connection_success_rate": f"{len(mcp_tools)}/{len(mcp_config)}"
192
+ }
193
+ )
194
+
195
+ return mcp_tools
196
+
197
+ async def _build_skill_tools(self, skill_defs: List[Dict[str, Any]]) -> List[Any]:
198
+ """
199
+ Build OS-level skill tools from skill definitions.
200
+
201
+ Args:
202
+ skill_defs: List of resolved skill definitions with configurations
203
+
204
+ Returns:
205
+ List of instantiated skill tool instances
206
+ """
207
+ # Import agno OS-level tools
208
+ try:
209
+ from agno.tools.file import FileTools
210
+ from agno.tools.shell import ShellTools
211
+ from agno.tools.docker import DockerTools
212
+ from agno.tools.sleep import SleepTools
213
+ from agno.tools.file_generation import FileGenerationTools
214
+ except ImportError as e:
215
+ logger.error(f"Failed to import agno tools: {str(e)}")
216
+ return []
217
+
218
+ # Tool registry mapping skill types to agno tool classes
219
+ SKILL_REGISTRY = {
220
+ "file_system": FileTools,
221
+ "shell": ShellTools,
222
+ "docker": DockerTools,
223
+ "sleep": SleepTools,
224
+ "file_generation": FileGenerationTools,
225
+ }
226
+
227
+ tools = []
228
+
229
+ if not skill_defs:
230
+ logger.info("No skill definitions provided, agent will run without OS-level tools")
231
+ return tools
232
+
233
+ logger.info(
234
+ f"Building skill tools from {len(skill_defs)} skill definitions",
235
+ extra={
236
+ "skill_count": len(skill_defs),
237
+ "skill_names": [t.get("name") for t in skill_defs],
238
+ "skill_types": [t.get("type") for t in skill_defs],
239
+ "skill_sources": [t.get("source") for t in skill_defs],
240
+ }
241
+ )
242
+
243
+ for skill_def in skill_defs:
244
+ if not skill_def.get("enabled", True):
245
+ logger.debug(
246
+ f"Skipping disabled skill",
247
+ extra={"skill_name": skill_def.get("name")}
248
+ )
249
+ continue
250
+
251
+ skill_type = skill_def.get("type")
252
+ tool_class = SKILL_REGISTRY.get(skill_type)
253
+
254
+ if not tool_class:
255
+ logger.warning(
256
+ f"Unknown skill type: {skill_type}",
257
+ extra={
258
+ "skill_type": skill_type,
259
+ "skill_name": skill_def.get("name")
260
+ }
261
+ )
262
+ continue
263
+
264
+ # Get configuration from skill definition
265
+ config = skill_def.get("configuration", {})
266
+
267
+ # Instantiate tool with configuration
268
+ try:
269
+ tool_instance = tool_class(**config)
270
+ tools.append(tool_instance)
271
+
272
+ logger.info(
273
+ f"Skill instantiated: {skill_def.get('name')}",
274
+ extra={
275
+ "skill_name": skill_def.get("name"),
276
+ "skill_type": skill_type,
277
+ "source": skill_def.get("source"),
278
+ "configuration": config
279
+ }
280
+ )
281
+ except Exception as e:
282
+ import traceback
283
+ logger.error(
284
+ f"Failed to instantiate skill '{skill_def.get('name')}': {str(e)}",
285
+ extra={
286
+ "skill_name": skill_def.get("name"),
287
+ "skill_type": skill_type,
288
+ "error": str(e),
289
+ "error_type": type(e).__name__,
290
+ "traceback": traceback.format_exc(),
291
+ }
292
+ )
293
+ # Continue with other tools even if one fails
294
+
295
+ logger.info(
296
+ f"Built {len(tools)} skill tools",
297
+ extra={
298
+ "tool_count": len(tools),
299
+ "tool_types": [type(t).__name__ for t in tools]
300
+ }
301
+ )
302
+
303
+ return tools
304
+
305
+ async def _build_context_graph_skill(
306
+ self,
307
+ graph_api_url: str,
308
+ api_key: str,
309
+ organization_id: str,
310
+ dataset_name: str,
311
+ ) -> List[Any]:
312
+ """
313
+ Build context graph skill (memory + search tools).
314
+
315
+ Args:
316
+ graph_api_url: Context graph API base URL
317
+ api_key: Kubiya API key
318
+ organization_id: Organization ID
319
+ dataset_name: Dataset name (from environment)
320
+
321
+ Returns:
322
+ List containing ContextGraphSkill instance
323
+ """
324
+ from control_plane_api.app.services.toolsets.context_graph_skill import ContextGraphSkill
325
+
326
+ tools = []
327
+
328
+ if not graph_api_url or not api_key:
329
+ logger.warning(
330
+ "context_graph_skill_not_configured",
331
+ extra={
332
+ "message": "Missing graph API URL or API key",
333
+ "has_url": bool(graph_api_url),
334
+ "has_key": bool(api_key)
335
+ }
336
+ )
337
+ return tools
338
+
339
+ try:
340
+ context_skill = ContextGraphSkill(
341
+ graph_api_url=graph_api_url,
342
+ api_key=api_key,
343
+ organization_id=organization_id,
344
+ dataset_name=dataset_name,
345
+ auto_create_dataset=True,
346
+ )
347
+
348
+ tools.append(context_skill)
349
+
350
+ logger.info(
351
+ "context_graph_skill_initialized",
352
+ extra={
353
+ "dataset_name": dataset_name,
354
+ "organization_id": organization_id,
355
+ "graph_api_url": graph_api_url,
356
+ }
357
+ )
358
+ except Exception as e:
359
+ import traceback
360
+ logger.error(
361
+ "context_graph_skill_failed",
362
+ extra={
363
+ "error": str(e),
364
+ "error_type": type(e).__name__,
365
+ "traceback": traceback.format_exc(),
366
+ }
367
+ )
368
+
369
+ return tools
370
+
371
+ async def _recall_relevant_memories(
372
+ self,
373
+ prompt: str,
374
+ graph_api_url: str,
375
+ api_key: str,
376
+ organization_id: str,
377
+ dataset_name: str,
378
+ limit: int = 3,
379
+ ) -> Optional[str]:
380
+ """
381
+ Recall relevant memories based on the user prompt.
382
+
383
+ Args:
384
+ prompt: User's prompt to search for relevant memories
385
+ graph_api_url: Context graph API URL
386
+ api_key: API key for authentication
387
+ organization_id: Organization ID
388
+ dataset_name: Dataset name for scoping
389
+ limit: Maximum number of memories to recall
390
+
391
+ Returns:
392
+ Formatted string of recalled memories or None if no memories found
393
+ """
394
+ try:
395
+ import httpx
396
+
397
+ # First, check if dataset exists
398
+ headers = {
399
+ "Authorization": f"Bearer {api_key}",
400
+ "X-Organization-ID": organization_id,
401
+ }
402
+
403
+ async with httpx.AsyncClient(timeout=10.0) as client:
404
+ # List datasets to find ours
405
+ response = await client.get(
406
+ f"{graph_api_url}/api/v1/graph/datasets",
407
+ headers=headers,
408
+ )
409
+
410
+ if response.status_code != 200:
411
+ logger.debug("Failed to list datasets for memory recall", status_code=response.status_code)
412
+ return None
413
+
414
+ datasets = response.json()
415
+ dataset_id = None
416
+ for ds in datasets:
417
+ if ds.get("name") == dataset_name:
418
+ dataset_id = ds["id"]
419
+ break
420
+
421
+ if not dataset_id:
422
+ logger.debug("Dataset not found for memory recall", dataset_name=dataset_name)
423
+ return None
424
+
425
+ # Recall memories using the user prompt as query
426
+ recall_response = await client.post(
427
+ f"{graph_api_url}/api/v1/graph/memory/recall",
428
+ headers=headers,
429
+ json={
430
+ "query": prompt,
431
+ "dataset_id": dataset_id,
432
+ "limit": limit,
433
+ },
434
+ timeout=5.0,
435
+ )
436
+
437
+ if recall_response.status_code != 200:
438
+ logger.debug("Memory recall request failed", status_code=recall_response.status_code)
439
+ return None
440
+
441
+ memories = recall_response.json()
442
+
443
+ if not memories or len(memories) == 0:
444
+ logger.debug("No relevant memories found", query=prompt[:50])
445
+ return None
446
+
447
+ # Format memories for injection
448
+ formatted = "\n\n---\n**📚 Related memories found:**\n"
449
+ for i, memory in enumerate(memories, 1):
450
+ content = memory.get('content', memory.get('text', 'N/A'))
451
+ formatted += f"\n{i}. {content}"
452
+ if memory.get('metadata'):
453
+ formatted += f"\n _Metadata: {memory['metadata']}_"
454
+ formatted += "\n---\n"
455
+
456
+ logger.info(
457
+ "recalled_memories_for_context",
458
+ count=len(memories),
459
+ query=prompt[:50],
460
+ dataset_name=dataset_name,
461
+ )
462
+
463
+ return formatted
464
+
465
+ except Exception as e:
466
+ logger.warning(
467
+ "memory_recall_failed",
468
+ error=str(e),
469
+ error_type=type(e).__name__,
470
+ )
471
+ return None
472
+
473
+ async def execute_agent_async(
474
+ self,
475
+ prompt: Optional[str] = None,
476
+ model: Optional[str] = None,
477
+ system_prompt: Optional[str] = None,
478
+ mcp_servers: Optional[Dict[str, Any]] = None,
479
+ skills: Optional[List[Dict[str, Any]]] = None,
480
+ temperature: float = 0.7,
481
+ max_tokens: Optional[int] = None,
482
+ stream: bool = False,
483
+ conversation_history: Optional[List[Dict[str, str]]] = None,
484
+ session_id: Optional[str] = None,
485
+ user_id: Optional[str] = None,
486
+ graph_api_url: Optional[str] = None,
487
+ dataset_name: Optional[str] = None,
488
+ organization_id: Optional[str] = None,
489
+ api_key: Optional[str] = None,
490
+ **kwargs: Any,
491
+ ) -> Dict[str, Any]:
492
+ """
493
+ Execute an agent using Agno Teams with MCP and OS-level skill support and session management.
494
+
495
+ Args:
496
+ prompt: The user prompt (for single-turn conversations)
497
+ model: Model identifier
498
+ system_prompt: System prompt for the agent
499
+ mcp_servers: MCP servers configuration dict
500
+ skills: List of resolved skill definitions (OS-level tools)
501
+ temperature: Temperature for response generation
502
+ max_tokens: Maximum tokens to generate
503
+ stream: Whether to stream the response
504
+ conversation_history: Full conversation history (for multi-turn conversations) - DEPRECATED, use session_id instead
505
+ session_id: Session ID for multi-turn conversations (enables Agno session management)
506
+ user_id: User ID for multi-user support
507
+ graph_api_url: Context graph API base URL for memory tools
508
+ dataset_name: Dataset name for memory scoping (typically environment name)
509
+ organization_id: Organization ID for API authentication
510
+ api_key: API key for graph API authentication
511
+ **kwargs: Additional parameters
512
+
513
+ Returns:
514
+ Dict containing the response and metadata including session messages
515
+ """
516
+ mcp_tools = []
517
+ skill_tools = []
518
+ try:
519
+ # Use default model if not specified
520
+ if not model:
521
+ model = os.environ.get("LITELLM_DEFAULT_MODEL", "claude-sonnet-4")
522
+
523
+ # Build and connect to MCP tools from configuration
524
+ mcp_tools = await self._build_mcp_tools_async(mcp_servers or {})
525
+
526
+ # Build OS-level skill tools
527
+ skill_tools = await self._build_skill_tools(skills or [])
528
+
529
+ # Build context graph skill (memory + search)
530
+ context_graph_tools = []
531
+ if graph_api_url and dataset_name and organization_id and api_key:
532
+ context_graph_tools = await self._build_context_graph_skill(
533
+ graph_api_url=graph_api_url,
534
+ api_key=api_key,
535
+ organization_id=organization_id,
536
+ dataset_name=dataset_name,
537
+ )
538
+
539
+ # Automatically recall relevant memories and inject into prompt
540
+ if prompt:
541
+ recalled_memories = await self._recall_relevant_memories(
542
+ prompt=prompt,
543
+ graph_api_url=graph_api_url,
544
+ api_key=api_key,
545
+ organization_id=organization_id,
546
+ dataset_name=dataset_name,
547
+ limit=3,
548
+ )
549
+
550
+ if recalled_memories:
551
+ # Inject recalled memories into the prompt
552
+ prompt = f"{prompt}{recalled_memories}"
553
+ logger.info(
554
+ "injected_recalled_memories_into_prompt",
555
+ extra={
556
+ "original_prompt_length": len(prompt) - len(recalled_memories),
557
+ "recalled_memories_length": len(recalled_memories),
558
+ "dataset_name": dataset_name,
559
+ }
560
+ )
561
+
562
+ # Create LiteLLM model instance
563
+ # IMPORTANT: Use openai/ prefix for custom proxy compatibility
564
+ litellm_model = LiteLLM(
565
+ id=f"openai/{model}",
566
+ api_base=os.environ.get("LITELLM_API_BASE", "https://llm-proxy.kubiya.ai"),
567
+ api_key=os.environ.get("LITELLM_API_KEY"),
568
+ temperature=temperature,
569
+ max_tokens=max_tokens,
570
+ )
571
+
572
+ # Create specialized agent members for MCP tools
573
+ members = []
574
+ if mcp_tools:
575
+ # Create a specialized agent for each MCP tool
576
+ for idx, tool in enumerate(mcp_tools):
577
+ member = Agent(
578
+ name=f"MCP Agent {idx+1}",
579
+ role="Execute MCP tool operations",
580
+ tools=[tool],
581
+ model=litellm_model,
582
+ )
583
+ members.append(member)
584
+ logger.info(
585
+ f"Created {len(members)} specialized MCP agents",
586
+ extra={
587
+ "mcp_agent_count": len(members),
588
+ "mcp_tools_per_agent": [1] * len(members)
589
+ }
590
+ )
591
+
592
+ # Combine all tools: MCP tools + OS-level skill tools + context graph tools
593
+ all_tools = mcp_tools + skill_tools + context_graph_tools
594
+ logger.info(
595
+ f"Total tools available: {len(all_tools)} (MCP: {len(mcp_tools)}, Skills: {len(skill_tools)}, Context Graph: {len(context_graph_tools)})",
596
+ extra={
597
+ "mcp_tool_count": len(mcp_tools),
598
+ "skill_tool_count": len(skill_tools),
599
+ "context_graph_tool_count": len(context_graph_tools),
600
+ "total_tools": len(all_tools)
601
+ }
602
+ )
603
+
604
+ # Create the team with database for session management
605
+ # The team itself gets all tools (both MCP and OS-level skills)
606
+ logger.info(
607
+ f"Creating Agent Execution Team with {len(members)} MCP agents and {len(all_tools)} total tools",
608
+ extra={
609
+ "team_members": len(members),
610
+ "total_tools": len(all_tools),
611
+ "mcp_tools": len(mcp_tools),
612
+ "skill_tools": len(skill_tools),
613
+ "session_enabled": bool(session_id),
614
+ "session_id": session_id,
615
+ "user_id": user_id,
616
+ "model": model,
617
+ }
618
+ )
619
+
620
+ team = Team(
621
+ name="Agent Execution Team",
622
+ members=members,
623
+ tools=all_tools, # Add all tools to the team
624
+ model=litellm_model,
625
+ instructions=system_prompt or ["You are a helpful AI assistant."],
626
+ markdown=True,
627
+ db=self.db, # Enable session storage
628
+ add_history_to_context=True, # Automatically add history to context
629
+ num_history_runs=5, # Include last 5 runs in context
630
+ read_team_history=True, # Enable reading team history
631
+ )
632
+
633
+ logger.info(
634
+ f"Team created successfully with session management enabled",
635
+ extra={
636
+ "team_name": team.name,
637
+ "team_id": id(team),
638
+ }
639
+ )
640
+
641
+ # For session-based conversations, just use the current prompt
642
+ # Agno will automatically handle the conversation history through sessions
643
+ if not prompt:
644
+ raise ValueError("'prompt' is required for session-based conversations")
645
+
646
+ input_text = prompt
647
+ logger.info(
648
+ f"Executing team with Agno. Model: {model}, Members: {len(members)}, MCP tools: {len(mcp_tools)}, Session: {session_id}, User: {user_id}"
649
+ )
650
+
651
+ # Execute team with session support (use arun for async)
652
+ run_kwargs = {}
653
+ if session_id:
654
+ run_kwargs["session_id"] = session_id
655
+ if user_id:
656
+ run_kwargs["user_id"] = user_id
657
+
658
+ if stream:
659
+ # For streaming, collect all chunks AND publish to Redis for real-time UI
660
+ response_stream: Iterator[TeamRunOutputEvent] = team.arun(
661
+ input_text,
662
+ stream=True,
663
+ stream_intermediate_steps=True,
664
+ **run_kwargs
665
+ )
666
+
667
+ # Generate unique message ID for this streaming turn
668
+ import time
669
+ message_id = f"{session_id}_{int(time.time() * 1000000)}" if session_id else f"msg_{int(time.time() * 1000000)}"
670
+
671
+ # Publish chunks to Redis for real-time UI updates
672
+ from control_plane_api.app.lib.redis_client import get_redis_client
673
+ from datetime import datetime, timezone
674
+ import json as json_lib
675
+
676
+ redis_client = get_redis_client()
677
+
678
+ content_chunks = []
679
+ async for chunk in response_stream:
680
+ if chunk.event == "TeamRunContent" and chunk.content:
681
+ content_chunks.append(chunk.content)
682
+
683
+ # Publish chunk to Redis immediately for real-time UI
684
+ if redis_client and session_id: # session_id acts as execution_id
685
+ try:
686
+ redis_key = f"execution:{session_id}:events"
687
+ event_data = {
688
+ "event_type": "message_chunk",
689
+ "timestamp": datetime.now(timezone.utc).isoformat(),
690
+ "data": {
691
+ "role": "assistant",
692
+ "content": chunk.content, # Delta chunk, not accumulated
693
+ "is_chunk": True,
694
+ "message_id": message_id,
695
+ }
696
+ }
697
+ await redis_client.lpush(redis_key, json_lib.dumps(event_data))
698
+ await redis_client.ltrim(redis_key, 0, 999) # Keep last 1000 events
699
+ await redis_client.expire(redis_key, 3600) # 1 hour TTL
700
+ except Exception as redis_error:
701
+ # Don't fail execution if Redis publish fails
702
+ logger.debug("redis_chunk_publish_failed", error=str(redis_error), session_id=session_id)
703
+
704
+ content = "".join(content_chunks)
705
+
706
+ # Get the final response object
707
+ response = await team.arun(input_text, stream=False, **run_kwargs)
708
+ else:
709
+ # Non-streaming execution with session
710
+ response = await team.arun(input_text, **run_kwargs)
711
+ content = response.content
712
+
713
+ # Extract usage from metrics if available
714
+ usage = {}
715
+ if hasattr(response, 'metrics') and response.metrics:
716
+ usage = {
717
+ "prompt_tokens": getattr(response.metrics, 'input_tokens', 0),
718
+ "completion_tokens": getattr(response.metrics, 'output_tokens', 0),
719
+ "total_tokens": getattr(response.metrics, 'total_tokens', 0),
720
+ }
721
+
722
+ # Get session messages if session_id was provided
723
+ messages = []
724
+ if session_id and self.db:
725
+ try:
726
+ # Fetch all messages for this session from Agno's database
727
+ session_messages = team.get_messages_for_session(session_id=session_id)
728
+ messages = [
729
+ {
730
+ "role": msg.role,
731
+ "content": msg.content,
732
+ "timestamp": msg.created_at if hasattr(msg, 'created_at') else None,
733
+ }
734
+ for msg in session_messages
735
+ ]
736
+ logger.info(f"Retrieved {len(messages)} messages from session {session_id}")
737
+ except Exception as e:
738
+ logger.warning(f"Failed to retrieve session messages: {str(e)}")
739
+
740
+ result = {
741
+ "success": True,
742
+ "response": content,
743
+ "model": model,
744
+ "usage": usage,
745
+ "finish_reason": "stop",
746
+ "mcp_tools_used": len(mcp_tools),
747
+ "run_id": getattr(response, 'run_id', None),
748
+ "session_id": session_id,
749
+ "messages": messages, # Include full session history
750
+ }
751
+
752
+ logger.info(
753
+ f"Team execution successful",
754
+ extra={
755
+ "mcp_tools": len(mcp_tools),
756
+ "members": len(members),
757
+ "session_messages": len(messages)
758
+ }
759
+ )
760
+ return result
761
+
762
+ except Exception as e:
763
+ import traceback
764
+ error_traceback = traceback.format_exc()
765
+ error_type = type(e).__name__
766
+
767
+ logger.error(f"Error executing team with Agno: {str(e)}")
768
+ logger.error(f"Error type: {error_type}")
769
+ logger.error(f"Traceback: {error_traceback}")
770
+
771
+ return {
772
+ "success": False,
773
+ "error": str(e),
774
+ "error_type": error_type,
775
+ "error_traceback": error_traceback,
776
+ "model": model or os.environ.get("LITELLM_DEFAULT_MODEL", "kubiya/claude-sonnet-4"),
777
+ "mcp_tools_used": 0,
778
+ }
779
+ finally:
780
+ # Close all MCP connections
781
+ for mcp_tool in mcp_tools:
782
+ try:
783
+ await mcp_tool.close()
784
+ except Exception as e:
785
+ logger.warning(f"Failed to close MCP tool: {str(e)}")
786
+
787
+ def execute_agent(
788
+ self,
789
+ prompt: str,
790
+ model: Optional[str] = None,
791
+ system_prompt: Optional[str] = None,
792
+ mcp_servers: Optional[Dict[str, Any]] = None,
793
+ temperature: float = 0.7,
794
+ max_tokens: Optional[int] = None,
795
+ stream: bool = False,
796
+ **kwargs: Any,
797
+ ) -> Dict[str, Any]:
798
+ """
799
+ Sync wrapper for execute_agent_async.
800
+
801
+ Args:
802
+ prompt: The user prompt
803
+ model: Model identifier
804
+ system_prompt: System prompt for the agent
805
+ mcp_servers: MCP servers configuration dict
806
+ temperature: Temperature for response generation
807
+ max_tokens: Maximum tokens to generate
808
+ stream: Whether to stream the response
809
+ **kwargs: Additional parameters
810
+
811
+ Returns:
812
+ Dict containing the response and metadata
813
+ """
814
+ import asyncio
815
+
816
+ # Always create a new event loop for isolation
817
+ loop = asyncio.new_event_loop()
818
+ asyncio.set_event_loop(loop)
819
+
820
+ try:
821
+ return loop.run_until_complete(
822
+ self.execute_agent_async(
823
+ prompt=prompt,
824
+ model=model,
825
+ system_prompt=system_prompt,
826
+ mcp_servers=mcp_servers,
827
+ temperature=temperature,
828
+ max_tokens=max_tokens,
829
+ stream=stream,
830
+ **kwargs,
831
+ )
832
+ )
833
+ finally:
834
+ loop.close()
835
+
836
+
837
+ # Singleton instance
838
+ agno_service = AgnoService()