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,410 @@
1
+ """Team-related Temporal activities"""
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+ from typing import Optional, List
6
+ from temporalio import activity
7
+ import structlog
8
+
9
+ from agno.agent import Agent
10
+ from agno.team import Team
11
+ from agno.models.litellm import LiteLLM
12
+
13
+ from control_plane_api.app.lib.supabase import get_supabase
14
+ from control_plane_api.app.activities.agent_activities import update_execution_status, ActivityUpdateExecutionInput
15
+
16
+ logger = structlog.get_logger()
17
+
18
+
19
+ @dataclass
20
+ class ActivityGetTeamAgentsInput:
21
+ """Input for get_team_agents activity"""
22
+ team_id: str
23
+ organization_id: str
24
+
25
+
26
+ @dataclass
27
+ class ActivityExecuteTeamInput:
28
+ """Input for execute_team_coordination activity"""
29
+ execution_id: str
30
+ team_id: str
31
+ organization_id: str
32
+ prompt: str
33
+ system_prompt: Optional[str] = None
34
+ agents: List[dict] = None
35
+ team_config: dict = None
36
+
37
+ def __post_init__(self):
38
+ if self.agents is None:
39
+ self.agents = []
40
+ if self.team_config is None:
41
+ self.team_config = {}
42
+
43
+
44
+ @activity.defn
45
+ async def get_team_agents(input: ActivityGetTeamAgentsInput) -> dict:
46
+ """
47
+ Get all agents in a team.
48
+
49
+ This activity fetches agents belonging to the team from the database.
50
+
51
+ Args:
52
+ input: Activity input with team details
53
+
54
+ Returns:
55
+ Dict with agents list
56
+ """
57
+ print(f"\n\n=== GET_TEAM_AGENTS START ===")
58
+ print(f"team_id: {input.team_id} (type: {type(input.team_id).__name__})")
59
+ print(f"organization_id: {input.organization_id} (type: {type(input.organization_id).__name__})")
60
+ print(f"================================\n")
61
+
62
+ activity.logger.info(
63
+ f"[DEBUG] Getting team agents START",
64
+ extra={
65
+ "team_id": input.team_id,
66
+ "team_id_type": type(input.team_id).__name__,
67
+ "organization_id": input.organization_id,
68
+ "organization_id_type": type(input.organization_id).__name__,
69
+ }
70
+ )
71
+
72
+ try:
73
+ client = get_supabase()
74
+ print(f"Supabase client created successfully")
75
+
76
+ activity.logger.info(
77
+ f"[DEBUG] Supabase client created, fetching team configuration",
78
+ extra={
79
+ "team_id": input.team_id,
80
+ "organization_id": input.organization_id,
81
+ }
82
+ )
83
+
84
+ # First, get the team configuration to get member_ids (source of truth)
85
+ print(f"Fetching team configuration for team_id={input.team_id}")
86
+ team_result = (
87
+ client.table("teams")
88
+ .select("configuration")
89
+ .eq("id", input.team_id)
90
+ .eq("organization_id", input.organization_id)
91
+ .single()
92
+ .execute()
93
+ )
94
+
95
+ if not team_result.data:
96
+ print(f"Team not found!")
97
+ activity.logger.error(
98
+ f"[DEBUG] Team not found",
99
+ extra={
100
+ "team_id": input.team_id,
101
+ "organization_id": input.organization_id,
102
+ }
103
+ )
104
+ return {"agents": [], "count": 0}
105
+
106
+ team_config = team_result.data.get("configuration", {})
107
+ member_ids = team_config.get("member_ids", [])
108
+ print(f"Team configuration member_ids: {member_ids}")
109
+
110
+ activity.logger.info(
111
+ f"[DEBUG] Team configuration loaded",
112
+ extra={
113
+ "team_id": input.team_id,
114
+ "member_ids": member_ids,
115
+ "member_count": len(member_ids),
116
+ }
117
+ )
118
+
119
+ # Get agents by member_ids (not by FK relationship)
120
+ agents = []
121
+ if member_ids:
122
+ print(f"Fetching agents with IDs: {member_ids}")
123
+ result = (
124
+ client.table("agents")
125
+ .select("*")
126
+ .in_("id", member_ids)
127
+ .eq("organization_id", input.organization_id)
128
+ .execute()
129
+ )
130
+ agents = result.data or []
131
+ print(f"Query executed. Result data length: {len(agents)}")
132
+
133
+ activity.logger.info(
134
+ f"[DEBUG] Query executed, processing results",
135
+ extra={
136
+ "agents_found": len(agents),
137
+ "agent_ids": [a.get("id") for a in agents],
138
+ }
139
+ )
140
+
141
+ print(f"Agents found: {len(agents)}")
142
+ if agents:
143
+ for agent in agents:
144
+ print(f" - {agent.get('name')} (ID: {agent.get('id')})")
145
+
146
+ activity.logger.info(
147
+ f"[DEBUG] Retrieved team agents",
148
+ extra={
149
+ "team_id": input.team_id,
150
+ "agent_count": len(agents),
151
+ "agent_names": [a.get("name") for a in agents],
152
+ "agent_ids": [a.get("id") for a in agents],
153
+ }
154
+ )
155
+
156
+ if not agents:
157
+ print(f"\n!!! NO AGENTS FOUND - Running verification query !!!")
158
+ activity.logger.warning(
159
+ f"[DEBUG] WARNING: No agents found for team - running verification query",
160
+ extra={
161
+ "team_id": input.team_id,
162
+ "organization_id": input.organization_id,
163
+ }
164
+ )
165
+
166
+ # Try query without org filter to debug
167
+ result_no_org = (
168
+ client.table("agents")
169
+ .select("id, name, team_id, organization_id")
170
+ .eq("team_id", input.team_id)
171
+ .execute()
172
+ )
173
+ print(f"Query without org filter returned {len(result_no_org.data or [])} agents")
174
+ if result_no_org.data:
175
+ for agent in result_no_org.data:
176
+ print(f" - {agent.get('name')} (team_id: {agent.get('team_id')}, org_id: {agent.get('organization_id')})")
177
+
178
+ activity.logger.warning(
179
+ f"[DEBUG] Query without org filter returned {len(result_no_org.data or [])} agents",
180
+ extra={
181
+ "agents_found": result_no_org.data if result_no_org.data else [],
182
+ }
183
+ )
184
+
185
+ print(f"\n=== GET_TEAM_AGENTS END: Returning {len(agents)} agents ===\n\n")
186
+ return {
187
+ "agents": agents,
188
+ "count": len(agents),
189
+ }
190
+
191
+ except Exception as e:
192
+ print(f"\n!!! EXCEPTION in get_team_agents: {type(e).__name__}: {str(e)} !!!\n")
193
+ activity.logger.error(
194
+ f"[DEBUG] EXCEPTION in get_team_agents",
195
+ extra={
196
+ "team_id": input.team_id,
197
+ "organization_id": input.organization_id,
198
+ "error": str(e),
199
+ "error_type": type(e).__name__,
200
+ }
201
+ )
202
+ raise
203
+
204
+
205
+ @activity.defn
206
+ async def execute_team_coordination(input: ActivityExecuteTeamInput) -> dict:
207
+ """
208
+ Execute team coordination using Agno Teams.
209
+
210
+ This activity creates an Agno Team with member Agents and executes
211
+ the team run, allowing Agno to handle coordination.
212
+
213
+ Args:
214
+ input: Activity input with team execution details
215
+
216
+ Returns:
217
+ Dict with aggregated response, usage, success flag
218
+ """
219
+ activity.logger.info(
220
+ f"Executing team coordination with Agno Teams",
221
+ extra={
222
+ "execution_id": input.execution_id,
223
+ "team_id": input.team_id,
224
+ "agent_count": len(input.agents),
225
+ }
226
+ )
227
+
228
+ try:
229
+ # Create Agno Agent objects for each team member
230
+ member_agents = []
231
+ for agent_data in input.agents:
232
+ # Get model ID (default to kubiya/claude-sonnet-4 if not specified)
233
+ model_id = agent_data.get("model_id") or "kubiya/claude-sonnet-4"
234
+
235
+ # Get LiteLLM configuration from environment
236
+ litellm_api_base = os.getenv("LITELLM_API_BASE", "https://llm-proxy.kubiya.ai")
237
+ litellm_api_key = os.getenv("LITELLM_API_KEY")
238
+
239
+ # Create Agno Agent with explicit LiteLLM proxy configuration
240
+ # IMPORTANT: Use openai/ prefix for custom proxy compatibility (same as agno_service)
241
+ member_agent = Agent(
242
+ name=agent_data["name"],
243
+ role=agent_data.get("description", agent_data["name"]),
244
+ model=LiteLLM(
245
+ id=f"openai/{model_id}", # e.g., "openai/kubiya/claude-sonnet-4"
246
+ api_base=litellm_api_base,
247
+ api_key=litellm_api_key,
248
+ ),
249
+ )
250
+ member_agents.append(member_agent)
251
+
252
+ activity.logger.info(
253
+ f"Created Agno Agent",
254
+ extra={
255
+ "agent_name": agent_data["name"],
256
+ "model": model_id,
257
+ }
258
+ )
259
+
260
+ # Create Agno Team with member agents and LiteLLM model for coordination
261
+ litellm_api_base = os.getenv("LITELLM_API_BASE", "https://llm-proxy.kubiya.ai")
262
+ litellm_api_key = os.getenv("LITELLM_API_KEY")
263
+
264
+ # Get coordinator model from team configuration (if specified by user in UI)
265
+ # Falls back to default if not configured
266
+ team_model = (
267
+ input.team_config.get("llm", {}).get("model")
268
+ or "kubiya/claude-sonnet-4" # Default coordinator model
269
+ )
270
+
271
+ # Create Team with openai/ prefix for custom proxy compatibility (same as agno_service)
272
+ team = Team(
273
+ members=member_agents,
274
+ name=f"Team {input.team_id}",
275
+ model=LiteLLM(
276
+ id=f"openai/{team_model}", # e.g., "openai/kubiya/claude-sonnet-4"
277
+ api_base=litellm_api_base,
278
+ api_key=litellm_api_key,
279
+ ),
280
+ )
281
+
282
+ activity.logger.info(
283
+ f"Created Agno Team with {len(member_agents)} members",
284
+ extra={
285
+ "coordinator_model": team_model,
286
+ "member_count": len(member_agents),
287
+ }
288
+ )
289
+
290
+ # Execute team run in a thread pool since team.run() is synchronous
291
+ # This prevents blocking the async event loop in Temporal
292
+ import asyncio
293
+ result = await asyncio.to_thread(team.run, input.prompt)
294
+
295
+ activity.logger.info(
296
+ f"Agno Team execution completed",
297
+ extra={
298
+ "execution_id": input.execution_id,
299
+ "has_content": bool(result.content),
300
+ }
301
+ )
302
+
303
+ # Extract response content
304
+ response_content = result.content if hasattr(result, "content") else str(result)
305
+
306
+ # Extract usage metrics if available
307
+ usage = {}
308
+ if hasattr(result, "metrics") and result.metrics:
309
+ metrics = result.metrics
310
+ usage = {
311
+ "input_tokens": getattr(metrics, "input_tokens", 0),
312
+ "output_tokens": getattr(metrics, "output_tokens", 0),
313
+ "total_tokens": getattr(metrics, "total_tokens", 0),
314
+ }
315
+
316
+ return {
317
+ "success": True,
318
+ "response": response_content,
319
+ "usage": usage,
320
+ "coordination_type": "agno_team",
321
+ }
322
+
323
+ except Exception as e:
324
+ activity.logger.error(
325
+ f"Team coordination failed",
326
+ extra={
327
+ "execution_id": input.execution_id,
328
+ "error": str(e),
329
+ }
330
+ )
331
+ return {
332
+ "success": False,
333
+ "error": str(e),
334
+ "coordination_type": "agno_team",
335
+ "usage": {},
336
+ }
337
+
338
+
339
+ async def _execute_sequential(input: ActivityExecuteTeamInput) -> dict:
340
+ """Execute agents sequentially"""
341
+ activity.logger.info("Executing team sequentially")
342
+
343
+ responses = []
344
+ total_usage = {}
345
+
346
+ for agent in input.agents:
347
+ try:
348
+ # Import here to avoid circular dependency
349
+ from control_plane_api.app.services.litellm_service import litellm_service
350
+
351
+ model = agent.get("model_id") or "kubiya/claude-sonnet-4"
352
+ model_config = agent.get("model_config", {})
353
+
354
+ # Execute agent
355
+ result = litellm_service.execute_agent(
356
+ prompt=f"{input.prompt}\n\nAgent role: {agent.get('description', agent['name'])}",
357
+ model=model,
358
+ system_prompt=input.system_prompt,
359
+ **model_config
360
+ )
361
+
362
+ if result.get("success"):
363
+ responses.append({
364
+ "agent": agent["name"],
365
+ "response": result.get("response"),
366
+ })
367
+
368
+ # Aggregate usage
369
+ usage = result.get("usage", {})
370
+ for key, value in usage.items():
371
+ total_usage[key] = total_usage.get(key, 0) + value
372
+
373
+ except Exception as e:
374
+ activity.logger.error(
375
+ f"Agent execution failed",
376
+ extra={"agent_id": agent["id"], "error": str(e)}
377
+ )
378
+ responses.append({
379
+ "agent": agent["name"],
380
+ "error": str(e),
381
+ })
382
+
383
+ # Combine responses
384
+ combined_response = "\n\n".join([
385
+ f"**{r['agent']}**: {r.get('response', r.get('error'))}"
386
+ for r in responses
387
+ ])
388
+
389
+ return {
390
+ "success": True,
391
+ "response": combined_response,
392
+ "usage": total_usage,
393
+ "coordination_type": "sequential",
394
+ }
395
+
396
+
397
+ async def _execute_parallel(input: ActivityExecuteTeamInput) -> dict:
398
+ """Execute agents in parallel (placeholder - would use asyncio.gather in real impl)"""
399
+ activity.logger.info("Executing team in parallel")
400
+ # For now, fall back to sequential
401
+ # In a real implementation, this would use asyncio.gather or child workflows
402
+ return await _execute_sequential(input)
403
+
404
+
405
+ async def _execute_hierarchical(input: ActivityExecuteTeamInput) -> dict:
406
+ """Execute agents hierarchically with a coordinator (placeholder)"""
407
+ activity.logger.info("Executing team hierarchically")
408
+ # For now, fall back to sequential
409
+ # In a real implementation, this would have a coordinator agent that delegates
410
+ return await _execute_sequential(input)