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,465 @@
1
+ """
2
+ Team executor service with runtime abstraction support.
3
+
4
+ This version supports both Agno-based teams and Claude Code SDK runtime teams.
5
+ """
6
+
7
+ from typing import Dict, Any, Optional, List
8
+ from datetime import datetime, timezone
9
+ import structlog
10
+ import asyncio
11
+ import os
12
+
13
+ from control_plane_api.worker.control_plane_client import ControlPlaneClient
14
+ from control_plane_api.worker.services.session_service import SessionService
15
+ from control_plane_api.worker.services.cancellation_manager import CancellationManager
16
+ from runtimes import (
17
+ RuntimeFactory,
18
+ RuntimeType,
19
+ RuntimeExecutionContext,
20
+ )
21
+ from control_plane_api.worker.utils.streaming_utils import StreamingHelper
22
+
23
+ logger = structlog.get_logger()
24
+
25
+
26
+ class TeamExecutorServiceV2:
27
+ """
28
+ Service for executing teams using runtime abstraction.
29
+
30
+ This service orchestrates team execution by:
31
+ 1. Loading session history
32
+ 2. Determining runtime type (Agno or Claude Code)
33
+ 3. Delegating execution to appropriate runtime
34
+ 4. Persisting session after execution
35
+
36
+ For Claude Code runtime:
37
+ - Team leader uses Claude Code SDK with Task tool
38
+ - Team members are executed as subagents via Task tool
39
+ - Streaming and tool hooks supported
40
+
41
+ For Agno runtime:
42
+ - Uses existing Agno Team implementation
43
+ - Full multi-agent coordination support
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ control_plane: ControlPlaneClient,
49
+ session_service: SessionService,
50
+ cancellation_manager: CancellationManager,
51
+ ):
52
+ """
53
+ Initialize the team executor service.
54
+
55
+ Args:
56
+ control_plane: Control Plane API client
57
+ session_service: Session management service
58
+ cancellation_manager: Execution cancellation manager
59
+ """
60
+ self.control_plane = control_plane
61
+ self.session_service = session_service
62
+ self.cancellation_manager = cancellation_manager
63
+ self.runtime_factory = RuntimeFactory()
64
+
65
+ async def execute(self, input: Any) -> Dict[str, Any]:
66
+ """
67
+ Execute a team using the configured runtime.
68
+
69
+ Args:
70
+ input: TeamExecutionInput with execution details
71
+
72
+ Returns:
73
+ Dict with response, usage, success flag, runtime_type, etc.
74
+ """
75
+ execution_id = input.execution_id
76
+
77
+ print("\n" + "=" * 80)
78
+ print("🚀 TEAM EXECUTION START (Runtime-Abstracted)")
79
+ print("=" * 80)
80
+ print(f"Execution ID: {execution_id}")
81
+ print(f"Team ID: {input.team_id}")
82
+ print(f"Organization: {input.organization_id}")
83
+ print(f"Agent Count: {len(input.agents)}")
84
+ print(f"Session ID: {input.session_id}")
85
+ print(f"Prompt: {input.prompt[:100]}..." if len(input.prompt) > 100 else f"Prompt: {input.prompt}")
86
+ print("=" * 80 + "\n")
87
+
88
+ logger.info(
89
+ "team_execution_start",
90
+ execution_id=execution_id[:8],
91
+ team_id=input.team_id,
92
+ session_id=input.session_id,
93
+ agent_count=len(input.agents)
94
+ )
95
+
96
+ try:
97
+ # STEP 1: Load session history
98
+ print("📚 Loading session history...")
99
+ session_history = self.session_service.load_session(
100
+ execution_id=execution_id,
101
+ session_id=input.session_id
102
+ )
103
+
104
+ if session_history:
105
+ print(f"✅ Loaded {len(session_history)} messages from previous session\n")
106
+ else:
107
+ print("ℹ️ Starting new conversation\n")
108
+
109
+ # STEP 2: Determine runtime type from team configuration
110
+ team_config = getattr(input, "team_config", {}) or {}
111
+ runtime_type_str = team_config.get("runtime", "default")
112
+ runtime_type = self.runtime_factory.parse_runtime_type(runtime_type_str)
113
+
114
+ print(f"🔌 Runtime Type: {runtime_type.value}")
115
+ print(f" Framework: {self._get_framework_name(runtime_type)}\n")
116
+
117
+ logger.info(
118
+ "runtime_selected",
119
+ execution_id=execution_id[:8],
120
+ runtime=runtime_type.value,
121
+ )
122
+
123
+ # STEP 3: Execute based on runtime type
124
+ if runtime_type == RuntimeType.CLAUDE_CODE:
125
+ result = await self._execute_with_claude_code(input, session_history)
126
+ else:
127
+ # Fall back to Agno-based team execution
128
+ from control_plane_api.worker.services.team_executor import TeamExecutorService
129
+
130
+ agno_executor = TeamExecutorService(
131
+ self.control_plane,
132
+ self.session_service,
133
+ self.cancellation_manager
134
+ )
135
+ return await agno_executor.execute(input)
136
+
137
+ print("\n✅ Team execution completed!")
138
+ print(f" Response Length: {len(result['response'])} chars")
139
+ print(f" Success: {result['success']}\n")
140
+
141
+ logger.info(
142
+ "team_execution_completed",
143
+ execution_id=execution_id[:8],
144
+ success=result["success"],
145
+ response_length=len(result["response"]),
146
+ )
147
+
148
+ # STEP 4: Persist session
149
+ if result["success"] and result["response"]:
150
+ print("💾 Persisting session history...")
151
+
152
+ # Build new messages
153
+ new_messages = [
154
+ {"role": "user", "content": input.prompt},
155
+ {"role": "assistant", "content": result["response"]},
156
+ ]
157
+
158
+ # Combine with previous history
159
+ complete_session = session_history + new_messages
160
+
161
+ success = self.session_service.persist_session(
162
+ execution_id=execution_id,
163
+ session_id=input.session_id or execution_id,
164
+ user_id=input.user_id,
165
+ messages=complete_session,
166
+ metadata={
167
+ "team_id": input.team_id,
168
+ "organization_id": input.organization_id,
169
+ "runtime_type": runtime_type.value,
170
+ "turn_count": len(complete_session),
171
+ "member_count": len(input.agents),
172
+ },
173
+ )
174
+
175
+ if success:
176
+ print(f"✅ Session persisted ({len(complete_session)} total messages)\n")
177
+ else:
178
+ print(f"⚠️ Session persistence failed\n")
179
+
180
+ # STEP 5: Print usage metrics
181
+ if result.get("usage"):
182
+ print(f"📊 Token Usage:")
183
+ print(f" Input: {result['usage'].get('prompt_tokens', 0)}")
184
+ print(f" Output: {result['usage'].get('completion_tokens', 0)}")
185
+ print(f" Total: {result['usage'].get('total_tokens', 0)}\n")
186
+
187
+ print("=" * 80)
188
+ print("🏁 TEAM EXECUTION END")
189
+ print("=" * 80 + "\n")
190
+
191
+ return result
192
+
193
+ except Exception as e:
194
+ print("\n" + "=" * 80)
195
+ print("❌ TEAM EXECUTION FAILED")
196
+ print("=" * 80)
197
+ print(f"Error: {str(e)}")
198
+ print("=" * 80 + "\n")
199
+
200
+ logger.error(
201
+ "team_execution_failed",
202
+ execution_id=execution_id[:8],
203
+ error=str(e)
204
+ )
205
+
206
+ return {
207
+ "success": False,
208
+ "error": str(e),
209
+ "model": input.model_id,
210
+ "usage": {},
211
+ "finish_reason": "error",
212
+ "runtime_type": runtime_type_str if "runtime_type_str" in locals() else "unknown",
213
+ }
214
+
215
+ async def _execute_with_claude_code(
216
+ self, input: Any, session_history: List[Dict]
217
+ ) -> Dict[str, Any]:
218
+ """
219
+ Execute team using Claude Code SDK.
220
+
221
+ Strategy:
222
+ - Team leader is a Claude Code agent with Task tool enabled
223
+ - Team members are defined as context for the leader
224
+ - Leader uses Task tool to delegate work to members
225
+ - Streaming and hooks provide real-time feedback
226
+
227
+ Args:
228
+ input: TeamExecutionInput
229
+ session_history: Previous conversation messages
230
+
231
+ Returns:
232
+ Result dict
233
+ """
234
+ execution_id = input.execution_id
235
+
236
+ print(f"⚙️ Creating Claude Code team leader...")
237
+
238
+ # Create runtime instance
239
+ runtime = self.runtime_factory.create_runtime(
240
+ runtime_type=RuntimeType.CLAUDE_CODE,
241
+ control_plane_client=self.control_plane,
242
+ cancellation_manager=self.cancellation_manager,
243
+ )
244
+
245
+ print(f"✅ Runtime created: {runtime.get_runtime_info()}\n")
246
+
247
+ # STEP 1: Build team context for system prompt
248
+ team_context = self._build_team_context(input.agents)
249
+
250
+ system_prompt = f"""You are the team leader coordinating a team of specialized AI agents.
251
+
252
+ Your team members:
253
+ {team_context}
254
+
255
+ When you need a team member to perform a task:
256
+ 1. Use the Task tool to delegate work to the appropriate agent
257
+ 2. Provide clear instructions in the subagent_type parameter
258
+ 3. Wait for their response before continuing
259
+ 4. Synthesize the results into a cohesive answer
260
+
261
+ Your goal is to coordinate the team effectively to solve the user's request.
262
+ """
263
+
264
+ print(f"📋 Team Context:")
265
+ print(f" Team Members: {len(input.agents)}")
266
+ for agent in input.agents:
267
+ print(f" - {agent.get('name')}: {agent.get('role', 'No role specified')[:60]}...")
268
+ print()
269
+
270
+ # STEP 2: Get skills for team leader (must include Task tool)
271
+ print(f"🔧 Fetching skills from Control Plane...")
272
+ skills = []
273
+ try:
274
+ # Get skills from first agent (team leader)
275
+ if input.agents:
276
+ leader_id = input.agents[0].get("id")
277
+ if leader_id:
278
+ skill_configs = self.control_plane.get_skills(leader_id)
279
+ if skill_configs:
280
+ print(f"✅ Resolved {len(skill_configs)} skills")
281
+
282
+ from services.skill_factory import SkillFactory
283
+ skills = SkillFactory.create_skills_from_list(skill_configs)
284
+
285
+ if skills:
286
+ print(f"✅ Instantiated {len(skills)} skill(s)")
287
+ except Exception as e:
288
+ print(f"⚠️ Error fetching skills: {str(e)}")
289
+ logger.error("skill_fetch_error", error=str(e))
290
+
291
+ # Always ensure Task tool is available for delegation
292
+ task_skill = {"type": "task", "name": "Task"}
293
+ if task_skill not in skills:
294
+ skills.append(task_skill)
295
+ print(f"✅ Added Task tool for team coordination\n")
296
+
297
+ # STEP 3: Build execution context
298
+ print("📦 Building execution context...")
299
+ team_config = getattr(input, "team_config", {}) or {}
300
+ context = RuntimeExecutionContext(
301
+ execution_id=execution_id,
302
+ agent_id=input.team_id, # Use team_id as agent_id
303
+ organization_id=input.organization_id,
304
+ prompt=input.prompt,
305
+ system_prompt=system_prompt,
306
+ conversation_history=session_history,
307
+ model_id=input.model_id,
308
+ model_config=getattr(input, "model_config", None),
309
+ agent_config=team_config,
310
+ skills=skills,
311
+ mcp_servers=getattr(input, "mcp_servers", None),
312
+ user_metadata=getattr(input, "user_metadata", None),
313
+ runtime_config=team_config.get("runtime_config"),
314
+ )
315
+ print("✅ Context ready\n")
316
+
317
+ # STEP 4: Execute via runtime with streaming
318
+ print("⚡ Executing team via Claude Code runtime...\n")
319
+
320
+ if runtime.supports_streaming():
321
+ result = await self._execute_streaming(runtime, context, input)
322
+ else:
323
+ exec_result = await runtime.execute(context)
324
+ result = {
325
+ "success": exec_result.success,
326
+ "response": exec_result.response,
327
+ "usage": exec_result.usage,
328
+ "model": exec_result.model or input.model_id,
329
+ "finish_reason": exec_result.finish_reason or "stop",
330
+ "tool_messages": exec_result.tool_messages or [],
331
+ "runtime_type": "claude_code",
332
+ "error": exec_result.error,
333
+ "team_member_count": len(input.agents),
334
+ }
335
+
336
+ return result
337
+
338
+ async def _execute_streaming(
339
+ self, runtime, context: RuntimeExecutionContext, input: Any
340
+ ) -> Dict[str, Any]:
341
+ """
342
+ Execute with streaming and publish events to Control Plane.
343
+
344
+ Args:
345
+ runtime: Runtime instance
346
+ context: Execution context
347
+ input: Original input for additional metadata
348
+
349
+ Returns:
350
+ Result dict
351
+ """
352
+ # Create streaming helper for publishing events
353
+ streaming_helper = StreamingHelper(
354
+ control_plane_client=self.control_plane,
355
+ execution_id=context.execution_id
356
+ )
357
+
358
+ accumulated_response = ""
359
+ final_result = None
360
+
361
+ # Define event callback for publishing to Control Plane
362
+ def event_callback(event: Dict):
363
+ """Callback to publish events to Control Plane SSE"""
364
+ event_type = event.get("type")
365
+
366
+ if event_type == "content_chunk":
367
+ # Publish content chunk
368
+ streaming_helper.publish_content_chunk(
369
+ content=event.get("content", ""),
370
+ message_id=event.get("message_id", context.execution_id),
371
+ )
372
+ elif event_type == "tool_start":
373
+ # Publish tool start
374
+ streaming_helper.publish_tool_start(
375
+ tool_name=event.get("tool_name"),
376
+ tool_execution_id=event.get("tool_execution_id"),
377
+ tool_args=event.get("tool_args", {}),
378
+ source="team_leader",
379
+ )
380
+ elif event_type == "tool_complete":
381
+ # Publish tool completion
382
+ streaming_helper.publish_tool_complete(
383
+ tool_name=event.get("tool_name"),
384
+ tool_execution_id=event.get("tool_execution_id"),
385
+ status=event.get("status", "success"),
386
+ output=event.get("output"),
387
+ error=event.get("error"),
388
+ source="team_leader",
389
+ )
390
+
391
+ # Stream execution
392
+ async for chunk in runtime.stream_execute(context, event_callback):
393
+ if chunk.response:
394
+ accumulated_response += chunk.response
395
+ print(chunk.response, end="", flush=True)
396
+
397
+ # Keep final result for metadata
398
+ if chunk.usage or chunk.finish_reason:
399
+ final_result = chunk
400
+
401
+ print() # New line after streaming
402
+
403
+ # Return final result with accumulated response
404
+ if final_result:
405
+ return {
406
+ "success": final_result.success,
407
+ "response": accumulated_response,
408
+ "usage": final_result.usage,
409
+ "model": final_result.model or input.model_id,
410
+ "finish_reason": final_result.finish_reason or "stop",
411
+ "tool_messages": final_result.tool_messages or [],
412
+ "runtime_type": "claude_code",
413
+ "error": final_result.error,
414
+ "team_member_count": len(input.agents),
415
+ }
416
+ else:
417
+ return {
418
+ "success": True,
419
+ "response": accumulated_response,
420
+ "usage": {},
421
+ "model": input.model_id,
422
+ "finish_reason": "stop",
423
+ "tool_messages": [],
424
+ "runtime_type": "claude_code",
425
+ "team_member_count": len(input.agents),
426
+ }
427
+
428
+ def _build_team_context(self, agents: List[Dict]) -> str:
429
+ """
430
+ Build team context description for system prompt.
431
+
432
+ Args:
433
+ agents: List of agent configurations
434
+
435
+ Returns:
436
+ Formatted team context string
437
+ """
438
+ context_lines = []
439
+ for i, agent in enumerate(agents, 1):
440
+ name = agent.get("name", f"Agent {i}")
441
+ role = agent.get("role", "No role specified")
442
+ agent_id = agent.get("id", "unknown")
443
+
444
+ context_lines.append(
445
+ f"{i}. **{name}** (ID: {agent_id})\n"
446
+ f" Role: {role}\n"
447
+ )
448
+
449
+ return "\n".join(context_lines)
450
+
451
+ def _get_framework_name(self, runtime_type: RuntimeType) -> str:
452
+ """
453
+ Get friendly framework name for runtime type.
454
+
455
+ Args:
456
+ runtime_type: Runtime type enum
457
+
458
+ Returns:
459
+ Framework name string
460
+ """
461
+ mapping = {
462
+ RuntimeType.DEFAULT: "Agno",
463
+ RuntimeType.CLAUDE_CODE: "Claude Code SDK",
464
+ }
465
+ return mapping.get(runtime_type, "Unknown")