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,836 @@
1
+ """
2
+ Agno Planning Toolkit - Synchronous tools for task planning with internal service integration
3
+
4
+ This module follows the Agno Toolkit pattern with internal service access:
5
+ - Tools are synchronous (not async) for proper Agno workflow compatibility
6
+ - Uses PlanningService with direct DB access (no HTTP self-calls)
7
+ - Returns structured JSON for reliable parsing
8
+ - Proper tool registration via Toolkit base class
9
+
10
+ Architecture:
11
+ - Agents/Teams: Direct DB queries via PlanningService (fast, no HTTP)
12
+ - Context Graph: HTTP to external graph.kubiya.ai (correct for external service)
13
+ """
14
+
15
+ import json
16
+ import structlog
17
+ import asyncio
18
+ import hashlib
19
+ import redis
20
+ from typing import Optional
21
+ from functools import wraps
22
+ from sqlalchemy.orm import Session
23
+ from agno.tools.toolkit import Toolkit
24
+
25
+ from control_plane_api.app.lib.planning_tools.planning_service import PlanningService
26
+ from control_plane_api.app.config import settings
27
+
28
+ logger = structlog.get_logger(__name__)
29
+
30
+ # Initialize Redis client for caching (lazy connection)
31
+ try:
32
+ redis_client = redis.from_url(settings.redis_url, decode_responses=True)
33
+ CACHE_ENABLED = True
34
+ except Exception as e:
35
+ logger.warning("redis_connection_failed", error=str(e), message="Tool caching disabled")
36
+ redis_client = None
37
+ CACHE_ENABLED = False
38
+
39
+
40
+ def cache_tool_result(ttl: int = 60):
41
+ """
42
+ Cache tool results in Redis for specified TTL.
43
+
44
+ Args:
45
+ ttl: Time-to-live in seconds (default: 60)
46
+
47
+ Returns:
48
+ Decorator function that wraps tool methods with caching
49
+ """
50
+ def decorator(func):
51
+ @wraps(func)
52
+ def wrapper(self, *args, **kwargs):
53
+ # If caching disabled, call function directly
54
+ if not CACHE_ENABLED or redis_client is None:
55
+ return func(self, *args, **kwargs)
56
+
57
+ # Generate cache key from function name, organization, and args
58
+ cache_parts = [
59
+ func.__name__,
60
+ self.organization_id,
61
+ str(args),
62
+ str(sorted(kwargs.items()))
63
+ ]
64
+ cache_string = "|".join(cache_parts)
65
+ cache_hash = hashlib.md5(cache_string.encode()).hexdigest()
66
+ cache_key = f"tool_cache:{func.__name__}:{cache_hash}"
67
+
68
+ try:
69
+ # Try to get from cache
70
+ cached = redis_client.get(cache_key)
71
+ if cached:
72
+ logger.info("tool_cache_hit", tool=func.__name__, organization=self.organization_id[:8])
73
+ return cached
74
+ except Exception as e:
75
+ logger.warning("redis_get_error", error=str(e), key=cache_key)
76
+
77
+ # Execute function
78
+ result = func(self, *args, **kwargs)
79
+
80
+ # Cache result
81
+ try:
82
+ redis_client.setex(cache_key, ttl, result)
83
+ logger.debug("tool_cache_set", tool=func.__name__, ttl=ttl)
84
+ except Exception as e:
85
+ logger.warning("redis_set_error", error=str(e), key=cache_key)
86
+
87
+ return result
88
+
89
+ return wrapper
90
+ return decorator
91
+
92
+
93
+ class PlanningToolkit(Toolkit):
94
+ """
95
+ Custom toolkit for task planning with auto-registered tools.
96
+
97
+ CRITICAL: Service must be initialized BEFORE calling super().__init__()
98
+ so it's available when tools are registered.
99
+
100
+ This uses internal services directly (no HTTP self-calls).
101
+ """
102
+
103
+ def __init__(
104
+ self,
105
+ db: Session,
106
+ organization_id: str,
107
+ api_token: str,
108
+ name: str = "planning_tools"
109
+ ):
110
+ """
111
+ Initialize planning toolkit with internal services.
112
+
113
+ Args:
114
+ db: SQLAlchemy database session
115
+ organization_id: Organization ID for filtering
116
+ api_token: Org-scoped API token
117
+ name: Toolkit name for Agno
118
+ """
119
+ # CRITICAL: Initialize service BEFORE calling super().__init__()
120
+ self.planning_service = PlanningService(
121
+ db=db,
122
+ organization_id=organization_id,
123
+ api_token=api_token
124
+ )
125
+ self.organization_id = organization_id
126
+
127
+ # Create tools list for auto-registration
128
+ tools = [
129
+ self.list_agents,
130
+ self.list_teams,
131
+ self.search_agents_by_capability,
132
+ self.search_teams_by_capability,
133
+ self.get_agent_details,
134
+ self.get_team_details,
135
+ self.list_environments,
136
+ self.list_worker_queues,
137
+ self.search_context_graph,
138
+ self.get_fallback_agent, # NEW: Fallback for when no perfect match found
139
+ ]
140
+
141
+ # Pass to parent for auto-registration
142
+ # This registers each method in self.functions dict
143
+ super().__init__(name=name, tools=tools)
144
+
145
+ logger.info(
146
+ "planning_toolkit_initialized",
147
+ tool_count=len(tools),
148
+ organization_id=organization_id[:8]
149
+ )
150
+
151
+ @cache_tool_result(ttl=60) # Cache for 60 seconds
152
+ def list_agents(self, limit: int = 20) -> str:
153
+ """List available agents in the organization.
154
+
155
+ Use this to discover what agents are available for executing tasks.
156
+
157
+ Args:
158
+ limit: Maximum number of agents to return (default: 20)
159
+
160
+ Returns:
161
+ JSON string with structured agent data and human-readable format
162
+ """
163
+ try:
164
+ # Call service method (direct DB query)
165
+ agents = self.planning_service.list_agents(limit=limit)
166
+
167
+ # Format human-readable output
168
+ if not agents:
169
+ human_readable = "No active agents found in the organization."
170
+ else:
171
+ human_readable = f"Found {len(agents)} active agents:\n\n"
172
+ for i, agent in enumerate(agents, 1):
173
+ human_readable += f"Agent {i}:\n"
174
+ human_readable += f" ID: {agent.get('id')}\n"
175
+ human_readable += f" Name: {agent.get('name')}\n"
176
+ human_readable += f" Model: {agent.get('model_id', 'default')}\n"
177
+ human_readable += f" Description: {agent.get('description', 'N/A')}\n"
178
+
179
+ capabilities = agent.get('capabilities', [])
180
+ if capabilities:
181
+ human_readable += f" Capabilities: {', '.join(capabilities)}\n"
182
+ human_readable += "\n"
183
+
184
+ # Return structured JSON
185
+ result = {
186
+ "type": "tool_result",
187
+ "tool": "list_agents",
188
+ "success": True,
189
+ "data": {
190
+ "agents": agents,
191
+ "count": len(agents)
192
+ },
193
+ "human_readable": human_readable
194
+ }
195
+ return json.dumps(result, indent=2)
196
+
197
+ except Exception as e:
198
+ logger.error("list_agents_tool_error", error=str(e), exc_info=True)
199
+ return json.dumps({
200
+ "type": "tool_result",
201
+ "tool": "list_agents",
202
+ "success": False,
203
+ "error": str(e)
204
+ })
205
+
206
+ @cache_tool_result(ttl=60) # Cache for 60 seconds
207
+ def list_teams(self, limit: int = 20) -> str:
208
+ """List available teams in the organization.
209
+
210
+ Use this to discover what teams are available for executing multi-agent tasks.
211
+
212
+ Args:
213
+ limit: Maximum number of teams to return (default: 20)
214
+
215
+ Returns:
216
+ JSON string with structured team data and human-readable format
217
+ """
218
+ try:
219
+ # Call service method (direct DB query)
220
+ teams = self.planning_service.list_teams(limit=limit)
221
+
222
+ # Format human-readable output
223
+ if not teams:
224
+ human_readable = "No active teams found in the organization."
225
+ else:
226
+ human_readable = f"Found {len(teams)} active teams:\n\n"
227
+ for i, team in enumerate(teams, 1):
228
+ human_readable += f"Team {i}:\n"
229
+ human_readable += f" ID: {team.get('id')}\n"
230
+ human_readable += f" Name: {team.get('name')}\n"
231
+ human_readable += f" Description: {team.get('description', 'N/A')}\n"
232
+ human_readable += f" Agent Count: {team.get('agent_count', 0)}\n"
233
+
234
+ agents = team.get('agents', [])
235
+ if agents:
236
+ agent_names = [a.get('name', 'Unknown') for a in agents[:5]]
237
+ human_readable += f" Members: {', '.join(agent_names)}\n"
238
+ human_readable += "\n"
239
+
240
+ # Return structured JSON
241
+ result = {
242
+ "type": "tool_result",
243
+ "tool": "list_teams",
244
+ "success": True,
245
+ "data": {
246
+ "teams": teams,
247
+ "count": len(teams)
248
+ },
249
+ "human_readable": human_readable
250
+ }
251
+ return json.dumps(result, indent=2)
252
+
253
+ except Exception as e:
254
+ logger.error("list_teams_tool_error", error=str(e), exc_info=True)
255
+ return json.dumps({
256
+ "type": "tool_result",
257
+ "tool": "list_teams",
258
+ "success": False,
259
+ "error": str(e)
260
+ })
261
+
262
+ def search_agents_by_capability(
263
+ self,
264
+ capability: str,
265
+ limit: int = 10
266
+ ) -> str:
267
+ """Search for agents that have a specific capability or skill.
268
+
269
+ Use this to find agents with required skills like 'kubernetes', 'aws', 'python', etc.
270
+
271
+ Args:
272
+ capability: Skill or capability name to search for (required)
273
+ limit: Maximum number of agents to return (default: 10)
274
+
275
+ Returns:
276
+ JSON string with structured agent data and human-readable format
277
+ """
278
+ try:
279
+ # Call service method (direct DB query)
280
+ agents = self.planning_service.search_agents_by_capability(
281
+ capability=capability,
282
+ limit=limit
283
+ )
284
+
285
+ # Format human-readable output
286
+ if not agents:
287
+ human_readable = f"No agents found with capability '{capability}'."
288
+ else:
289
+ human_readable = f"Found {len(agents)} agents with '{capability}' capability:\n\n"
290
+ for i, agent in enumerate(agents, 1):
291
+ human_readable += f"Agent {i}:\n"
292
+ human_readable += f" ID: {agent.get('id')}\n"
293
+ human_readable += f" Name: {agent.get('name')}\n"
294
+ human_readable += f" Model: {agent.get('model_id', 'default')}\n"
295
+ human_readable += f" Description: {agent.get('description', 'N/A')}\n"
296
+
297
+ capabilities = agent.get('capabilities', [])
298
+ if capabilities:
299
+ human_readable += f" All Capabilities: {', '.join(capabilities)}\n"
300
+ human_readable += "\n"
301
+
302
+ # Return structured JSON
303
+ result = {
304
+ "type": "tool_result",
305
+ "tool": "search_agents_by_capability",
306
+ "success": True,
307
+ "data": {
308
+ "agents": agents,
309
+ "count": len(agents),
310
+ "query": {
311
+ "capability": capability
312
+ }
313
+ },
314
+ "human_readable": human_readable
315
+ }
316
+ return json.dumps(result, indent=2)
317
+
318
+ except Exception as e:
319
+ logger.error("search_agents_by_capability_tool_error", error=str(e), exc_info=True)
320
+ return json.dumps({
321
+ "type": "tool_result",
322
+ "tool": "search_agents_by_capability",
323
+ "success": False,
324
+ "error": str(e)
325
+ })
326
+
327
+ def search_teams_by_capability(
328
+ self,
329
+ capability: str,
330
+ limit: int = 5
331
+ ) -> str:
332
+ """Search for teams that have agents with a specific capability.
333
+
334
+ Use this to find teams for multi-agent tasks requiring specific skills.
335
+
336
+ Args:
337
+ capability: Skill or capability name to search for (required)
338
+ limit: Maximum number of teams to return (default: 5)
339
+
340
+ Returns:
341
+ JSON string with structured team data and human-readable format
342
+ """
343
+ try:
344
+ # Call service method (direct DB query)
345
+ teams = self.planning_service.search_teams_by_capability(
346
+ capability=capability,
347
+ limit=limit
348
+ )
349
+
350
+ # Format human-readable output
351
+ if not teams:
352
+ human_readable = f"No teams found with capability '{capability}'."
353
+ else:
354
+ human_readable = f"Found {len(teams)} teams with '{capability}' capability:\n\n"
355
+ for i, team in enumerate(teams, 1):
356
+ human_readable += f"Team {i}:\n"
357
+ human_readable += f" ID: {team.get('id')}\n"
358
+ human_readable += f" Name: {team.get('name')}\n"
359
+ human_readable += f" Description: {team.get('description', 'N/A')}\n"
360
+ human_readable += f" Agent Count: {team.get('agent_count', 0)}\n"
361
+
362
+ agents = team.get('agents', [])
363
+ if agents:
364
+ agent_names = [a.get('name', 'Unknown') for a in agents[:5]]
365
+ human_readable += f" Members: {', '.join(agent_names)}\n"
366
+ human_readable += "\n"
367
+
368
+ # Return structured JSON
369
+ result = {
370
+ "type": "tool_result",
371
+ "tool": "search_teams_by_capability",
372
+ "success": True,
373
+ "data": {
374
+ "teams": teams,
375
+ "count": len(teams),
376
+ "query": {
377
+ "capability": capability
378
+ }
379
+ },
380
+ "human_readable": human_readable
381
+ }
382
+ return json.dumps(result, indent=2)
383
+
384
+ except Exception as e:
385
+ logger.error("search_teams_by_capability_tool_error", error=str(e), exc_info=True)
386
+ return json.dumps({
387
+ "type": "tool_result",
388
+ "tool": "search_teams_by_capability",
389
+ "success": False,
390
+ "error": str(e)
391
+ })
392
+
393
+ def get_agent_details(self, agent_id: str) -> str:
394
+ """Get complete details for a specific agent.
395
+
396
+ Use this after finding relevant agents to get full execution environment details.
397
+
398
+ Args:
399
+ agent_id: Agent ID to fetch (required)
400
+
401
+ Returns:
402
+ JSON string with full agent details and human-readable format
403
+ """
404
+ try:
405
+ if not agent_id:
406
+ return json.dumps({
407
+ "type": "tool_result",
408
+ "tool": "get_agent_details",
409
+ "success": False,
410
+ "error": "agent_id is required"
411
+ })
412
+
413
+ # Call service method (direct DB query)
414
+ agent = self.planning_service.get_agent_details(agent_id=agent_id)
415
+
416
+ if not agent:
417
+ return json.dumps({
418
+ "type": "tool_result",
419
+ "tool": "get_agent_details",
420
+ "success": False,
421
+ "error": f"Agent {agent_id} not found"
422
+ })
423
+
424
+ # Format human-readable output
425
+ human_readable = f"Agent Details:\n"
426
+ human_readable += f" ID: {agent.get('id')}\n"
427
+ human_readable += f" Name: {agent.get('name')}\n"
428
+ human_readable += f" Model: {agent.get('model_id', 'default')}\n"
429
+ human_readable += f" Status: {agent.get('status')}\n"
430
+ human_readable += f" Description: {agent.get('description', 'N/A')}\n"
431
+
432
+ capabilities = agent.get('capabilities', [])
433
+ if capabilities:
434
+ human_readable += f" Capabilities: {', '.join(capabilities)}\n"
435
+
436
+ skills = agent.get('skills', [])
437
+ if skills:
438
+ human_readable += f" Skills: {len(skills)} configured\n"
439
+
440
+ exec_env = agent.get('execution_environment', {})
441
+ if exec_env:
442
+ if exec_env.get('secrets'):
443
+ human_readable += f" Secrets: {', '.join(exec_env['secrets'].keys())}\n"
444
+ if exec_env.get('env_vars'):
445
+ human_readable += f" Env Vars: {', '.join(exec_env['env_vars'].keys())}\n"
446
+
447
+ # Return structured JSON
448
+ result = {
449
+ "type": "tool_result",
450
+ "tool": "get_agent_details",
451
+ "success": True,
452
+ "data": {
453
+ "agent": agent
454
+ },
455
+ "human_readable": human_readable
456
+ }
457
+ return json.dumps(result, indent=2)
458
+
459
+ except Exception as e:
460
+ logger.error("get_agent_details_tool_error", error=str(e), exc_info=True)
461
+ return json.dumps({
462
+ "type": "tool_result",
463
+ "tool": "get_agent_details",
464
+ "success": False,
465
+ "error": str(e)
466
+ })
467
+
468
+ def get_team_details(self, team_id: str) -> str:
469
+ """Get complete details for a specific team.
470
+
471
+ Use this after finding relevant teams to get full team composition and capabilities.
472
+
473
+ Args:
474
+ team_id: Team ID to fetch (required)
475
+
476
+ Returns:
477
+ JSON string with full team details and human-readable format
478
+ """
479
+ try:
480
+ if not team_id:
481
+ return json.dumps({
482
+ "type": "tool_result",
483
+ "tool": "get_team_details",
484
+ "success": False,
485
+ "error": "team_id is required"
486
+ })
487
+
488
+ # Call service method (direct DB query)
489
+ team = self.planning_service.get_team_details(team_id=team_id)
490
+
491
+ if not team:
492
+ return json.dumps({
493
+ "type": "tool_result",
494
+ "tool": "get_team_details",
495
+ "success": False,
496
+ "error": f"Team {team_id} not found"
497
+ })
498
+
499
+ # Format human-readable output
500
+ human_readable = f"Team Details:\n"
501
+ human_readable += f" ID: {team.get('id')}\n"
502
+ human_readable += f" Name: {team.get('name')}\n"
503
+ human_readable += f" Status: {team.get('status')}\n"
504
+ human_readable += f" Description: {team.get('description', 'N/A')}\n"
505
+ human_readable += f" Agent Count: {team.get('agent_count', 0)}\n"
506
+
507
+ agents = team.get('agents', [])
508
+ if agents:
509
+ human_readable += f"\n Team Members:\n"
510
+ for agent in agents[:10]: # Show first 10
511
+ human_readable += f" - {agent.get('name')} ({agent.get('model_id', 'default')})\n"
512
+
513
+ # Return structured JSON
514
+ result = {
515
+ "type": "tool_result",
516
+ "tool": "get_team_details",
517
+ "success": True,
518
+ "data": {
519
+ "team": team
520
+ },
521
+ "human_readable": human_readable
522
+ }
523
+ return json.dumps(result, indent=2)
524
+
525
+ except Exception as e:
526
+ logger.error("get_team_details_tool_error", error=str(e), exc_info=True)
527
+ return json.dumps({
528
+ "type": "tool_result",
529
+ "tool": "get_team_details",
530
+ "success": False,
531
+ "error": str(e)
532
+ })
533
+
534
+ def search_context_graph(
535
+ self,
536
+ query: str,
537
+ label: Optional[str] = None,
538
+ limit: int = 20
539
+ ) -> str:
540
+ """Search the context graph for relevant resources.
541
+
542
+ Use this to discover services, repositories, or other resources that might be relevant.
543
+
544
+ Args:
545
+ query: Search query text (required)
546
+ label: Optional label to filter by (e.g., 'Service', 'Repository')
547
+ limit: Maximum results to return (default: 20)
548
+
549
+ Returns:
550
+ JSON string with search results and human-readable format
551
+ """
552
+ try:
553
+ if not query:
554
+ return json.dumps({
555
+ "type": "tool_result",
556
+ "tool": "search_context_graph",
557
+ "success": False,
558
+ "error": "query is required"
559
+ })
560
+
561
+ # This method is async, need to wrap it
562
+ # Get or create event loop
563
+ try:
564
+ loop = asyncio.get_event_loop()
565
+ except RuntimeError:
566
+ loop = asyncio.new_event_loop()
567
+ asyncio.set_event_loop(loop)
568
+
569
+ # Run async method synchronously
570
+ results = loop.run_until_complete(
571
+ self.planning_service.search_context_graph(
572
+ query=query,
573
+ label=label,
574
+ limit=limit
575
+ )
576
+ )
577
+
578
+ nodes = results.get('nodes', []) if isinstance(results, dict) else []
579
+
580
+ # Format human-readable output
581
+ if not nodes:
582
+ human_readable = f"No resources found matching '{query}'."
583
+ else:
584
+ human_readable = f"Found {len(nodes)} resources matching '{query}':\n\n"
585
+ for i, node in enumerate(nodes, 1):
586
+ labels = node.get('labels', [])
587
+ props = node.get('properties', {})
588
+
589
+ human_readable += f"Resource {i}:\n"
590
+ human_readable += f" ID: {node.get('id')}\n"
591
+ human_readable += f" Type: {', '.join(labels) if labels else 'Unknown'}\n"
592
+
593
+ # Show key properties
594
+ if props:
595
+ human_readable += f" Properties:\n"
596
+ for key, value in list(props.items())[:5]: # Show first 5 props
597
+ human_readable += f" {key}: {value}\n"
598
+ human_readable += "\n"
599
+
600
+ # Return structured JSON
601
+ result = {
602
+ "type": "tool_result",
603
+ "tool": "search_context_graph",
604
+ "success": True,
605
+ "data": {
606
+ "nodes": nodes,
607
+ "count": len(nodes),
608
+ "query": {
609
+ "text": query,
610
+ "label": label
611
+ }
612
+ },
613
+ "human_readable": human_readable
614
+ }
615
+ return json.dumps(result, indent=2)
616
+
617
+ except Exception as e:
618
+ logger.error("search_context_graph_tool_error", error=str(e), exc_info=True)
619
+ return json.dumps({
620
+ "type": "tool_result",
621
+ "tool": "search_context_graph",
622
+ "success": False,
623
+ "error": str(e)
624
+ })
625
+
626
+ @cache_tool_result(ttl=60) # Cache for 60 seconds
627
+ def list_environments(self, status: str = "active", limit: int = 20) -> str:
628
+ """List available execution environments in the organization.
629
+
630
+ Use this to discover where agents and teams can be executed.
631
+ Environments provide execution context like secrets, env vars, and worker queues.
632
+
633
+ Args:
634
+ status: Filter by status (default: "active")
635
+ limit: Maximum number of environments to return (default: 20)
636
+
637
+ Returns:
638
+ JSON string with structured environment data and human-readable format
639
+ """
640
+ try:
641
+ # Call service method (direct DB query)
642
+ environments = self.planning_service.list_environments(
643
+ status=status,
644
+ limit=limit
645
+ )
646
+
647
+ # Format human-readable output
648
+ if not environments:
649
+ human_readable = "No active environments found in the organization."
650
+ else:
651
+ human_readable = f"Found {len(environments)} active environments:\n\n"
652
+ for i, env in enumerate(environments, 1):
653
+ human_readable += f"Environment {i}:\n"
654
+ human_readable += f" ID: {env.get('id')}\n"
655
+ human_readable += f" Name: {env.get('name')}\n"
656
+ human_readable += f" Display Name: {env.get('display_name')}\n"
657
+ human_readable += f" Status: {env.get('status')}\n"
658
+ if env.get('description'):
659
+ human_readable += f" Description: {env.get('description')}\n"
660
+ tags = env.get('tags', [])
661
+ if tags:
662
+ human_readable += f" Tags: {', '.join(tags)}\n"
663
+ human_readable += "\n"
664
+
665
+ # Return structured JSON
666
+ result = {
667
+ "type": "tool_result",
668
+ "tool": "list_environments",
669
+ "success": True,
670
+ "data": {
671
+ "environments": environments,
672
+ "count": len(environments)
673
+ },
674
+ "human_readable": human_readable
675
+ }
676
+ return json.dumps(result, indent=2)
677
+
678
+ except Exception as e:
679
+ logger.error("list_environments_tool_error", error=str(e), exc_info=True)
680
+ return json.dumps({
681
+ "type": "tool_result",
682
+ "tool": "list_environments",
683
+ "success": False,
684
+ "error": str(e)
685
+ })
686
+
687
+ @cache_tool_result(ttl=60) # Cache for 60 seconds
688
+ def list_worker_queues(
689
+ self,
690
+ environment_id: str = None,
691
+ status: str = "active",
692
+ limit: int = 20
693
+ ) -> str:
694
+ """List available worker queues where tasks can be executed.
695
+
696
+ Worker queues are execution targets within environments. Each queue can have
697
+ multiple active workers ready to process tasks. Use this to find the best
698
+ queue for executing a task.
699
+
700
+ Args:
701
+ environment_id: Optional - filter by environment ID (default: None, returns all)
702
+ status: Filter by status (default: "active")
703
+ limit: Maximum number of queues to return (default: 20)
704
+
705
+ Returns:
706
+ JSON string with structured worker queue data including active worker counts
707
+ """
708
+ try:
709
+ # Call service method (direct DB query)
710
+ worker_queues = self.planning_service.list_worker_queues(
711
+ environment_id=environment_id,
712
+ status=status,
713
+ limit=limit
714
+ )
715
+
716
+ # Format human-readable output
717
+ if not worker_queues:
718
+ human_readable = "No active worker queues found"
719
+ if environment_id:
720
+ human_readable += f" in environment {environment_id}"
721
+ human_readable += "."
722
+ else:
723
+ human_readable = f"Found {len(worker_queues)} active worker queues:\n\n"
724
+ for i, queue in enumerate(worker_queues, 1):
725
+ human_readable += f"Queue {i}:\n"
726
+ human_readable += f" ID: {queue.get('id')}\n"
727
+ human_readable += f" Name: {queue.get('name')}\n"
728
+ human_readable += f" Display Name: {queue.get('display_name')}\n"
729
+ human_readable += f" Environment ID: {queue.get('environment_id')}\n"
730
+ human_readable += f" Status: {queue.get('status')}\n"
731
+ human_readable += f" Active Workers: {queue.get('active_workers', 0)}\n"
732
+ if queue.get('description'):
733
+ human_readable += f" Description: {queue.get('description')}\n"
734
+ human_readable += "\n"
735
+
736
+ # Return structured JSON
737
+ result = {
738
+ "type": "tool_result",
739
+ "tool": "list_worker_queues",
740
+ "success": True,
741
+ "data": {
742
+ "worker_queues": worker_queues,
743
+ "count": len(worker_queues)
744
+ },
745
+ "human_readable": human_readable
746
+ }
747
+ return json.dumps(result, indent=2)
748
+
749
+ except Exception as e:
750
+ logger.error("list_worker_queues_tool_error", error=str(e), exc_info=True)
751
+ return json.dumps({
752
+ "type": "tool_result",
753
+ "tool": "list_worker_queues",
754
+ "success": False,
755
+ "error": str(e)
756
+ })
757
+
758
+ @cache_tool_result(ttl=300) # Cache for 5 minutes (fallback rarely changes)
759
+ def get_fallback_agent(self) -> str:
760
+ """Get a general-purpose fallback agent when no specific match is found.
761
+
762
+ **Use this tool when**:
763
+ - search_agents_by_capability returns empty results
764
+ - No agents match the task requirements
765
+ - You need to select SOMETHING (never return None)
766
+
767
+ This returns the most recently used general-purpose agent, ensuring
768
+ Step 1 ALWAYS has an agent to select.
769
+
770
+ Returns:
771
+ JSON string with fallback agent details
772
+ """
773
+ try:
774
+ # Try to get a versatile, recently-used agent
775
+ agents = self.planning_service.list_agents(limit=50)
776
+
777
+ if not agents:
778
+ # Absolutely no agents - return error
779
+ return json.dumps({
780
+ "type": "tool_result",
781
+ "tool": "get_fallback_agent",
782
+ "success": False,
783
+ "error": "No agents available in organization. Cannot select fallback."
784
+ })
785
+
786
+ # Find best fallback: prefer general-purpose, recently used
787
+ fallback_agent = None
788
+
789
+ # Priority 1: Agent with "general" in name or description
790
+ for agent in agents:
791
+ name_lower = agent.get("name", "").lower()
792
+ desc_lower = agent.get("description", "").lower()
793
+ if "general" in name_lower or "general" in desc_lower:
794
+ fallback_agent = agent
795
+ break
796
+
797
+ # Priority 2: First agent in list (most recently used/created)
798
+ if not fallback_agent:
799
+ fallback_agent = agents[0]
800
+
801
+ # Format human-readable output
802
+ human_readable = f"Fallback Agent (no perfect match found):\n\n"
803
+ human_readable += f"ID: {fallback_agent.get('id')}\n"
804
+ human_readable += f"Name: {fallback_agent.get('name')}\n"
805
+ human_readable += f"Model: {fallback_agent.get('model_id', 'default')}\n"
806
+ human_readable += f"Description: {fallback_agent.get('description', 'General purpose agent')}\n"
807
+
808
+ capabilities = fallback_agent.get('capabilities', [])
809
+ if capabilities:
810
+ human_readable += f"Capabilities: {', '.join(capabilities)}\n"
811
+
812
+ human_readable += "\n⚠️ This is a fallback selection. "
813
+ human_readable += "Consider explaining in your reasoning why no perfect match was found."
814
+
815
+ # Return structured JSON
816
+ result = {
817
+ "type": "tool_result",
818
+ "tool": "get_fallback_agent",
819
+ "success": True,
820
+ "data": {
821
+ "agent": fallback_agent,
822
+ "is_fallback": True,
823
+ "note": "No perfect match - this is the best available general-purpose agent"
824
+ },
825
+ "human_readable": human_readable
826
+ }
827
+ return json.dumps(result, indent=2)
828
+
829
+ except Exception as e:
830
+ logger.error("get_fallback_agent_tool_error", error=str(e), exc_info=True)
831
+ return json.dumps({
832
+ "type": "tool_result",
833
+ "tool": "get_fallback_agent",
834
+ "success": False,
835
+ "error": str(e)
836
+ })