kubiya-control-plane-api 0.1.0__py3-none-any.whl → 0.3.4__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.

Potentially problematic release.


This version of kubiya-control-plane-api might be problematic. Click here for more details.

Files changed (185) hide show
  1. control_plane_api/README.md +266 -0
  2. control_plane_api/__init__.py +0 -0
  3. control_plane_api/__version__.py +1 -0
  4. control_plane_api/alembic/README +1 -0
  5. control_plane_api/alembic/env.py +98 -0
  6. control_plane_api/alembic/script.py.mako +28 -0
  7. control_plane_api/alembic/versions/1382bec74309_initial_migration_with_all_models.py +251 -0
  8. control_plane_api/alembic/versions/1f54bc2a37e3_add_analytics_tables.py +162 -0
  9. control_plane_api/alembic/versions/2e4cb136dc10_rename_toolset_ids_to_skill_ids_in_teams.py +30 -0
  10. control_plane_api/alembic/versions/31cd69a644ce_add_skill_templates_table.py +28 -0
  11. control_plane_api/alembic/versions/89e127caa47d_add_jobs_and_job_executions_tables.py +161 -0
  12. control_plane_api/alembic/versions/add_llm_models_table.py +51 -0
  13. control_plane_api/alembic/versions/b0e10697f212_add_runtime_column_to_teams_simple.py +42 -0
  14. control_plane_api/alembic/versions/ce43b24b63bf_add_execution_trigger_source_and_fix_.py +155 -0
  15. control_plane_api/alembic/versions/d4eaf16e3f8d_rename_toolsets_to_skills.py +84 -0
  16. control_plane_api/alembic/versions/efa2dc427da1_rename_metadata_to_custom_metadata.py +32 -0
  17. control_plane_api/alembic/versions/f973b431d1ce_add_workflow_executor_to_skill_types.py +44 -0
  18. control_plane_api/alembic.ini +148 -0
  19. control_plane_api/api/index.py +12 -0
  20. control_plane_api/app/__init__.py +11 -0
  21. control_plane_api/app/activities/__init__.py +20 -0
  22. control_plane_api/app/activities/agent_activities.py +379 -0
  23. control_plane_api/app/activities/team_activities.py +410 -0
  24. control_plane_api/app/activities/temporal_cloud_activities.py +577 -0
  25. control_plane_api/app/config/__init__.py +35 -0
  26. control_plane_api/app/config/api_config.py +354 -0
  27. control_plane_api/app/config/model_pricing.py +318 -0
  28. control_plane_api/app/config.py +95 -0
  29. control_plane_api/app/database.py +135 -0
  30. control_plane_api/app/exceptions.py +408 -0
  31. control_plane_api/app/lib/__init__.py +11 -0
  32. control_plane_api/app/lib/job_executor.py +312 -0
  33. control_plane_api/app/lib/kubiya_client.py +235 -0
  34. control_plane_api/app/lib/litellm_pricing.py +166 -0
  35. control_plane_api/app/lib/planning_tools/__init__.py +22 -0
  36. control_plane_api/app/lib/planning_tools/agents.py +155 -0
  37. control_plane_api/app/lib/planning_tools/base.py +189 -0
  38. control_plane_api/app/lib/planning_tools/environments.py +214 -0
  39. control_plane_api/app/lib/planning_tools/resources.py +240 -0
  40. control_plane_api/app/lib/planning_tools/teams.py +198 -0
  41. control_plane_api/app/lib/policy_enforcer_client.py +939 -0
  42. control_plane_api/app/lib/redis_client.py +436 -0
  43. control_plane_api/app/lib/supabase.py +71 -0
  44. control_plane_api/app/lib/temporal_client.py +138 -0
  45. control_plane_api/app/lib/validation/__init__.py +20 -0
  46. control_plane_api/app/lib/validation/runtime_validation.py +287 -0
  47. control_plane_api/app/main.py +128 -0
  48. control_plane_api/app/middleware/__init__.py +8 -0
  49. control_plane_api/app/middleware/auth.py +513 -0
  50. control_plane_api/app/middleware/exception_handler.py +267 -0
  51. control_plane_api/app/middleware/rate_limiting.py +384 -0
  52. control_plane_api/app/middleware/request_id.py +202 -0
  53. control_plane_api/app/models/__init__.py +27 -0
  54. control_plane_api/app/models/agent.py +79 -0
  55. control_plane_api/app/models/analytics.py +206 -0
  56. control_plane_api/app/models/associations.py +81 -0
  57. control_plane_api/app/models/environment.py +63 -0
  58. control_plane_api/app/models/execution.py +93 -0
  59. control_plane_api/app/models/job.py +179 -0
  60. control_plane_api/app/models/llm_model.py +75 -0
  61. control_plane_api/app/models/presence.py +49 -0
  62. control_plane_api/app/models/project.py +47 -0
  63. control_plane_api/app/models/session.py +38 -0
  64. control_plane_api/app/models/team.py +66 -0
  65. control_plane_api/app/models/workflow.py +55 -0
  66. control_plane_api/app/policies/README.md +121 -0
  67. control_plane_api/app/policies/approved_users.rego +62 -0
  68. control_plane_api/app/policies/business_hours.rego +51 -0
  69. control_plane_api/app/policies/rate_limiting.rego +100 -0
  70. control_plane_api/app/policies/tool_restrictions.rego +86 -0
  71. control_plane_api/app/routers/__init__.py +4 -0
  72. control_plane_api/app/routers/agents.py +364 -0
  73. control_plane_api/app/routers/agents_v2.py +1260 -0
  74. control_plane_api/app/routers/analytics.py +1014 -0
  75. control_plane_api/app/routers/context_manager.py +562 -0
  76. control_plane_api/app/routers/environment_context.py +270 -0
  77. control_plane_api/app/routers/environments.py +715 -0
  78. control_plane_api/app/routers/execution_environment.py +517 -0
  79. control_plane_api/app/routers/executions.py +1911 -0
  80. control_plane_api/app/routers/health.py +92 -0
  81. control_plane_api/app/routers/health_v2.py +326 -0
  82. control_plane_api/app/routers/integrations.py +274 -0
  83. control_plane_api/app/routers/jobs.py +1344 -0
  84. control_plane_api/app/routers/models.py +82 -0
  85. control_plane_api/app/routers/models_v2.py +361 -0
  86. control_plane_api/app/routers/policies.py +639 -0
  87. control_plane_api/app/routers/presence.py +234 -0
  88. control_plane_api/app/routers/projects.py +902 -0
  89. control_plane_api/app/routers/runners.py +379 -0
  90. control_plane_api/app/routers/runtimes.py +172 -0
  91. control_plane_api/app/routers/secrets.py +155 -0
  92. control_plane_api/app/routers/skills.py +1001 -0
  93. control_plane_api/app/routers/skills_definitions.py +140 -0
  94. control_plane_api/app/routers/task_planning.py +1256 -0
  95. control_plane_api/app/routers/task_queues.py +654 -0
  96. control_plane_api/app/routers/team_context.py +270 -0
  97. control_plane_api/app/routers/teams.py +1400 -0
  98. control_plane_api/app/routers/worker_queues.py +1545 -0
  99. control_plane_api/app/routers/workers.py +935 -0
  100. control_plane_api/app/routers/workflows.py +204 -0
  101. control_plane_api/app/runtimes/__init__.py +6 -0
  102. control_plane_api/app/runtimes/validation.py +344 -0
  103. control_plane_api/app/schemas/job_schemas.py +295 -0
  104. control_plane_api/app/services/__init__.py +1 -0
  105. control_plane_api/app/services/agno_service.py +619 -0
  106. control_plane_api/app/services/litellm_service.py +190 -0
  107. control_plane_api/app/services/policy_service.py +525 -0
  108. control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
  109. control_plane_api/app/skills/__init__.py +44 -0
  110. control_plane_api/app/skills/base.py +229 -0
  111. control_plane_api/app/skills/business_intelligence.py +189 -0
  112. control_plane_api/app/skills/data_visualization.py +154 -0
  113. control_plane_api/app/skills/docker.py +104 -0
  114. control_plane_api/app/skills/file_generation.py +94 -0
  115. control_plane_api/app/skills/file_system.py +110 -0
  116. control_plane_api/app/skills/python.py +92 -0
  117. control_plane_api/app/skills/registry.py +65 -0
  118. control_plane_api/app/skills/shell.py +102 -0
  119. control_plane_api/app/skills/workflow_executor.py +469 -0
  120. control_plane_api/app/utils/workflow_executor.py +354 -0
  121. control_plane_api/app/workflows/__init__.py +11 -0
  122. control_plane_api/app/workflows/agent_execution.py +507 -0
  123. control_plane_api/app/workflows/agent_execution_with_skills.py +222 -0
  124. control_plane_api/app/workflows/namespace_provisioning.py +326 -0
  125. control_plane_api/app/workflows/team_execution.py +399 -0
  126. control_plane_api/scripts/seed_models.py +239 -0
  127. control_plane_api/worker/__init__.py +0 -0
  128. control_plane_api/worker/activities/__init__.py +0 -0
  129. control_plane_api/worker/activities/agent_activities.py +1241 -0
  130. control_plane_api/worker/activities/approval_activities.py +234 -0
  131. control_plane_api/worker/activities/runtime_activities.py +388 -0
  132. control_plane_api/worker/activities/skill_activities.py +267 -0
  133. control_plane_api/worker/activities/team_activities.py +1217 -0
  134. control_plane_api/worker/config/__init__.py +31 -0
  135. control_plane_api/worker/config/worker_config.py +275 -0
  136. control_plane_api/worker/control_plane_client.py +529 -0
  137. control_plane_api/worker/examples/analytics_integration_example.py +362 -0
  138. control_plane_api/worker/models/__init__.py +1 -0
  139. control_plane_api/worker/models/inputs.py +89 -0
  140. control_plane_api/worker/runtimes/__init__.py +31 -0
  141. control_plane_api/worker/runtimes/base.py +789 -0
  142. control_plane_api/worker/runtimes/claude_code_runtime.py +1443 -0
  143. control_plane_api/worker/runtimes/default_runtime.py +617 -0
  144. control_plane_api/worker/runtimes/factory.py +173 -0
  145. control_plane_api/worker/runtimes/validation.py +93 -0
  146. control_plane_api/worker/services/__init__.py +1 -0
  147. control_plane_api/worker/services/agent_executor.py +422 -0
  148. control_plane_api/worker/services/agent_executor_v2.py +383 -0
  149. control_plane_api/worker/services/analytics_collector.py +457 -0
  150. control_plane_api/worker/services/analytics_service.py +464 -0
  151. control_plane_api/worker/services/approval_tools.py +310 -0
  152. control_plane_api/worker/services/approval_tools_agno.py +207 -0
  153. control_plane_api/worker/services/cancellation_manager.py +177 -0
  154. control_plane_api/worker/services/data_visualization.py +827 -0
  155. control_plane_api/worker/services/jira_tools.py +257 -0
  156. control_plane_api/worker/services/runtime_analytics.py +328 -0
  157. control_plane_api/worker/services/session_service.py +194 -0
  158. control_plane_api/worker/services/skill_factory.py +175 -0
  159. control_plane_api/worker/services/team_executor.py +574 -0
  160. control_plane_api/worker/services/team_executor_v2.py +465 -0
  161. control_plane_api/worker/services/workflow_executor_tools.py +1418 -0
  162. control_plane_api/worker/tests/__init__.py +1 -0
  163. control_plane_api/worker/tests/e2e/__init__.py +0 -0
  164. control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
  165. control_plane_api/worker/tests/integration/__init__.py +0 -0
  166. control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
  167. control_plane_api/worker/tests/unit/__init__.py +0 -0
  168. control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
  169. control_plane_api/worker/utils/__init__.py +1 -0
  170. control_plane_api/worker/utils/chunk_batcher.py +305 -0
  171. control_plane_api/worker/utils/retry_utils.py +60 -0
  172. control_plane_api/worker/utils/streaming_utils.py +373 -0
  173. control_plane_api/worker/worker.py +753 -0
  174. control_plane_api/worker/workflows/__init__.py +0 -0
  175. control_plane_api/worker/workflows/agent_execution.py +589 -0
  176. control_plane_api/worker/workflows/team_execution.py +429 -0
  177. kubiya_control_plane_api-0.3.4.dist-info/METADATA +229 -0
  178. kubiya_control_plane_api-0.3.4.dist-info/RECORD +182 -0
  179. kubiya_control_plane_api-0.3.4.dist-info/entry_points.txt +2 -0
  180. kubiya_control_plane_api-0.3.4.dist-info/top_level.txt +1 -0
  181. kubiya_control_plane_api-0.1.0.dist-info/METADATA +0 -66
  182. kubiya_control_plane_api-0.1.0.dist-info/RECORD +0 -5
  183. kubiya_control_plane_api-0.1.0.dist-info/top_level.txt +0 -1
  184. {kubiya_control_plane_api-0.1.0.dist-info/licenses → control_plane_api}/LICENSE +0 -0
  185. {kubiya_control_plane_api-0.1.0.dist-info → kubiya_control_plane_api-0.3.4.dist-info}/WHEEL +0 -0
@@ -0,0 +1,429 @@
1
+ """Team execution workflow for Temporal"""
2
+
3
+ from dataclasses import dataclass, field
4
+ from datetime import timedelta
5
+ from typing import Optional, List, Dict, Any
6
+ from temporalio import workflow
7
+ import asyncio
8
+
9
+ with workflow.unsafe.imports_passed_through():
10
+ from control_plane_api.worker.activities.team_activities import (
11
+ get_team_agents,
12
+ execute_team_coordination,
13
+ ActivityGetTeamAgentsInput,
14
+ ActivityExecuteTeamInput,
15
+ )
16
+ from control_plane_api.worker.activities.agent_activities import (
17
+ update_execution_status,
18
+ ActivityUpdateExecutionInput,
19
+ )
20
+
21
+
22
+ @dataclass
23
+ class TeamExecutionInput:
24
+ """Input for team execution workflow"""
25
+ execution_id: str
26
+ team_id: str
27
+ organization_id: str
28
+ prompt: str
29
+ system_prompt: Optional[str] = None
30
+ team_config: dict = None
31
+ user_metadata: dict = None
32
+ mcp_servers: dict = None # MCP servers configuration
33
+
34
+ def __post_init__(self):
35
+ if self.team_config is None:
36
+ self.team_config = {}
37
+ if self.user_metadata is None:
38
+ self.user_metadata = {}
39
+ if self.mcp_servers is None:
40
+ self.mcp_servers = {}
41
+
42
+
43
+ @dataclass
44
+ class ChatMessage:
45
+ """Represents a message in the conversation"""
46
+ role: str # "user", "assistant", "system", "tool"
47
+ content: str
48
+ timestamp: str
49
+ tool_name: Optional[str] = None
50
+ tool_input: Optional[Dict[str, Any]] = None
51
+ tool_output: Optional[Dict[str, Any]] = None
52
+
53
+
54
+ @dataclass
55
+ class ExecutionState:
56
+ """Current state of the execution for queries"""
57
+ status: str # "pending", "running", "waiting_for_input", "completed", "failed"
58
+ messages: List[ChatMessage] = field(default_factory=list)
59
+ current_response: str = ""
60
+ error_message: Optional[str] = None
61
+ usage: Dict[str, Any] = field(default_factory=dict)
62
+ metadata: Dict[str, Any] = field(default_factory=dict)
63
+ is_waiting_for_input: bool = False
64
+ should_complete: bool = False
65
+
66
+
67
+ @workflow.defn
68
+ class TeamExecutionWorkflow:
69
+ """
70
+ Workflow for executing a team of agents with HITL support.
71
+
72
+ This workflow:
73
+ 1. Gets team agents
74
+ 2. Coordinates execution across agents
75
+ 3. Aggregates results
76
+ 4. Updates execution status
77
+ 5. Supports queries for real-time state access
78
+ 6. Supports signals for adding followup messages
79
+ """
80
+
81
+ def __init__(self) -> None:
82
+ """Initialize workflow state"""
83
+ self._state = ExecutionState(status="pending")
84
+ self._lock = asyncio.Lock()
85
+ self._new_message_count = 0
86
+ self._processed_message_count = 0
87
+
88
+ @workflow.query
89
+ def get_state(self) -> ExecutionState:
90
+ """Query handler: Get current execution state including messages and status"""
91
+ return self._state
92
+
93
+ @workflow.signal
94
+ async def add_message(self, message: ChatMessage) -> None:
95
+ """
96
+ Signal handler: Add a message to the conversation.
97
+ This allows clients to send followup messages while the workflow is running.
98
+ """
99
+ async with self._lock:
100
+ self._state.messages.append(message)
101
+ self._new_message_count += 1
102
+ self._state.is_waiting_for_input = False
103
+ workflow.logger.info(
104
+ f"Message added to team conversation",
105
+ extra={
106
+ "role": message.role,
107
+ "content_preview": message.content[:100] if message.content else "",
108
+ "total_messages": len(self._state.messages)
109
+ }
110
+ )
111
+
112
+ @workflow.signal
113
+ async def mark_as_done(self) -> None:
114
+ """
115
+ Signal handler: Mark the workflow as complete.
116
+ """
117
+ async with self._lock:
118
+ self._state.should_complete = True
119
+ self._state.is_waiting_for_input = False
120
+ workflow.logger.info("Team workflow marked as done by user")
121
+
122
+ @workflow.signal
123
+ async def update_streaming_response(self, current_response: str) -> None:
124
+ """
125
+ Signal handler: Update current streaming response.
126
+ Activity sends this periodically during execution for state tracking.
127
+ """
128
+ async with self._lock:
129
+ self._state.current_response = current_response
130
+ workflow.logger.info(
131
+ f"Streaming response updated",
132
+ extra={"response_length": len(current_response)}
133
+ )
134
+
135
+ @workflow.run
136
+ async def run(self, input: TeamExecutionInput) -> dict:
137
+ """
138
+ Run the team execution workflow with HITL pattern.
139
+
140
+ This workflow implements a continuous conversation loop:
141
+ 1. Process the initial user message
142
+ 2. Execute team coordination and return response
143
+ 3. Wait for user input (signals)
144
+ 4. Process followup messages in a loop
145
+ 5. Only complete when user explicitly marks as done
146
+
147
+ Args:
148
+ input: Workflow input with team execution details
149
+
150
+ Returns:
151
+ Team execution result dict
152
+ """
153
+ workflow.logger.info(
154
+ f"Starting team execution workflow with HITL pattern",
155
+ extra={
156
+ "execution_id": input.execution_id,
157
+ "team_id": input.team_id,
158
+ "organization_id": input.organization_id,
159
+ }
160
+ )
161
+
162
+ # Initialize state with user's initial message
163
+ self._state.messages.append(ChatMessage(
164
+ role="user",
165
+ content=input.prompt,
166
+ timestamp=workflow.now().isoformat(),
167
+ ))
168
+ self._state.status = "running"
169
+ self._new_message_count = 1
170
+ self._processed_message_count = 0
171
+
172
+ try:
173
+ # Step 1: Update execution status to running
174
+ await workflow.execute_activity(
175
+ update_execution_status,
176
+ ActivityUpdateExecutionInput(
177
+ execution_id=input.execution_id,
178
+ status="running",
179
+ started_at=workflow.now().isoformat(),
180
+ execution_metadata={
181
+ "workflow_started": True,
182
+ "hitl_enabled": True,
183
+ },
184
+ ),
185
+ start_to_close_timeout=timedelta(seconds=30),
186
+ )
187
+
188
+ # Step 2: Get team agents once at the beginning
189
+ workflow.logger.info(
190
+ f"[WORKFLOW] About to call get_team_agents",
191
+ extra={
192
+ "team_id": input.team_id,
193
+ "organization_id": input.organization_id,
194
+ }
195
+ )
196
+
197
+ team_agents = await workflow.execute_activity(
198
+ get_team_agents,
199
+ ActivityGetTeamAgentsInput(
200
+ team_id=input.team_id,
201
+ organization_id=input.organization_id,
202
+ ),
203
+ start_to_close_timeout=timedelta(seconds=30),
204
+ )
205
+
206
+ workflow.logger.info(
207
+ f"[WORKFLOW] get_team_agents returned",
208
+ extra={
209
+ "result": team_agents,
210
+ "agents_count": len(team_agents.get("agents", [])) if team_agents else 0,
211
+ }
212
+ )
213
+
214
+ if not team_agents.get("agents"):
215
+ workflow.logger.error(
216
+ f"[WORKFLOW] NO AGENTS RETURNED!",
217
+ extra={
218
+ "team_agents": team_agents,
219
+ "team_id": input.team_id,
220
+ "organization_id": input.organization_id,
221
+ }
222
+ )
223
+ raise ValueError("No agents found in team")
224
+
225
+ # HITL Conversation Loop - Continue until user marks as done
226
+ conversation_turn = 0
227
+ while not self._state.should_complete:
228
+ conversation_turn += 1
229
+ workflow.logger.info(
230
+ f"Starting team conversation turn {conversation_turn}",
231
+ extra={"turn": conversation_turn, "message_count": len(self._state.messages)}
232
+ )
233
+
234
+ # Get the latest user message
235
+ latest_message = self._state.messages[-1] if self._state.messages else None
236
+ latest_prompt = latest_message.content if latest_message and latest_message.role == "user" else input.prompt
237
+
238
+ # Step 3: Execute team coordination
239
+ team_result = await workflow.execute_activity(
240
+ execute_team_coordination,
241
+ ActivityExecuteTeamInput(
242
+ execution_id=input.execution_id,
243
+ team_id=input.team_id,
244
+ organization_id=input.organization_id,
245
+ prompt=latest_prompt,
246
+ system_prompt=input.system_prompt,
247
+ agents=team_agents["agents"],
248
+ team_config=input.team_config,
249
+ mcp_servers=input.mcp_servers, # Pass MCP servers
250
+ session_id=input.execution_id, # Use execution_id as session_id
251
+ user_id=input.user_metadata.get("user_id") if input.user_metadata else None,
252
+ # Activity reads CONTROL_PLANE_URL and KUBIYA_API_KEY from worker environment
253
+ ),
254
+ start_to_close_timeout=timedelta(minutes=30), # Teams can take longer
255
+ )
256
+
257
+ # Add tool execution status messages (real-time updates)
258
+ if team_result.get("tool_execution_messages"):
259
+ async with self._lock:
260
+ for tool_msg in team_result["tool_execution_messages"]:
261
+ self._state.messages.append(ChatMessage(
262
+ role="system",
263
+ content=tool_msg.get("content", ""),
264
+ timestamp=tool_msg.get("timestamp", workflow.now().isoformat()),
265
+ tool_name=tool_msg.get("tool_name"),
266
+ ))
267
+
268
+ # Add tool messages to state (detailed tool info)
269
+ if team_result.get("tool_messages"):
270
+ async with self._lock:
271
+ for tool_msg in team_result["tool_messages"]:
272
+ self._state.messages.append(ChatMessage(
273
+ role="tool",
274
+ content=tool_msg.get("content", ""),
275
+ timestamp=tool_msg.get("timestamp", workflow.now().isoformat()),
276
+ tool_name=tool_msg.get("tool_name"),
277
+ tool_input=tool_msg.get("tool_input"),
278
+ ))
279
+
280
+ # Update state with team response
281
+ if team_result.get("response"):
282
+ async with self._lock:
283
+ self._state.messages.append(ChatMessage(
284
+ role="assistant",
285
+ content=team_result["response"],
286
+ timestamp=workflow.now().isoformat(),
287
+ ))
288
+ self._state.current_response = team_result["response"]
289
+ self._processed_message_count += 1
290
+
291
+ # Update usage and metadata (accumulate across turns)
292
+ if team_result.get("usage"):
293
+ current_usage = self._state.usage
294
+ new_usage = team_result.get("usage", {})
295
+ self._state.usage = {
296
+ "input_tokens": current_usage.get("input_tokens", 0) + new_usage.get("input_tokens", 0),
297
+ "output_tokens": current_usage.get("output_tokens", 0) + new_usage.get("output_tokens", 0),
298
+ "total_tokens": current_usage.get("total_tokens", 0) + new_usage.get("total_tokens", 0),
299
+ }
300
+
301
+ # Update metadata
302
+ self._state.metadata.update({
303
+ "agent_count": len(team_agents["agents"]),
304
+ "coordination_type": team_result.get("coordination_type"),
305
+ "conversation_turns": conversation_turn,
306
+ })
307
+
308
+ # Check if team execution failed
309
+ if not team_result.get("success"):
310
+ self._state.status = "failed"
311
+ self._state.error_message = team_result.get("error")
312
+ break
313
+
314
+ # Update execution status to waiting_for_input
315
+ self._state.status = "waiting_for_input"
316
+ self._state.is_waiting_for_input = True
317
+
318
+ # Update database to reflect waiting state
319
+ await workflow.execute_activity(
320
+ update_execution_status,
321
+ ActivityUpdateExecutionInput(
322
+ execution_id=input.execution_id,
323
+ status="waiting_for_input",
324
+ response=self._state.current_response,
325
+ usage=self._state.usage,
326
+ execution_metadata={
327
+ **self._state.metadata,
328
+ "conversation_turns": conversation_turn,
329
+ "waiting_for_user": True,
330
+ },
331
+ ),
332
+ start_to_close_timeout=timedelta(seconds=30),
333
+ )
334
+
335
+ workflow.logger.info(
336
+ f"Waiting for user input after team turn {conversation_turn}",
337
+ extra={"turn": conversation_turn}
338
+ )
339
+
340
+ # Wait for either new message or mark as done
341
+ await workflow.wait_condition(
342
+ lambda: self._new_message_count > self._processed_message_count or self._state.should_complete,
343
+ timeout=timedelta(hours=24)
344
+ )
345
+
346
+ if self._state.should_complete:
347
+ workflow.logger.info("User marked team workflow as done")
348
+ break
349
+
350
+ # Continue loop to process new message
351
+ self._state.status = "running"
352
+
353
+ # Conversation complete - finalize workflow
354
+ final_status = "failed" if self._state.status == "failed" else "completed"
355
+ self._state.status = final_status
356
+
357
+ await workflow.execute_activity(
358
+ update_execution_status,
359
+ ActivityUpdateExecutionInput(
360
+ execution_id=input.execution_id,
361
+ status=final_status,
362
+ completed_at=workflow.now().isoformat(),
363
+ response=self._state.current_response,
364
+ error_message=self._state.error_message,
365
+ usage=self._state.usage,
366
+ execution_metadata={
367
+ **self._state.metadata,
368
+ "workflow_completed": True,
369
+ "total_conversation_turns": conversation_turn,
370
+ },
371
+ ),
372
+ start_to_close_timeout=timedelta(seconds=30),
373
+ )
374
+
375
+ workflow.logger.info(
376
+ f"Team execution workflow completed with HITL",
377
+ extra={
378
+ "execution_id": input.execution_id,
379
+ "status": final_status,
380
+ "conversation_turns": conversation_turn,
381
+ }
382
+ )
383
+
384
+ return {
385
+ "success": final_status == "completed",
386
+ "execution_id": input.execution_id,
387
+ "status": final_status,
388
+ "response": self._state.current_response,
389
+ "usage": self._state.usage,
390
+ "conversation_turns": conversation_turn,
391
+ }
392
+
393
+ except Exception as e:
394
+ # Update state with error
395
+ self._state.status = "failed"
396
+ self._state.error_message = str(e)
397
+ self._state.metadata["error_type"] = type(e).__name__
398
+
399
+ workflow.logger.error(
400
+ f"Team execution workflow failed",
401
+ extra={
402
+ "execution_id": input.execution_id,
403
+ "error": str(e),
404
+ }
405
+ )
406
+
407
+ # Update execution as failed
408
+ try:
409
+ await workflow.execute_activity(
410
+ update_execution_status,
411
+ ActivityUpdateExecutionInput(
412
+ execution_id=input.execution_id,
413
+ status="failed",
414
+ completed_at=workflow.now().isoformat(),
415
+ error_message=f"Workflow error: {str(e)}",
416
+ execution_metadata={
417
+ "workflow_error": True,
418
+ "error_type": type(e).__name__,
419
+ },
420
+ ),
421
+ start_to_close_timeout=timedelta(seconds=30),
422
+ )
423
+ except Exception as update_error:
424
+ workflow.logger.error(
425
+ f"Failed to update status after error",
426
+ extra={"error": str(update_error)}
427
+ )
428
+
429
+ raise
@@ -0,0 +1,229 @@
1
+ Metadata-Version: 2.4
2
+ Name: kubiya-control-plane-api
3
+ Version: 0.3.4
4
+ Summary: Agent Control Plane API - Multi-tenant AI agent orchestration and management platform
5
+ Author-email: Kubiya <support@kubiya.ai>
6
+ License-Expression: MIT
7
+ Keywords: ai,agents,orchestration,temporal,fastapi,multi-tenant,workflow,automation
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Framework :: FastAPI
16
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
17
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
18
+ Requires-Python: <4.0.0,>=3.10
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: httpx<1.0.0,>=0.26.0
21
+ Requires-Dist: pydantic<3.0.0,>=2.5.3
22
+ Requires-Dist: fastapi<1.0.0,>=0.109.0
23
+ Requires-Dist: uvicorn[standard]<1.0.0,>=0.27.0
24
+ Requires-Dist: sqlalchemy<3.0.0,>=2.0.25
25
+ Requires-Dist: psycopg2-binary<3.0.0,>=2.9.9
26
+ Requires-Dist: pydantic-settings<3.0.0,>=2.1.0
27
+ Requires-Dist: alembic<2.0.0,>=1.13.1
28
+ Requires-Dist: supabase<3.0.0,>=2.3.0
29
+ Requires-Dist: temporalio<2.0.0,>=1.5.0
30
+ Requires-Dist: python-multipart<1.0.0,>=0.0.6
31
+ Requires-Dist: python-jose[cryptography]<4.0.0,>=3.3.0
32
+ Requires-Dist: PyJWT<3.0.0,>=2.8.0
33
+ Requires-Dist: passlib[bcrypt]<2.0.0,>=1.7.4
34
+ Requires-Dist: mangum<1.0.0,>=0.17.0
35
+ Requires-Dist: structlog<25.0.0,>=24.1.0
36
+ Requires-Dist: croniter<3.0.0,>=2.0.0
37
+ Requires-Dist: litellm<2.0.0,>=1.30.0
38
+ Requires-Dist: agno<3.0.0,>=2.0.10
39
+ Requires-Dist: mcp<2.0.0,>=1.0.0
40
+ Requires-Dist: redis<6.0.0,>=5.0.0
41
+ Requires-Dist: kubiya-sdk<3.0.0,>=2.0.3
42
+ Provides-Extra: worker
43
+ Requires-Dist: temporalio<2.0.0,>=1.5.0; extra == "worker"
44
+ Requires-Dist: structlog<25.0.0,>=24.1.0; extra == "worker"
45
+ Requires-Dist: agno<3.0.0,>=2.0.10; extra == "worker"
46
+ Requires-Dist: litellm<2.0.0,>=1.30.0; extra == "worker"
47
+ Requires-Dist: psutil<6.0.0,>=5.9.0; extra == "worker"
48
+ Requires-Dist: docker<8.0.0,>=7.0.0; extra == "worker"
49
+ Requires-Dist: redis<6.0.0,>=5.0.0; extra == "worker"
50
+ Requires-Dist: mcp<2.0.0,>=1.0.0; extra == "worker"
51
+ Requires-Dist: nest-asyncio<2.0.0,>=1.5.0; extra == "worker"
52
+ Requires-Dist: claude-agent-sdk<1.0.0,>=0.1.0; extra == "worker"
53
+ Provides-Extra: api
54
+ Requires-Dist: fastapi<1.0.0,>=0.109.0; extra == "api"
55
+ Requires-Dist: uvicorn[standard]<1.0.0,>=0.27.0; extra == "api"
56
+ Requires-Dist: sqlalchemy<3.0.0,>=2.0.25; extra == "api"
57
+ Requires-Dist: psycopg2-binary<3.0.0,>=2.9.9; extra == "api"
58
+ Requires-Dist: pydantic-settings<3.0.0,>=2.1.0; extra == "api"
59
+ Requires-Dist: alembic<2.0.0,>=1.13.1; extra == "api"
60
+ Requires-Dist: supabase<3.0.0,>=2.3.0; extra == "api"
61
+ Requires-Dist: temporalio<2.0.0,>=1.5.0; extra == "api"
62
+ Requires-Dist: python-multipart<1.0.0,>=0.0.6; extra == "api"
63
+ Requires-Dist: python-jose[cryptography]<4.0.0,>=3.3.0; extra == "api"
64
+ Requires-Dist: PyJWT<3.0.0,>=2.8.0; extra == "api"
65
+ Requires-Dist: passlib[bcrypt]<2.0.0,>=1.7.4; extra == "api"
66
+ Requires-Dist: mangum<1.0.0,>=0.17.0; extra == "api"
67
+ Requires-Dist: structlog<25.0.0,>=24.1.0; extra == "api"
68
+ Requires-Dist: croniter<3.0.0,>=2.0.0; extra == "api"
69
+ Requires-Dist: kubiya-sdk<3.0.0,>=2.0.3; extra == "api"
70
+ Provides-Extra: dev
71
+ Requires-Dist: pytest<9.0.0,>=7.4.0; extra == "dev"
72
+ Requires-Dist: pytest-asyncio<1.0.0,>=0.21.0; extra == "dev"
73
+ Requires-Dist: pytest-cov<6.0.0,>=4.1.0; extra == "dev"
74
+ Requires-Dist: pytest-mock<4.0.0,>=3.11.0; extra == "dev"
75
+ Requires-Dist: pytest-xdist<4.0.0,>=3.5.0; extra == "dev"
76
+ Requires-Dist: fakeredis<3.0.0,>=2.20.0; extra == "dev"
77
+ Requires-Dist: respx<1.0.0,>=0.20.0; extra == "dev"
78
+ Requires-Dist: black<25.0.0,>=23.0.0; extra == "dev"
79
+ Requires-Dist: ruff<1.0.0,>=0.1.0; extra == "dev"
80
+ Requires-Dist: mypy<2.0.0,>=1.0.0; extra == "dev"
81
+ Provides-Extra: test
82
+ Requires-Dist: pytest<9.0.0,>=7.4.0; extra == "test"
83
+ Requires-Dist: pytest-asyncio<1.0.0,>=0.21.0; extra == "test"
84
+ Requires-Dist: pytest-cov<6.0.0,>=4.1.0; extra == "test"
85
+ Requires-Dist: pytest-mock<4.0.0,>=3.11.0; extra == "test"
86
+ Requires-Dist: pytest-xdist<4.0.0,>=3.5.0; extra == "test"
87
+ Requires-Dist: fakeredis<3.0.0,>=2.20.0; extra == "test"
88
+ Requires-Dist: respx<1.0.0,>=0.20.0; extra == "test"
89
+ Provides-Extra: all
90
+ Requires-Dist: kubiya-control-plane-api[api,dev,test,worker]; extra == "all"
91
+
92
+ # Kubiya Control Plane API
93
+
94
+ Multi-tenant AI agent orchestration and management platform powered by Temporal workflows.
95
+
96
+ ## Installation
97
+
98
+ ### Basic Installation
99
+
100
+ ```bash
101
+ pip install kubiya-control-plane-api
102
+ ```
103
+
104
+ ### Installation with Extras
105
+
106
+ Install with development dependencies:
107
+
108
+ ```bash
109
+ pip install "kubiya-control-plane-api[dev]"
110
+ ```
111
+
112
+ Install with test dependencies:
113
+
114
+ ```bash
115
+ pip install "kubiya-control-plane-api[test]"
116
+ ```
117
+
118
+ Install with all optional dependencies:
119
+
120
+ ```bash
121
+ pip install "kubiya-control-plane-api[all]"
122
+ ```
123
+
124
+ ## Running for Development
125
+
126
+ ### 1. Clone the repository
127
+
128
+ ```bash
129
+ git clone https://github.com/kubiyabot/agent-control-plane.git
130
+ cd agent-control-plane
131
+ ```
132
+
133
+ ### 2. Set up environment variables
134
+
135
+ Create a `.env` file with the required variables (see below).
136
+
137
+ ### 3. Build an image and run.
138
+
139
+ ```bash
140
+ make build
141
+
142
+ make up
143
+ ```
144
+
145
+ The API will be available at `http://localhost:7777/api/docs`
146
+
147
+ ## Running the Worker
148
+
149
+ The worker processes Temporal workflows for agent execution.
150
+
151
+ ### Using the CLI command
152
+
153
+ After installing the package, run:
154
+
155
+ ```bash
156
+ kubiya-control-plane-worker
157
+ ```
158
+
159
+ ### Using Python module
160
+
161
+ ```bash
162
+ python -m control_plane_api.worker
163
+ ```
164
+
165
+ ## Required Environment Variables
166
+
167
+ The following environment variables **must** be set to run the worker:
168
+
169
+ ### Temporal Configuration (Required)
170
+
171
+ ```bash
172
+ TEMPORAL_HOST=localhost:7233
173
+ TEMPORAL_NAMESPACE=default
174
+ ```
175
+
176
+ - `TEMPORAL_HOST`: Address of your Temporal server
177
+ - `TEMPORAL_NAMESPACE`: Temporal namespace to use
178
+
179
+ ### Database Configuration (Required)
180
+
181
+ **Option 1: Direct PostgreSQL**
182
+
183
+ ```bash
184
+ DATABASE_URL=postgresql://user:password@localhost:5432/control_plane
185
+ ```
186
+
187
+ **Option 2: Supabase**
188
+
189
+ ```bash
190
+ SUPABASE_URL=https://your-project.supabase.co
191
+ SUPABASE_SERVICE_KEY=your-service-role-key
192
+ SUPABASE_POSTGRES_URL=postgresql://user:password@host:5432/database
193
+ ```
194
+
195
+ You need **either** `DATABASE_URL` **or** all three Supabase variables.
196
+
197
+ ### Temporal Cloud Authentication (If using Temporal Cloud)
198
+
199
+ If connecting to Temporal Cloud instead of a self-hosted server, you also need **one** of:
200
+
201
+ **Option A: API Key**
202
+
203
+ ```bash
204
+ TEMPORAL_API_KEY=your-temporal-cloud-api-key
205
+ ```
206
+
207
+ **Option B: mTLS Certificates**
208
+
209
+ ```bash
210
+ TEMPORAL_CLIENT_CERT_PATH=/path/to/cert.pem
211
+ TEMPORAL_CLIENT_KEY_PATH=/path/to/key.pem
212
+ ```
213
+
214
+ ### Quick Start Example
215
+
216
+ ```bash
217
+ # 1. Install the package
218
+ pip install kubiya-control-plane-api
219
+
220
+ # 2. Set required environment variables
221
+ export TEMPORAL_HOST=localhost:7233
222
+ export TEMPORAL_NAMESPACE=default
223
+ export DATABASE_URL=postgresql://user:password@localhost:5432/control_plane
224
+
225
+ # 3. Run the worker
226
+ kubiya-control-plane-worker
227
+ ```
228
+
229
+ The worker will connect to Temporal and start processing agent execution workflows.