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,808 @@
1
+ """Context Graph Search skill implementation for all runtimes."""
2
+ import os
3
+ import json
4
+ import structlog
5
+ from typing import Optional, Dict, Any, List
6
+ import httpx
7
+ from agno.tools import Toolkit
8
+ from control_plane_api.worker.skills.builtin.schema_fix_mixin import SchemaFixMixin
9
+
10
+ logger = structlog.get_logger(__name__)
11
+
12
+
13
+ class ContextGraphSearchTools(SchemaFixMixin, Toolkit):
14
+ """
15
+ Context Graph Search toolkit for querying Neo4j-based context graphs and managing memory.
16
+
17
+ Provides tools for:
18
+ - **Memory**: Store and recall information persistently
19
+ - **Graph Search**: Search nodes by properties and relationships
20
+ - **Text Search**: Find nodes by text patterns
21
+ - **Custom Queries**: Execute Cypher queries
22
+ - **Metadata**: Get available labels and relationship types
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ api_base: Optional[str] = None,
28
+ timeout: int = 120, # 120s timeout for sync memory operations (cognify can take 30-60s)
29
+ default_limit: int = 100,
30
+ **kwargs
31
+ ):
32
+ """
33
+ Initialize Context Graph Search tools.
34
+
35
+ Args:
36
+ api_base: Context Graph API base URL (defaults to CONTEXT_GRAPH_API_BASE env var)
37
+ timeout: Request timeout in seconds (default: 120s for sync memory operations)
38
+ default_limit: Default result limit for queries
39
+ **kwargs: Additional configuration
40
+ """
41
+ super().__init__(name="context-graph-search")
42
+
43
+ # Get authentication
44
+ self.api_key = os.environ.get("KUBIYA_API_KEY")
45
+ self.org_id = os.environ.get("KUBIYA_ORG_ID")
46
+
47
+ # Get dataset name for memory scoping (defaults to "default")
48
+ self.dataset_name = os.environ.get("MEMORY_DATASET_NAME", "default")
49
+ self._dataset_id = None # Lazy-loaded and cached
50
+
51
+ if not self.api_key:
52
+ logger.warning("No KUBIYA_API_KEY provided - context graph queries will fail")
53
+
54
+ # Resolve API base URL
55
+ # Priority: 1) explicit param, 2) env var, 3) fetch from control plane, 4) fallback
56
+ if api_base:
57
+ self.api_base = api_base.rstrip("/")
58
+ elif os.environ.get("CONTEXT_GRAPH_API_BASE"):
59
+ self.api_base = os.environ.get("CONTEXT_GRAPH_API_BASE").rstrip("/")
60
+ else:
61
+ # Fetch context-graph-api URL from control plane's client config
62
+ self.api_base = self._fetch_graph_url_from_control_plane() or "https://context-graph-api.dev.kubiya.ai"
63
+ self.api_base = self.api_base.rstrip("/")
64
+
65
+ self.timeout = timeout
66
+ self.default_limit = default_limit
67
+
68
+ # Prepare headers
69
+ self.headers = {
70
+ "Authorization": f"UserKey {self.api_key}",
71
+ "Accept": "application/json",
72
+ "Content-Type": "application/json",
73
+ "X-Kubiya-Client": "agent-runtime-builtin-tool",
74
+ }
75
+
76
+ if self.org_id:
77
+ self.headers["X-Organization-ID"] = self.org_id
78
+
79
+ # Register all tool methods
80
+ # Memory tools (most important - register first)
81
+ self.register(self.store_memory)
82
+ self.register(self.recall_memory)
83
+ # Knowledge ingestion tools
84
+ self.register(self.ingest_knowledge)
85
+ self.register(self.process_dataset)
86
+ # Graph search tools
87
+ self.register(self.search_nodes)
88
+ self.register(self.get_node)
89
+ self.register(self.search_by_text)
90
+ # Advanced tools
91
+ self.register(self.get_relationships)
92
+ self.register(self.get_subgraph)
93
+ self.register(self.execute_query)
94
+ self.register(self.get_labels)
95
+ self.register(self.get_relationship_types)
96
+
97
+ logger.info(f"Initialized Context Graph Search tools with memory and ingestion support (api_base: {self.api_base}, dataset: {self.dataset_name})")
98
+
99
+ # Fix: Rebuild function schemas with proper parameters
100
+ self._rebuild_function_schemas()
101
+ def _fetch_graph_url_from_control_plane(self) -> Optional[str]:
102
+ """
103
+ Fetch the context graph API URL from control plane's client config endpoint.
104
+
105
+ Returns:
106
+ Context graph API URL or None if fetch fails
107
+ """
108
+ control_plane_url = os.environ.get("CONTROL_PLANE_URL", "http://localhost:7777")
109
+
110
+ try:
111
+ with httpx.Client(timeout=5.0) as client:
112
+ headers = {
113
+ "Authorization": f"UserKey {self.api_key}",
114
+ "Accept": "application/json",
115
+ }
116
+
117
+ response = client.get(
118
+ f"{control_plane_url}/api/v1/client/config",
119
+ headers=headers
120
+ )
121
+
122
+ if response.status_code == 200:
123
+ config = response.json()
124
+ graph_url = config.get("context_graph_api_base")
125
+ logger.info(f"Fetched graph URL from control plane: {graph_url}")
126
+ return graph_url
127
+ else:
128
+ logger.warning(f"Failed to fetch client config: HTTP {response.status_code}")
129
+ return None
130
+
131
+ except Exception as e:
132
+ logger.warning(f"Could not fetch client config from control plane: {e}")
133
+ return None
134
+
135
+ def _make_request(
136
+ self,
137
+ method: str,
138
+ path: str,
139
+ params: Optional[Dict[str, Any]] = None,
140
+ body: Optional[Dict[str, Any]] = None,
141
+ ) -> Dict[str, Any]:
142
+ """
143
+ Make HTTP request to Context Graph API.
144
+
145
+ Args:
146
+ method: HTTP method (GET, POST, etc.)
147
+ path: API path (e.g., "/api/v1/graph/nodes")
148
+ params: Query parameters
149
+ body: Request body for POST requests
150
+
151
+ Returns:
152
+ Response JSON
153
+
154
+ Raises:
155
+ Exception: If request fails
156
+ """
157
+ url = f"{self.api_base}{path}"
158
+
159
+ try:
160
+ with httpx.Client(timeout=self.timeout) as client:
161
+ if method == "GET":
162
+ response = client.get(url, headers=self.headers, params=params)
163
+ elif method == "POST":
164
+ response = client.post(url, headers=self.headers, params=params, json=body)
165
+ else:
166
+ raise ValueError(f"Unsupported HTTP method: {method}")
167
+
168
+ response.raise_for_status()
169
+ return response.json()
170
+
171
+ except httpx.TimeoutException:
172
+ raise Exception(f"Request timed out after {self.timeout}s: {method} {path}")
173
+ except httpx.HTTPStatusError as e:
174
+ raise Exception(f"HTTP {e.response.status_code}: {e.response.text}")
175
+ except Exception as e:
176
+ raise Exception(f"Request failed: {str(e)}")
177
+
178
+ def search_nodes(
179
+ self,
180
+ label: Optional[str] = None,
181
+ property_name: Optional[str] = None,
182
+ property_value: Optional[str] = None,
183
+ integration: Optional[str] = None,
184
+ skip: int = 0,
185
+ limit: Optional[int] = None,
186
+ ) -> str:
187
+ """
188
+ Search for nodes in the context graph by label and/or properties.
189
+
190
+ Args:
191
+ label: Node label to filter by (e.g., "User", "Repository", "Service")
192
+ property_name: Property name to filter by
193
+ property_value: Property value to match
194
+ integration: Integration name to filter by
195
+ skip: Number of results to skip
196
+ limit: Maximum number of results to return
197
+
198
+ Returns:
199
+ JSON string with search results
200
+
201
+ Example:
202
+ search_nodes(label="User", property_name="email", property_value="user@example.com")
203
+ search_nodes(label="Repository", integration="github")
204
+ """
205
+ body = {}
206
+ if label:
207
+ body["label"] = label
208
+ if property_name:
209
+ body["property_name"] = property_name
210
+ if property_value:
211
+ body["property_value"] = property_value
212
+
213
+ params = {
214
+ "skip": skip,
215
+ "limit": limit or self.default_limit,
216
+ }
217
+ if integration:
218
+ params["integration"] = integration
219
+
220
+ result = self._make_request("POST", "/api/v1/graph/nodes/search", params=params, body=body)
221
+ return json.dumps(result, indent=2)
222
+
223
+ def get_node(
224
+ self,
225
+ node_id: str,
226
+ integration: Optional[str] = None,
227
+ ) -> str:
228
+ """
229
+ Get a specific node by its ID.
230
+
231
+ Args:
232
+ node_id: The node ID to retrieve
233
+ integration: Optional integration name to filter by
234
+
235
+ Returns:
236
+ JSON string with node details
237
+
238
+ Example:
239
+ get_node(node_id="abc123")
240
+ """
241
+ params = {}
242
+ if integration:
243
+ params["integration"] = integration
244
+
245
+ result = self._make_request("GET", f"/api/v1/graph/nodes/{node_id}", params=params)
246
+ return json.dumps(result, indent=2)
247
+
248
+ def get_relationships(
249
+ self,
250
+ node_id: str,
251
+ direction: str = "both",
252
+ relationship_type: Optional[str] = None,
253
+ integration: Optional[str] = None,
254
+ skip: int = 0,
255
+ limit: Optional[int] = None,
256
+ ) -> str:
257
+ """
258
+ Get relationships for a specific node.
259
+
260
+ Args:
261
+ node_id: The node ID to get relationships for
262
+ direction: Relationship direction ("incoming", "outgoing", or "both")
263
+ relationship_type: Optional relationship type to filter by
264
+ integration: Optional integration name to filter by
265
+ skip: Number of results to skip
266
+ limit: Maximum number of results to return
267
+
268
+ Returns:
269
+ JSON string with relationships
270
+
271
+ Example:
272
+ get_relationships(node_id="abc123", direction="outgoing", relationship_type="OWNS")
273
+ """
274
+ params = {
275
+ "direction": direction,
276
+ "skip": skip,
277
+ "limit": limit or self.default_limit,
278
+ }
279
+ if relationship_type:
280
+ params["relationship_type"] = relationship_type
281
+ if integration:
282
+ params["integration"] = integration
283
+
284
+ result = self._make_request("GET", f"/api/v1/graph/nodes/{node_id}/relationships", params=params)
285
+ return json.dumps(result, indent=2)
286
+
287
+ def get_subgraph(
288
+ self,
289
+ node_id: str,
290
+ depth: int = 1,
291
+ relationship_types: Optional[List[str]] = None,
292
+ integration: Optional[str] = None,
293
+ ) -> str:
294
+ """
295
+ Get a subgraph starting from a node.
296
+
297
+ Args:
298
+ node_id: Starting node ID
299
+ depth: Traversal depth (1-5)
300
+ relationship_types: Optional list of relationship types to follow
301
+ integration: Optional integration name to filter by
302
+
303
+ Returns:
304
+ JSON string with subgraph (nodes and relationships)
305
+
306
+ Example:
307
+ get_subgraph(node_id="abc123", depth=2, relationship_types=["OWNS", "MANAGES"])
308
+ """
309
+ body = {
310
+ "node_id": node_id,
311
+ "depth": min(max(depth, 1), 5), # Clamp between 1 and 5
312
+ }
313
+ if relationship_types:
314
+ body["relationship_types"] = relationship_types
315
+
316
+ params = {}
317
+ if integration:
318
+ params["integration"] = integration
319
+
320
+ result = self._make_request("POST", "/api/v1/graph/subgraph", params=params, body=body)
321
+ return json.dumps(result, indent=2)
322
+
323
+ def search_by_text(
324
+ self,
325
+ property_name: str,
326
+ search_text: str,
327
+ label: Optional[str] = None,
328
+ integration: Optional[str] = None,
329
+ skip: int = 0,
330
+ limit: Optional[int] = None,
331
+ ) -> str:
332
+ """
333
+ Search nodes by text pattern in a property.
334
+
335
+ Args:
336
+ property_name: Property name to search in
337
+ search_text: Text to search for (supports partial matching)
338
+ label: Optional node label to filter by
339
+ integration: Optional integration name to filter by
340
+ skip: Number of results to skip
341
+ limit: Maximum number of results to return
342
+
343
+ Returns:
344
+ JSON string with search results
345
+
346
+ Example:
347
+ search_by_text(property_name="name", search_text="kubernetes", label="Service")
348
+ """
349
+ body = {
350
+ "property_name": property_name,
351
+ "search_text": search_text,
352
+ }
353
+ if label:
354
+ body["label"] = label
355
+
356
+ params = {
357
+ "skip": skip,
358
+ "limit": limit or self.default_limit,
359
+ }
360
+ if integration:
361
+ params["integration"] = integration
362
+
363
+ result = self._make_request("POST", "/api/v1/graph/nodes/search/text", params=params, body=body)
364
+ return json.dumps(result, indent=2)
365
+
366
+ def execute_query(
367
+ self,
368
+ query: str,
369
+ ) -> str:
370
+ """
371
+ Execute a custom Cypher query (read-only).
372
+
373
+ The query will be automatically scoped to your organization's data.
374
+ All node patterns will have the organization label injected.
375
+
376
+ Args:
377
+ query: Cypher query to execute (read-only)
378
+
379
+ Returns:
380
+ JSON string with query results
381
+
382
+ Example:
383
+ execute_query(query="MATCH (u:User)-[:OWNS]->(r:Repository) RETURN u.name, r.name LIMIT 10")
384
+ """
385
+ body = {"query": query}
386
+
387
+ result = self._make_request("POST", "/api/v1/graph/query", body=body)
388
+ return json.dumps(result, indent=2)
389
+
390
+ def get_labels(
391
+ self,
392
+ integration: Optional[str] = None,
393
+ skip: int = 0,
394
+ limit: Optional[int] = None,
395
+ ) -> str:
396
+ """
397
+ Get all node labels in the context graph.
398
+
399
+ Args:
400
+ integration: Optional integration name to filter by
401
+ skip: Number of results to skip
402
+ limit: Maximum number of results to return
403
+
404
+ Returns:
405
+ JSON string with available labels
406
+
407
+ Example:
408
+ get_labels()
409
+ get_labels(integration="github")
410
+ """
411
+ params = {
412
+ "skip": skip,
413
+ "limit": limit or self.default_limit,
414
+ }
415
+ if integration:
416
+ params["integration"] = integration
417
+
418
+ result = self._make_request("GET", "/api/v1/graph/labels", params=params)
419
+ return json.dumps(result, indent=2)
420
+
421
+ def get_relationship_types(
422
+ self,
423
+ integration: Optional[str] = None,
424
+ skip: int = 0,
425
+ limit: Optional[int] = None,
426
+ ) -> str:
427
+ """
428
+ Get all relationship types in the context graph.
429
+
430
+ Args:
431
+ integration: Optional integration name to filter by
432
+ skip: Number of results to skip
433
+ limit: Maximum number of results to return
434
+
435
+ Returns:
436
+ JSON string with available relationship types
437
+
438
+ Example:
439
+ get_relationship_types()
440
+ get_relationship_types(integration="github")
441
+ """
442
+ params = {
443
+ "skip": skip,
444
+ "limit": limit or self.default_limit,
445
+ }
446
+ if integration:
447
+ params["integration"] = integration
448
+
449
+ result = self._make_request("GET", "/api/v1/graph/relationship-types", params=params)
450
+ return json.dumps(result, indent=2)
451
+
452
+ def _get_or_create_dataset(self) -> str:
453
+ """
454
+ Get or create dataset ID for memory operations (cached).
455
+
456
+ Returns:
457
+ Dataset ID (UUID string)
458
+ """
459
+ if self._dataset_id:
460
+ return self._dataset_id
461
+
462
+ try:
463
+ # List datasets
464
+ response = self._make_request("GET", "/api/v1/graph/datasets")
465
+
466
+ # API returns {"datasets": [...]} - extract the list
467
+ datasets = response.get("datasets", []) if isinstance(response, dict) else response
468
+
469
+ if isinstance(datasets, list):
470
+ for ds in datasets:
471
+ if ds.get("name") == self.dataset_name:
472
+ self._dataset_id = ds["id"]
473
+ logger.info(f"Found dataset: {self.dataset_name} ({self._dataset_id})")
474
+ return self._dataset_id
475
+
476
+ # Create dataset if not found
477
+ create_body = {
478
+ "name": self.dataset_name,
479
+ "description": f"Memory dataset for {self.dataset_name}",
480
+ "scope": "org",
481
+ }
482
+ result = self._make_request("POST", "/api/v1/graph/datasets", body=create_body)
483
+ self._dataset_id = result.get("id")
484
+ logger.info(f"Created dataset: {self.dataset_name} ({self._dataset_id})")
485
+ return self._dataset_id
486
+
487
+ except Exception as e:
488
+ logger.error(f"Failed to get/create dataset: {e}")
489
+ raise
490
+
491
+ def store_memory(
492
+ self,
493
+ content: str,
494
+ metadata: Optional[Dict[str, Any]] = None,
495
+ wait_for_completion: bool = False,
496
+ ) -> str:
497
+ """
498
+ Store information in persistent memory for later recall.
499
+
500
+ Use this to remember important information across conversations like:
501
+ - User preferences and context
502
+ - Task progress and decisions
503
+ - System configurations and credentials
504
+ - Important facts and observations
505
+
506
+ Args:
507
+ content: Information to remember (clear, descriptive text)
508
+ metadata: Optional metadata (e.g., {"category": "user_preference", "priority": "high"})
509
+ wait_for_completion: If True, wait for indexing to complete before returning (5-30s).
510
+ If False (default), return immediately with job_id for async processing.
511
+ Set to True when you need to recall the memory immediately after storage.
512
+
513
+ Returns:
514
+ Success message with memory ID (and job_id if async mode)
515
+
516
+ Example:
517
+ # Async mode (default) - returns immediately, indexing happens in background
518
+ store_memory("User prefers Python over JavaScript for scripting tasks")
519
+
520
+ # Sync mode - waits for indexing, memory immediately searchable
521
+ store_memory("Critical security policy", wait_for_completion=True)
522
+
523
+ # With metadata
524
+ store_memory("Production DB read-only on weekends",
525
+ metadata={"category": "policy"},
526
+ wait_for_completion=True)
527
+ """
528
+ try:
529
+ dataset_id = self._get_or_create_dataset()
530
+
531
+ # API expects dataset_id and context in body, metadata optional in body
532
+ body = {
533
+ "dataset_id": dataset_id,
534
+ "context": {"content": content},
535
+ "metadata": metadata or {},
536
+ "sync": wait_for_completion, # Agent controls sync vs async
537
+ }
538
+
539
+ result = self._make_request("POST", "/api/v1/graph/memory/store", body=body)
540
+ memory_id = result.get("memory_id", "unknown")
541
+
542
+ if wait_for_completion:
543
+ # Sync mode - memory is indexed and searchable now
544
+ return f"✓ Memory stored and indexed successfully (ID: {memory_id}). Ready for immediate recall."
545
+ else:
546
+ # Async mode - memory is being processed in background
547
+ job_id = result.get("job_id", "unknown")
548
+ return f"✓ Memory storage initiated (ID: {memory_id}, Job: {job_id}). Indexing in progress (typically takes 10-30 seconds)."
549
+
550
+ except Exception as e:
551
+ logger.error(f"Failed to store memory: {e}")
552
+ return f"Error storing memory: {str(e)}"
553
+
554
+ def recall_memory(
555
+ self,
556
+ query: str,
557
+ limit: int = 5,
558
+ search_type: str = "GRAPH_COMPLETION",
559
+ ) -> str:
560
+ """
561
+ Search and retrieve previously stored memories using semantic search.
562
+
563
+ Use this to recall information from past conversations or stored context.
564
+ The search is semantic (vector similarity), so use natural language queries.
565
+
566
+ Args:
567
+ query: What you want to remember (natural language query)
568
+ limit: Maximum number of memories to return (default: 5, max: 100)
569
+ search_type: Search strategy (default: "GRAPH_COMPLETION")
570
+ - "GRAPH_COMPLETION": Best for general recall (default, recommended)
571
+ - "CHUNKS": Raw text chunks without graph context
572
+ - "RAG_COMPLETION": Retrieval-augmented generation format
573
+ - "TEMPORAL": Time-based recall (if available)
574
+ - "FEEDBACK": Feedback-enhanced results (if available)
575
+
576
+ Returns:
577
+ Formatted list of relevant memories with metadata and relevance scores
578
+
579
+ Example:
580
+ recall_memory("What are the user's preferences?")
581
+ recall_memory("production database policies", limit=10)
582
+ recall_memory("recent kubernetes issues", search_type="TEMPORAL")
583
+ """
584
+ try:
585
+ # Validate search_type
586
+ valid_types = ["GRAPH_COMPLETION", "CHUNKS", "RAG_COMPLETION", "TEMPORAL", "FEEDBACK"]
587
+ if search_type and search_type not in valid_types:
588
+ logger.warning(f"Invalid search_type '{search_type}', using GRAPH_COMPLETION")
589
+ search_type = "GRAPH_COMPLETION"
590
+
591
+ # Use semantic search endpoint (/api/v1/graph/search) which searches across all accessible memories
592
+ # This performs vector similarity search with optional graph context
593
+ body = {
594
+ "query": query,
595
+ "limit": min(max(limit, 1), 100), # Clamp between 1 and 100
596
+ "search_type": search_type,
597
+ }
598
+
599
+ result = self._make_request("POST", "/api/v1/graph/search", body=body)
600
+
601
+ # API returns {query: str, results: list, count: int}
602
+ results = result.get("results", [])
603
+
604
+ if not results or len(results) == 0:
605
+ return f"No memories found for query: '{query}'\n\nTry:\n- Using more specific keywords\n- Asking in a different way\n- Checking if memories were stored in this dataset"
606
+
607
+ # Format results with proper structure
608
+ formatted = f"Found {len(results)} relevant memories for '{query}':\n\n"
609
+
610
+ for i, item in enumerate(results, 1):
611
+ # Extract content from various possible structures
612
+ if isinstance(item, dict):
613
+ content = (
614
+ item.get('text') or
615
+ item.get('content') or
616
+ item.get('chunk_text') or
617
+ (str(item.get('data')) if item.get('data') else None) or
618
+ 'No content available'
619
+ )
620
+ else:
621
+ content = str(item)
622
+
623
+ # Truncate long content but show full length in metadata
624
+ content_preview = content[:500]
625
+ if len(content) > 500:
626
+ content_preview += f"... ({len(content)} chars total)"
627
+
628
+ formatted += f"{i}. {content_preview}\n"
629
+
630
+ # Add relevance score (most important metadata)
631
+ if isinstance(item, dict):
632
+ score = item.get('score') or item.get('similarity_score')
633
+ if isinstance(item.get('distance'), (int, float)):
634
+ # Convert distance to similarity (0 distance = 100% similarity)
635
+ score = 1.0 - min(item.get('distance'), 1.0)
636
+
637
+ if score is not None:
638
+ score_pct = score * 100
639
+ quality = "excellent" if score >= 0.8 else "good" if score >= 0.6 else "moderate"
640
+ formatted += f" 📊 Relevance: {score_pct:.1f}% ({quality} match)\n"
641
+
642
+ # Add source/type if available
643
+ source_type = item.get('type') or item.get('node_type')
644
+ if source_type:
645
+ formatted += f" đŸˇī¸ Type: {source_type}\n"
646
+
647
+ # Add dataset info if available
648
+ if item.get('dataset_name'):
649
+ formatted += f" 📁 Dataset: {item['dataset_name']}\n"
650
+
651
+ # Add timestamp if available
652
+ if item.get('created_at'):
653
+ try:
654
+ from datetime import datetime
655
+ dt = datetime.fromisoformat(item['created_at'].replace('Z', '+00:00'))
656
+ formatted += f" 📅 Created: {dt.strftime('%Y-%m-%d %H:%M')}\n"
657
+ except:
658
+ formatted += f" 📅 Created: {item['created_at']}\n"
659
+
660
+ # Add custom metadata if present (excluding internal fields)
661
+ if item.get('metadata'):
662
+ meta = item['metadata']
663
+ if isinstance(meta, dict):
664
+ # Filter out internal/empty metadata
665
+ display_meta = {k: v for k, v in meta.items()
666
+ if v and k not in ['_internal', 'embedding', 'vector']}
667
+ if display_meta:
668
+ formatted += f" â„šī¸ Metadata: {json.dumps(display_meta)}\n"
669
+
670
+ formatted += "\n"
671
+
672
+ return formatted
673
+
674
+ except Exception as e:
675
+ error_msg = str(e)
676
+ logger.error(f"Failed to recall memory: {error_msg}")
677
+
678
+ # Provide helpful error messages
679
+ if "404" in error_msg:
680
+ return f"Memory search endpoint not found. Please check API configuration."
681
+ elif "503" in error_msg:
682
+ return f"Cognitive memory features are not enabled. Contact your administrator."
683
+ elif "401" in error_msg or "403" in error_msg:
684
+ return f"Authentication failed. Check your API key and permissions."
685
+ else:
686
+ return f"Error recalling memory: {error_msg}"
687
+
688
+ def ingest_knowledge(
689
+ self,
690
+ text: str,
691
+ dataset_name: Optional[str] = None,
692
+ ) -> str:
693
+ """
694
+ Ingest text/knowledge into a dataset for later semantic search.
695
+
696
+ This adds raw text to a dataset which can then be processed with process_dataset()
697
+ to extract entities, relationships, and create embeddings for semantic search.
698
+
699
+ Use this to add:
700
+ - Documentation and guides
701
+ - Code snippets and explanations
702
+ - Meeting notes and decisions
703
+ - Technical specifications
704
+ - Any textual knowledge
705
+
706
+ Args:
707
+ text: Text content to ingest (any length - can be multiple paragraphs)
708
+ dataset_name: Target dataset name (optional, uses environment dataset if not specified)
709
+
710
+ Returns:
711
+ Success message with dataset info
712
+
713
+ Example:
714
+ ingest_knowledge("Kubernetes is a container orchestration platform...")
715
+ ingest_knowledge("Our API uses REST...", dataset_name="api-docs")
716
+ """
717
+ try:
718
+ # Use environment dataset if not specified
719
+ target_dataset = dataset_name or self.dataset_name
720
+
721
+ body = {
722
+ "text": text,
723
+ "dataset_name": target_dataset,
724
+ }
725
+
726
+ result = self._make_request("POST", "/api/v1/graph/knowledge", body=body)
727
+
728
+ status = result.get("status")
729
+ message = result.get("message", "")
730
+ text_length = result.get("text_length", len(text))
731
+
732
+ if status == "success":
733
+ return f"✓ Knowledge ingested successfully into dataset '{target_dataset}'\n" \
734
+ f" 📄 {text_length} characters added\n" \
735
+ f" â„šī¸ Run process_dataset('{target_dataset}') to make it searchable"
736
+ else:
737
+ return f"âš ī¸ Knowledge ingestion completed with warnings: {message}"
738
+
739
+ except Exception as e:
740
+ error_msg = str(e)
741
+ logger.error(f"Failed to ingest knowledge: {error_msg}")
742
+
743
+ if "404" in error_msg:
744
+ return f"Dataset '{dataset_name or self.dataset_name}' not found. Create it first with store_memory()."
745
+ elif "503" in error_msg:
746
+ return "Cognitive features are not enabled. Contact your administrator."
747
+ else:
748
+ return f"Error ingesting knowledge: {error_msg}"
749
+
750
+ def process_dataset(
751
+ self,
752
+ dataset_name: Optional[str] = None,
753
+ ) -> str:
754
+ """
755
+ Process a dataset to extract knowledge and create searchable embeddings.
756
+
757
+ This transforms raw text added via ingest_knowledge() into a semantic knowledge graph:
758
+ 1. Extracts entities and concepts
759
+ 2. Identifies relationships
760
+ 3. Creates vector embeddings
761
+ 4. Makes content searchable via recall_memory()
762
+
763
+ **Important**: This operation can take 10-60 seconds depending on dataset size.
764
+
765
+ Args:
766
+ dataset_name: Dataset to process (optional, uses environment dataset if not specified)
767
+
768
+ Returns:
769
+ Processing status and result info
770
+
771
+ Example:
772
+ process_dataset()
773
+ process_dataset("api-docs")
774
+ """
775
+ try:
776
+ # Use environment dataset if not specified
777
+ target_dataset = dataset_name or self.dataset_name
778
+
779
+ body = {
780
+ "dataset_name": target_dataset,
781
+ }
782
+
783
+ result = self._make_request("POST", "/api/v1/graph/cognify", body=body)
784
+
785
+ status = result.get("status")
786
+ message = result.get("message", "")
787
+
788
+ if status == "success":
789
+ return f"✓ Dataset '{target_dataset}' processed successfully\n" \
790
+ f" 📊 Knowledge graph created and embeddings generated\n" \
791
+ f" 🔍 Content is now searchable with recall_memory()\n" \
792
+ f" â„šī¸ {message}"
793
+ else:
794
+ return f"âš ī¸ Processing completed with status: {status}\n" \
795
+ f" Message: {message}"
796
+
797
+ except Exception as e:
798
+ error_msg = str(e)
799
+ logger.error(f"Failed to process dataset: {error_msg}")
800
+
801
+ if "404" in error_msg:
802
+ return f"Dataset '{dataset_name or self.dataset_name}' not found."
803
+ elif "400" in error_msg:
804
+ return f"Dataset '{dataset_name or self.dataset_name}' has no data. Add content with ingest_knowledge() first."
805
+ elif "503" in error_msg:
806
+ return "Cognitive features are not enabled. Contact your administrator."
807
+ else:
808
+ return f"Error processing dataset: {error_msg}"