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,248 @@
1
+ """
2
+ Configuration builder for Agno runtime.
3
+
4
+ This module handles:
5
+ - LiteLLM API configuration
6
+ - Model selection and setup
7
+ - Agent configuration
8
+ - Environment variable management
9
+ """
10
+
11
+ import os
12
+ import structlog
13
+ from typing import Any, Optional, List
14
+
15
+ from agno.agent import Agent
16
+ from agno.models.litellm import LiteLLM
17
+ from control_plane_api.worker.services.system_prompt_enhancement import create_default_prompt_builder
18
+ from control_plane_api.worker.runtimes.model_utils import get_effective_model, is_model_override_active
19
+
20
+ # Import LiteLLMLangfuse for proper Langfuse metadata support
21
+ try:
22
+ from agno.models.litellm import LiteLLMLangfuse
23
+ LANGFUSE_SUPPORT = True
24
+ except ImportError:
25
+ LANGFUSE_SUPPORT = False
26
+
27
+ logger = structlog.get_logger(__name__)
28
+
29
+ # Module-level singleton for system prompt enhancement
30
+ # NOTE: This is now created per-execution to support dynamic skill context
31
+ # _prompt_builder = create_default_prompt_builder()
32
+
33
+
34
+ def build_agno_agent_config(
35
+ agent_id: str,
36
+ system_prompt: Optional[str] = None,
37
+ model_id: Optional[str] = None,
38
+ skills: Optional[List[Any]] = None,
39
+ mcp_tools: Optional[List[Any]] = None,
40
+ tool_hooks: Optional[List[Any]] = None,
41
+ user_id: Optional[str] = None,
42
+ session_id: Optional[str] = None,
43
+ organization_id: Optional[str] = None,
44
+ agent_name: Optional[str] = None,
45
+ skill_configs: Optional[List[Any]] = None,
46
+ user_metadata: Optional[dict] = None,
47
+ additional_context: Optional[dict] = None,
48
+ ) -> Agent:
49
+ """
50
+ Build Agno Agent configuration with LiteLLM.
51
+
52
+ Args:
53
+ agent_id: Unique identifier for the agent
54
+ system_prompt: System-level instructions
55
+ model_id: Model identifier (overrides default)
56
+ skills: List of skills/tools available to agent
57
+ mcp_tools: List of MCPTools instances
58
+ tool_hooks: List of tool execution hooks
59
+ user_id: User identifier (email) for Langfuse tracking
60
+ session_id: Session identifier for Langfuse tracking
61
+ organization_id: Organization identifier for Langfuse tracking
62
+ agent_name: Agent name for generation naming
63
+ skill_configs: Original skill configuration dictionaries for prompt enhancement
64
+ user_metadata: User metadata dict containing user_id, user_name, user_email, user_avatar
65
+ additional_context: Additional contextual information to inject (custom key-value pairs)
66
+
67
+ Returns:
68
+ Configured Agno Agent instance
69
+
70
+ Raises:
71
+ ValueError: If required environment variables are missing
72
+ """
73
+ # Get LiteLLM configuration from environment
74
+ litellm_api_base = os.getenv(
75
+ "LITELLM_API_BASE", "https://llm-proxy.kubiya.ai"
76
+ )
77
+ litellm_api_key = os.getenv("LITELLM_API_KEY")
78
+
79
+ if not litellm_api_key:
80
+ raise ValueError("LITELLM_API_KEY environment variable not set")
81
+
82
+ # Determine model to use with override support
83
+ # Priority: KUBIYA_MODEL_OVERRIDE > model_id > LITELLM_DEFAULT_MODEL > default
84
+ model = get_effective_model(
85
+ context_model_id=model_id,
86
+ log_context={"agent_id": agent_id},
87
+ )
88
+
89
+ logger.info(
90
+ "building_agno_agent_config",
91
+ agent_id=agent_id,
92
+ model=model,
93
+ has_skills=bool(skills),
94
+ has_mcp_tools=bool(mcp_tools),
95
+ mcp_tools_count=len(mcp_tools) if mcp_tools else 0,
96
+ has_tool_hooks=bool(tool_hooks),
97
+ )
98
+
99
+ # Build metadata for Langfuse tracking
100
+ # Format: trace_user_id = EMAIL-ORG_NAME, trace_name = "agent-chat" (simple), metadata = {details}
101
+ metadata = {}
102
+
103
+ # Always set a simple, consistent trace name
104
+ metadata["trace_name"] = "agent-chat"
105
+ metadata["generation_name"] = "agent-chat"
106
+
107
+ if user_id and organization_id:
108
+ # Format: EMAIL-ORG_NAME (EMAIL is the user_id)
109
+ metadata["trace_user_id"] = f"{user_id}-{organization_id}"
110
+ metadata["user_id"] = f"{user_id}-{organization_id}"
111
+
112
+ if session_id:
113
+ # Use session_id as trace_id to group all messages in same conversation
114
+ metadata["trace_id"] = session_id
115
+ metadata["session_id"] = session_id
116
+
117
+ # Add additional details directly to metadata for Langfuse "Metadata" column
118
+ # Any extra fields not in the standard spec will be saved as metadata
119
+ if agent_id:
120
+ metadata["agent_id"] = agent_id
121
+ if agent_name:
122
+ metadata["agent_name"] = agent_name
123
+ if user_id:
124
+ metadata["user_email"] = user_id
125
+ if organization_id:
126
+ metadata["organization_id"] = organization_id
127
+ if model_id:
128
+ metadata["model"] = model_id
129
+
130
+ # DEBUG: Log the complete metadata being sent
131
+ logger.warning(
132
+ "🔍 DEBUG: AGNO RUNTIME - LANGFUSE METADATA",
133
+ metadata=metadata,
134
+ user_id_input=user_id,
135
+ organization_id_input=organization_id,
136
+ agent_name_input=agent_name,
137
+ session_id_input=session_id,
138
+ metadata_json=str(metadata),
139
+ )
140
+
141
+ # Create LiteLLM model instance with metadata
142
+ # Use LiteLLMLangfuse for proper Langfuse integration if available
143
+ if LANGFUSE_SUPPORT and metadata:
144
+ logger.warning(
145
+ "🔍 DEBUG: USING LiteLLMLangfuse CLASS FOR LANGFUSE INTEGRATION",
146
+ metadata=metadata,
147
+ )
148
+ litellm_model = LiteLLMLangfuse(
149
+ id=f"openai/{model}",
150
+ api_base=litellm_api_base,
151
+ api_key=litellm_api_key,
152
+ metadata=metadata,
153
+ )
154
+ else:
155
+ logger.warning(
156
+ "🔍 DEBUG: USING STANDARD LiteLLM CLASS (No Langfuse integration)",
157
+ has_metadata=bool(metadata),
158
+ langfuse_available=LANGFUSE_SUPPORT,
159
+ note="Install agno with Langfuse support for proper metadata tracking"
160
+ )
161
+ litellm_model = LiteLLM(
162
+ id=f"openai/{model}",
163
+ api_base=litellm_api_base,
164
+ api_key=litellm_api_key,
165
+ metadata=metadata if metadata else None,
166
+ )
167
+
168
+ logger.warning(
169
+ "🔍 DEBUG: LITELLM MODEL CREATED",
170
+ model_class=litellm_model.__class__.__name__,
171
+ model_id=f"openai/{model}",
172
+ api_base=litellm_api_base,
173
+ has_metadata=bool(metadata),
174
+ metadata_keys=list(metadata.keys()) if metadata else [],
175
+ )
176
+
177
+ # Combine skills and MCP tools
178
+ all_tools = []
179
+ if skills:
180
+ all_tools.extend(skills)
181
+ if mcp_tools:
182
+ all_tools.extend(mcp_tools)
183
+
184
+ # Enhance system prompt with runtime-specific additions
185
+ # Create per-execution prompt builder to support dynamic skill context and user context
186
+ from control_plane_api.worker.services.skill_context_enhancement import (
187
+ SkillContextEnhancement,
188
+ )
189
+
190
+ # Create prompt builder with user context and additional context
191
+ prompt_builder = create_default_prompt_builder(
192
+ user_metadata=user_metadata,
193
+ additional_context=additional_context,
194
+ )
195
+
196
+ # Add skill context enhancement if enabled and skills are configured
197
+ skill_context_enabled = os.getenv("ENABLE_SKILL_CONTEXT_ENHANCEMENT", "true").lower() == "true"
198
+ if skill_context_enabled and skill_configs:
199
+ skill_context_enhancement = SkillContextEnhancement(skill_configs)
200
+ prompt_builder.add_enhancement(skill_context_enhancement)
201
+ logger.info(
202
+ "skill_context_enhancement_enabled",
203
+ skill_count=len(skill_configs),
204
+ agent_id=agent_id,
205
+ )
206
+
207
+ enhanced_system_prompt = prompt_builder.build(
208
+ base_prompt=system_prompt,
209
+ runtime_type="agno",
210
+ )
211
+
212
+ # Build agent configuration
213
+ agent = Agent(
214
+ name=f"Agent {agent_id}",
215
+ role=enhanced_system_prompt or "You are a helpful AI assistant",
216
+ model=litellm_model,
217
+ tools=all_tools if all_tools else None,
218
+ tool_hooks=tool_hooks if tool_hooks else None,
219
+ )
220
+
221
+ return agent
222
+
223
+
224
+ def validate_litellm_config() -> bool:
225
+ """
226
+ Validate LiteLLM configuration is present.
227
+
228
+ Returns:
229
+ True if configuration is valid
230
+
231
+ Raises:
232
+ ValueError: If configuration is invalid
233
+ """
234
+ litellm_api_key = os.getenv("LITELLM_API_KEY")
235
+
236
+ if not litellm_api_key:
237
+ raise ValueError(
238
+ "LITELLM_API_KEY environment variable not set. "
239
+ "This is required for Agno runtime to function."
240
+ )
241
+
242
+ logger.debug(
243
+ "litellm_config_validated",
244
+ api_base=os.getenv("LITELLM_API_BASE", "https://llm-proxy.kubiya.ai"),
245
+ default_model=os.environ.get("LITELLM_DEFAULT_MODEL", "kubiya/claude-sonnet-4"),
246
+ )
247
+
248
+ return True
@@ -0,0 +1,385 @@
1
+ """
2
+ Tool execution hooks for Agno runtime.
3
+
4
+ This module provides:
5
+ - Tool execution event hooks
6
+ - Real-time event publishing to Control Plane
7
+ - Event callback creation
8
+ - Tool execution tracking
9
+ - Policy enforcement for tool executions
10
+ """
11
+
12
+ import uuid
13
+ import asyncio
14
+ import json
15
+ import structlog
16
+ from typing import Callable, Any, Optional, Dict, TYPE_CHECKING
17
+
18
+ if TYPE_CHECKING:
19
+ from control_plane_client import ControlPlaneClient
20
+ from control_plane_api.worker.services.tool_enforcement import ToolEnforcementService
21
+
22
+ logger = structlog.get_logger(__name__)
23
+
24
+
25
+ def _parse_json_arguments(arguments: Dict[str, Any]) -> Dict[str, Any]:
26
+ """
27
+ Parse JSON strings in tool arguments to their proper types.
28
+
29
+ Agno sometimes passes dict/list arguments as JSON strings instead of actual objects.
30
+ This function detects and parses those strings into proper Python types.
31
+
32
+ Args:
33
+ arguments: Raw tool arguments from agno
34
+
35
+ Returns:
36
+ Parsed arguments with JSON strings converted to dicts/lists
37
+ """
38
+ if not arguments:
39
+ return arguments
40
+
41
+ parsed = {}
42
+ for key, value in arguments.items():
43
+ # If it's a string that looks like JSON, try to parse it
44
+ if isinstance(value, str) and value.strip().startswith(('{', '[')):
45
+ try:
46
+ parsed[key] = json.loads(value)
47
+ except (json.JSONDecodeError, ValueError):
48
+ # Not valid JSON, keep as string
49
+ parsed[key] = value
50
+ else:
51
+ parsed[key] = value
52
+
53
+ return parsed
54
+
55
+
56
+ def create_tool_hook_for_streaming(
57
+ control_plane: "ControlPlaneClient",
58
+ execution_id: str,
59
+ message_id: str,
60
+ enforcement_context: Optional[Dict[str, Any]] = None,
61
+ enforcement_service: Optional["ToolEnforcementService"] = None,
62
+ ) -> Callable:
63
+ """
64
+ Create a tool hook for streaming execution that publishes directly to Control Plane.
65
+
66
+ This hook publishes tool events immediately (not batched) for real-time visibility.
67
+ Used in streaming execution mode. Includes policy enforcement checks.
68
+
69
+ IMPORTANT: Parameter names MUST match Agno's expected hook signature exactly:
70
+ - function_name: str - The name of the tool being called
71
+ - function_call: Callable - The actual function to call
72
+ - arguments: Dict[str, Any] - Arguments to pass to the function
73
+
74
+ Args:
75
+ control_plane: Control Plane client for publishing events
76
+ execution_id: Execution ID for this run
77
+ message_id: Message ID for the current assistant turn (links tool to conversation)
78
+ enforcement_context: Optional context for policy enforcement
79
+ enforcement_service: Optional enforcement service for policy checks
80
+
81
+ Returns:
82
+ Tool hook function for Agno agent
83
+ """
84
+ def tool_hook(
85
+ function_name: str,
86
+ function_call: Callable,
87
+ arguments: Dict[str, Any],
88
+ **kwargs,
89
+ ):
90
+ """
91
+ Hook to capture tool execution for real-time streaming.
92
+
93
+ Agno calls this hook with exact parameter names: function_name, function_call, arguments.
94
+ The hook MUST call function_call(**arguments) and return the result.
95
+ """
96
+ tool_name = function_name or "unknown"
97
+ # Parse JSON strings in arguments to proper types (dict/list)
98
+ tool_args = _parse_json_arguments(arguments or {})
99
+ # Use UUID for tool_execution_id to avoid collisions when tools run simultaneously
100
+ tool_execution_id = f"{tool_name}_{uuid.uuid4().hex[:12]}"
101
+
102
+ logger.info(
103
+ "tool_hook_invoked",
104
+ tool_name=tool_name,
105
+ tool_execution_id=tool_execution_id,
106
+ execution_id=execution_id[:8],
107
+ has_function_call=function_call is not None and callable(function_call),
108
+ )
109
+
110
+ # Enforcement check (non-blocking)
111
+ enforcement_allowed = True
112
+ enforcement_violation = None
113
+ enforcement_metadata = {}
114
+
115
+ if enforcement_service and enforcement_context:
116
+ try:
117
+ # Run enforcement check asynchronously using control_plane's thread-local loop
118
+ # This reuses the same event loop for all async operations in this thread,
119
+ # preventing resource leaks and "await wasn't used with future" errors
120
+ loop = control_plane._get_thread_event_loop()
121
+
122
+ enforcement_allowed, enforcement_violation, enforcement_metadata = (
123
+ loop.run_until_complete(
124
+ enforcement_service.enforce_tool_execution(
125
+ tool_name=tool_name,
126
+ tool_args=tool_args,
127
+ enforcement_context={
128
+ **enforcement_context,
129
+ "execution_id": execution_id,
130
+ "message_id": message_id,
131
+ "tool_execution_id": tool_execution_id,
132
+ },
133
+ )
134
+ )
135
+ )
136
+ except Exception as e:
137
+ logger.error(
138
+ "enforcement_check_failed",
139
+ tool_name=tool_name,
140
+ error=str(e),
141
+ )
142
+ # Fail open - allow execution
143
+ enforcement_metadata = {"enforcer": "error", "error": str(e)}
144
+
145
+ # Publish tool start event (blocking call - OK in thread)
146
+ control_plane.publish_event(
147
+ execution_id=execution_id,
148
+ event_type="tool_started",
149
+ data={
150
+ "tool_name": tool_name,
151
+ "tool_execution_id": tool_execution_id,
152
+ "tool_arguments": tool_args,
153
+ "message_id": message_id, # Link tool to assistant message
154
+ "message": f"Executing tool: {tool_name}",
155
+ "enforcement": enforcement_metadata, # Add enforcement metadata
156
+ }
157
+ )
158
+
159
+ # Execute tool using Agno's function_call pattern
160
+ # IMPORTANT: Must call function_call(**arguments) as per Agno hook contract
161
+ result = None
162
+ error = None
163
+ try:
164
+ if function_call and callable(function_call):
165
+ result = function_call(**tool_args) if tool_args else function_call()
166
+ else:
167
+ raise ValueError(f"function_call not callable: {function_call}")
168
+ status = "success"
169
+
170
+ # Inject enforcement violation into result if blocked
171
+ if not enforcement_allowed and enforcement_violation:
172
+ # Prepend violation message to result
173
+ violation_prefix = f"\n{'='*60}\n"
174
+ violation_prefix += "⛔ POLICY VIOLATION DETECTED\n"
175
+ violation_prefix += f"{'='*60}\n"
176
+ violation_prefix += enforcement_violation
177
+ violation_prefix += f"\n{'='*60}\n\n"
178
+
179
+ if result:
180
+ result = violation_prefix + str(result)
181
+ else:
182
+ result = violation_prefix + "(Tool execution completed but blocked by policy)"
183
+
184
+ logger.warning(
185
+ "tool_execution_policy_violation",
186
+ tool_name=tool_name,
187
+ tool_execution_id=tool_execution_id,
188
+ enforcement_metadata=enforcement_metadata,
189
+ )
190
+
191
+ except Exception as e:
192
+ error = e
193
+ status = "failed"
194
+ logger.error(
195
+ "tool_execution_failed",
196
+ tool_name=tool_name,
197
+ error=str(e),
198
+ )
199
+
200
+ # Publish tool completion event with enforcement metadata
201
+ control_plane.publish_event(
202
+ execution_id=execution_id,
203
+ event_type="tool_completed",
204
+ data={
205
+ "tool_name": tool_name,
206
+ "tool_execution_id": tool_execution_id,
207
+ "status": status,
208
+ "tool_output": str(result)[:1000] if result else None,
209
+ "tool_error": str(error) if error else None,
210
+ "message_id": message_id, # Link tool to assistant message
211
+ "message": f"Tool {status}: {tool_name}",
212
+ "enforcement": enforcement_metadata, # Add enforcement metadata
213
+ }
214
+ )
215
+
216
+ if error:
217
+ raise error
218
+ return result
219
+
220
+ return tool_hook
221
+
222
+
223
+ def create_tool_hook_with_callback(
224
+ execution_id: str,
225
+ message_id: str,
226
+ event_callback: Callable[[Dict[str, Any]], None],
227
+ enforcement_context: Optional[Dict[str, Any]] = None,
228
+ enforcement_service: Optional["ToolEnforcementService"] = None,
229
+ ) -> Callable:
230
+ """
231
+ Create a tool hook that uses a callback for event publishing.
232
+
233
+ This hook uses a callback function to publish events, allowing for flexible
234
+ event handling (batching, filtering, etc.). Used in non-streaming execution.
235
+ Includes policy enforcement checks.
236
+
237
+ IMPORTANT: Parameter names MUST match Agno's expected hook signature exactly:
238
+ - function_name: str - The name of the tool being called
239
+ - function_call: Callable - The actual function to call
240
+ - arguments: Dict[str, Any] - Arguments to pass to the function
241
+
242
+ Args:
243
+ execution_id: Execution ID for this run
244
+ message_id: Message ID for the current assistant turn
245
+ event_callback: Callback function for event publishing
246
+ enforcement_context: Optional context for policy enforcement
247
+ enforcement_service: Optional enforcement service for policy checks
248
+
249
+ Returns:
250
+ Tool hook function for Agno agent
251
+ """
252
+ def tool_hook(
253
+ function_name: str,
254
+ function_call: Callable,
255
+ arguments: Dict[str, Any],
256
+ **kwargs,
257
+ ):
258
+ """
259
+ Hook to capture tool execution for callback-based publishing.
260
+
261
+ Agno calls this hook with exact parameter names: function_name, function_call, arguments.
262
+ The hook MUST call function_call(**arguments) and return the result.
263
+ """
264
+ tool_name = function_name or "unknown"
265
+ # Parse JSON strings in arguments to proper types (dict/list)
266
+ tool_args = _parse_json_arguments(arguments or {})
267
+ # Use UUID for tool_execution_id to avoid collisions
268
+ tool_execution_id = f"{tool_name}_{uuid.uuid4().hex[:12]}"
269
+
270
+ # Enforcement check (non-blocking)
271
+ enforcement_allowed = True
272
+ enforcement_violation = None
273
+ enforcement_metadata = {}
274
+
275
+ if enforcement_service and enforcement_context:
276
+ try:
277
+ # Run enforcement check asynchronously
278
+ # For callback-based hooks (used in non-streaming/testing), create a temporary loop
279
+ try:
280
+ loop = asyncio.get_event_loop()
281
+ if loop.is_closed():
282
+ raise RuntimeError("Event loop is closed")
283
+ except RuntimeError:
284
+ loop = asyncio.new_event_loop()
285
+ asyncio.set_event_loop(loop)
286
+
287
+ enforcement_allowed, enforcement_violation, enforcement_metadata = (
288
+ loop.run_until_complete(
289
+ enforcement_service.enforce_tool_execution(
290
+ tool_name=tool_name,
291
+ tool_args=tool_args,
292
+ enforcement_context={
293
+ **enforcement_context,
294
+ "execution_id": execution_id,
295
+ "message_id": message_id,
296
+ "tool_execution_id": tool_execution_id,
297
+ },
298
+ )
299
+ )
300
+ )
301
+ except Exception as e:
302
+ logger.error(
303
+ "enforcement_check_failed",
304
+ tool_name=tool_name,
305
+ error=str(e),
306
+ )
307
+ # Fail open - allow execution
308
+ enforcement_metadata = {"enforcer": "error", "error": str(e)}
309
+
310
+ # Publish tool start event via callback
311
+ event_callback(
312
+ {
313
+ "type": "tool_start",
314
+ "tool_name": tool_name,
315
+ "tool_execution_id": tool_execution_id,
316
+ "tool_args": tool_args,
317
+ "message_id": message_id,
318
+ "execution_id": execution_id,
319
+ "enforcement": enforcement_metadata, # Add enforcement metadata
320
+ }
321
+ )
322
+
323
+ # Execute tool using Agno's function_call pattern
324
+ # IMPORTANT: Must call function_call(**arguments) as per Agno hook contract
325
+ result = None
326
+ error = None
327
+ try:
328
+ if function_call and callable(function_call):
329
+ result = function_call(**tool_args) if tool_args else function_call()
330
+ else:
331
+ raise ValueError(f"function_call not callable: {function_call}")
332
+
333
+ status = "success"
334
+
335
+ # Inject enforcement violation into result if blocked
336
+ if not enforcement_allowed and enforcement_violation:
337
+ # Prepend violation message to result
338
+ violation_prefix = f"\n{'='*60}\n"
339
+ violation_prefix += "⛔ POLICY VIOLATION DETECTED\n"
340
+ violation_prefix += f"{'='*60}\n"
341
+ violation_prefix += enforcement_violation
342
+ violation_prefix += f"\n{'='*60}\n\n"
343
+
344
+ if result:
345
+ result = violation_prefix + str(result)
346
+ else:
347
+ result = violation_prefix + "(Tool execution completed but blocked by policy)"
348
+
349
+ logger.warning(
350
+ "tool_execution_policy_violation",
351
+ tool_name=tool_name,
352
+ tool_execution_id=tool_execution_id,
353
+ enforcement_metadata=enforcement_metadata,
354
+ )
355
+
356
+ except Exception as e:
357
+ error = e
358
+ status = "failed"
359
+ logger.error(
360
+ "tool_execution_failed",
361
+ tool_name=tool_name,
362
+ error=str(e),
363
+ )
364
+
365
+ # Publish tool completion event via callback with enforcement metadata
366
+ event_callback(
367
+ {
368
+ "type": "tool_complete",
369
+ "tool_name": tool_name,
370
+ "tool_execution_id": tool_execution_id,
371
+ "status": status,
372
+ "output": str(result)[:1000] if result else None,
373
+ "error": str(error) if error else None,
374
+ "message_id": message_id,
375
+ "execution_id": execution_id,
376
+ "enforcement": enforcement_metadata, # Add enforcement metadata
377
+ }
378
+ )
379
+
380
+ if error:
381
+ raise error
382
+
383
+ return result
384
+
385
+ return tool_hook