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,222 @@
1
+ """
2
+ Agent Execution Workflow with Skill Support.
3
+
4
+ This workflow demonstrates how to execute an agent with skills resolved
5
+ from the Control Plane API.
6
+ """
7
+
8
+ from datetime import timedelta
9
+ from dataclasses import dataclass
10
+ from temporalio import workflow
11
+ import structlog
12
+
13
+ # Import activities
14
+ from control_plane_api.app.activities.agent_activities import (
15
+ execute_agent_llm,
16
+ update_execution_status,
17
+ )
18
+ from control_plane_api.app.activities.skill_activities import (
19
+ resolve_agent_skills,
20
+ instantiate_agent_skills,
21
+ )
22
+
23
+ logger = structlog.get_logger()
24
+
25
+
26
+ @dataclass
27
+ class AgentExecutionInput:
28
+ """Input for agent execution workflow"""
29
+ execution_id: str
30
+ organization_id: str
31
+ agent_id: str
32
+ prompt: str
33
+ system_prompt: str | None = None
34
+ control_plane_url: str = "https://agent-control-plane.vercel.app"
35
+ api_key: str = None # Kubiya API key for authentication
36
+
37
+
38
+ @dataclass
39
+ class AgentExecutionResult:
40
+ """Result of agent execution workflow"""
41
+ execution_id: str
42
+ response: str
43
+ usage: dict
44
+ status: str
45
+ skills_used: list[str] | None = None
46
+
47
+
48
+ @workflow.defn
49
+ class AgentExecutionWithToolSetsWorkflow:
50
+ """
51
+ Orchestrates agent execution with skill resolution.
52
+
53
+ Flow:
54
+ 1. Fetch agent configuration from Control Plane
55
+ 2. Resolve skills from Control Plane (with inheritance)
56
+ 3. Instantiate agno tool instances
57
+ 4. Create agent with tools
58
+ 5. Execute agent
59
+ 6. Return results
60
+ """
61
+
62
+ @workflow.run
63
+ async def run(self, input: AgentExecutionInput) -> AgentExecutionResult:
64
+ """
65
+ Execute agent workflow with skills.
66
+
67
+ Args:
68
+ input: AgentExecutionInput with agent_id, prompt, etc.
69
+
70
+ Returns:
71
+ AgentExecutionResult with response and metadata
72
+ """
73
+ workflow.logger.info(
74
+ "agent_execution_workflow_started",
75
+ execution_id=input.execution_id,
76
+ agent_id=input.agent_id,
77
+ organization_id=input.organization_id
78
+ )
79
+
80
+ # 1. Update execution status to 'running'
81
+ await workflow.execute_activity(
82
+ update_execution_status,
83
+ args=[input.execution_id, "running"],
84
+ start_to_close_timeout=timedelta(seconds=10)
85
+ )
86
+
87
+ try:
88
+ # 2. Resolve skills from Control Plane
89
+ # The Control Plane handles inheritance: Environment → Team → Agent
90
+ skill_definitions = await workflow.execute_activity(
91
+ resolve_agent_skills,
92
+ args=[
93
+ input.agent_id,
94
+ input.control_plane_url,
95
+ input.api_key
96
+ ],
97
+ start_to_close_timeout=timedelta(seconds=30)
98
+ )
99
+
100
+ workflow.logger.info(
101
+ "skills_resolved",
102
+ execution_id=input.execution_id,
103
+ skill_count=len(skill_definitions),
104
+ skill_types=[t.get("type") for t in skill_definitions]
105
+ )
106
+
107
+ # 3. Instantiate agno tool instances
108
+ agent_tools = await workflow.execute_activity(
109
+ instantiate_agent_skills,
110
+ args=[skill_definitions],
111
+ start_to_close_timeout=timedelta(seconds=10)
112
+ )
113
+
114
+ workflow.logger.info(
115
+ "tools_instantiated",
116
+ execution_id=input.execution_id,
117
+ tool_count=len(agent_tools)
118
+ )
119
+
120
+ # 4. Execute agent with LLM
121
+ # Pass tools to the execution activity
122
+ llm_result = await workflow.execute_activity(
123
+ execute_agent_llm,
124
+ args=[
125
+ input.agent_id,
126
+ input.prompt,
127
+ input.system_prompt,
128
+ agent_tools, # Pass instantiated tools
129
+ input.control_plane_url,
130
+ input.api_key
131
+ ],
132
+ start_to_close_timeout=timedelta(minutes=10),
133
+ retry_policy=workflow.RetryPolicy(
134
+ maximum_attempts=3,
135
+ initial_interval=timedelta(seconds=1),
136
+ backoff_coefficient=2.0
137
+ )
138
+ )
139
+
140
+ # 5. Update execution status to 'completed'
141
+ await workflow.execute_activity(
142
+ update_execution_status,
143
+ args=[input.execution_id, "completed", llm_result],
144
+ start_to_close_timeout=timedelta(seconds=30)
145
+ )
146
+
147
+ workflow.logger.info(
148
+ "agent_execution_workflow_completed",
149
+ execution_id=input.execution_id,
150
+ agent_id=input.agent_id
151
+ )
152
+
153
+ return AgentExecutionResult(
154
+ execution_id=input.execution_id,
155
+ response=llm_result.get("response", ""),
156
+ usage=llm_result.get("usage", {}),
157
+ status="completed",
158
+ skills_used=[t.get("name") for t in skill_definitions]
159
+ )
160
+
161
+ except Exception as e:
162
+ workflow.logger.error(
163
+ "agent_execution_workflow_failed",
164
+ execution_id=input.execution_id,
165
+ error=str(e)
166
+ )
167
+
168
+ # Update execution status to 'failed'
169
+ await workflow.execute_activity(
170
+ update_execution_status,
171
+ args=[input.execution_id, "failed", {"error": str(e)}],
172
+ start_to_close_timeout=timedelta(seconds=30)
173
+ )
174
+
175
+ raise
176
+
177
+
178
+ # Example usage in Control Plane API:
179
+ """
180
+ from temporalio.client import Client
181
+ from control_plane_api.app.workflows.agent_execution_with_skills import (
182
+ AgentExecutionWithToolSetsWorkflow,
183
+ AgentExecutionInput
184
+ )
185
+
186
+ # In your agent execution endpoint:
187
+ @router.post("/api/v1/agents/{agent_id}/execute")
188
+ async def execute_agent(
189
+ agent_id: str,
190
+ request: AgentExecutionRequest,
191
+ organization: dict = Depends(get_current_organization),
192
+ ):
193
+ # Get Temporal client
194
+ temporal_client = await get_temporal_client()
195
+
196
+ # Create execution record
197
+ execution_id = str(uuid.uuid4())
198
+
199
+ # Submit workflow to Temporal
200
+ workflow_handle = await temporal_client.start_workflow(
201
+ AgentExecutionWithToolSetsWorkflow.run,
202
+ AgentExecutionInput(
203
+ execution_id=execution_id,
204
+ organization_id=organization["id"],
205
+ agent_id=agent_id,
206
+ prompt=request.prompt,
207
+ system_prompt=request.system_prompt,
208
+ control_plane_url=settings.CONTROL_PLANE_URL,
209
+ api_key=organization["api_key"] # Or get from auth
210
+ ),
211
+ id=f"agent-exec-{execution_id}",
212
+ task_queue=request.worker_queue_id, # Route to specific worker queue
213
+ execution_timeout=timedelta(hours=1)
214
+ )
215
+
216
+ return AgentExecutionResponse(
217
+ execution_id=execution_id,
218
+ workflow_id=workflow_handle.id,
219
+ status="pending",
220
+ message="Agent execution submitted successfully"
221
+ )
222
+ """
@@ -0,0 +1,326 @@
1
+ """
2
+ Temporal Cloud Namespace Provisioning Workflow
3
+
4
+ This workflow handles the provisioning of Temporal Cloud namespaces using tcld CLI.
5
+ Since Temporal doesn't provide SDK/API for namespace creation, we use the CLI tool.
6
+
7
+ Flow:
8
+ 1. Check if namespace already exists
9
+ 2. Create namespace if needed
10
+ 3. Poll until namespace is ready
11
+ 4. Generate API key
12
+ 5. Store credentials
13
+ 6. Update task queue status to 'ready'
14
+ """
15
+
16
+ from dataclasses import dataclass
17
+ from datetime import timedelta
18
+ from temporalio import workflow
19
+ from temporalio.common import RetryPolicy
20
+ import structlog
21
+
22
+ # Import activities
23
+ with workflow.unsafe.imports_passed_through():
24
+ from control_plane_api.app.activities.temporal_cloud_activities import (
25
+ check_namespace_exists,
26
+ create_namespace,
27
+ poll_namespace_status,
28
+ generate_namespace_api_key,
29
+ store_namespace_credentials,
30
+ update_task_queue_status,
31
+ CheckNamespaceInput,
32
+ CreateNamespaceInput,
33
+ PollNamespaceStatusInput,
34
+ GenerateApiKeyInput,
35
+ StoreNamespaceCredentialsInput,
36
+ )
37
+
38
+ logger = structlog.get_logger()
39
+
40
+
41
+ @dataclass
42
+ class ProvisionNamespaceInput:
43
+ """Input for namespace provisioning workflow"""
44
+ organization_id: str
45
+ organization_name: str
46
+ task_queue_id: str
47
+ account_id: str
48
+ region: str = "aws-us-east-1"
49
+ retention_days: int = 30
50
+
51
+
52
+ @dataclass
53
+ class ProvisionNamespaceOutput:
54
+ """Output from namespace provisioning workflow"""
55
+ success: bool
56
+ namespace_name: str
57
+ namespace_id: str | None = None
58
+ status: str = "pending"
59
+ error_message: str | None = None
60
+
61
+
62
+ @workflow.defn
63
+ class ProvisionTemporalNamespaceWorkflow:
64
+ """
65
+ Workflow to provision a Temporal Cloud namespace for an organization.
66
+
67
+ This workflow is triggered when the first task queue is created for an org.
68
+ It handles the entire provisioning process including retries and error handling.
69
+ """
70
+
71
+ @workflow.run
72
+ async def run(self, input: ProvisionNamespaceInput) -> ProvisionNamespaceOutput:
73
+ """
74
+ Main workflow execution.
75
+
76
+ Args:
77
+ input: Provisioning input with org details
78
+
79
+ Returns:
80
+ ProvisionNamespaceOutput with result
81
+ """
82
+ workflow.logger.info(
83
+ f"Starting namespace provisioning workflow",
84
+ extra={
85
+ "organization_id": input.organization_id,
86
+ "task_queue_id": input.task_queue_id,
87
+ }
88
+ )
89
+
90
+ # Generate namespace name: kubiya-{org_slug}-{short_id}
91
+ # Format: kubiya-acme-corp-a1b2c3
92
+ org_slug = input.organization_name.lower().replace(" ", "-")[:20]
93
+ org_short_id = input.organization_id[:6]
94
+ namespace_name = f"kubiya-{org_slug}-{org_short_id}"
95
+
96
+ try:
97
+ # Step 1: Check if namespace already exists
98
+ workflow.logger.info("Step 1: Checking if namespace exists")
99
+
100
+ check_result = await workflow.execute_activity(
101
+ check_namespace_exists,
102
+ CheckNamespaceInput(
103
+ organization_id=input.organization_id,
104
+ namespace_name=namespace_name,
105
+ ),
106
+ start_to_close_timeout=timedelta(seconds=30),
107
+ retry_policy=RetryPolicy(
108
+ maximum_attempts=3,
109
+ initial_interval=timedelta(seconds=1),
110
+ maximum_interval=timedelta(seconds=10),
111
+ ),
112
+ )
113
+
114
+ if check_result.get("exists"):
115
+ workflow.logger.info(
116
+ f"Namespace already exists",
117
+ extra={"namespace_name": namespace_name}
118
+ )
119
+
120
+ # If it exists and is ready, update task queue and we're done
121
+ if check_result.get("status") == "ready":
122
+ namespace_id = check_result.get("details", {}).get("id")
123
+
124
+ await workflow.execute_activity(
125
+ update_task_queue_status,
126
+ args=[input.task_queue_id, "ready", None, namespace_id],
127
+ start_to_close_timeout=timedelta(seconds=15),
128
+ )
129
+
130
+ return ProvisionNamespaceOutput(
131
+ success=True,
132
+ namespace_name=namespace_name,
133
+ namespace_id=namespace_id,
134
+ status="ready",
135
+ )
136
+
137
+ # Step 2: Create namespace
138
+ workflow.logger.info("Step 2: Creating namespace")
139
+
140
+ create_result = await workflow.execute_activity(
141
+ create_namespace,
142
+ CreateNamespaceInput(
143
+ organization_id=input.organization_id,
144
+ namespace_name=namespace_name,
145
+ account_id=input.account_id,
146
+ region=input.region,
147
+ retention_days=input.retention_days,
148
+ ),
149
+ start_to_close_timeout=timedelta(seconds=60),
150
+ retry_policy=RetryPolicy(
151
+ maximum_attempts=3,
152
+ initial_interval=timedelta(seconds=2),
153
+ maximum_interval=timedelta(seconds=10),
154
+ ),
155
+ )
156
+
157
+ if not create_result.get("success"):
158
+ error_msg = create_result.get("error", "Failed to create namespace")
159
+ workflow.logger.error(
160
+ f"Namespace creation failed",
161
+ extra={"error": error_msg}
162
+ )
163
+
164
+ # Update task queue with error
165
+ await workflow.execute_activity(
166
+ update_task_queue_status,
167
+ args=[input.task_queue_id, "error", error_msg, None],
168
+ start_to_close_timeout=timedelta(seconds=15),
169
+ )
170
+
171
+ return ProvisionNamespaceOutput(
172
+ success=False,
173
+ namespace_name=namespace_name,
174
+ status="error",
175
+ error_message=error_msg,
176
+ )
177
+
178
+ namespace_id = create_result.get("namespace_id")
179
+
180
+ # Step 3: Poll namespace status until ready
181
+ workflow.logger.info("Step 3: Polling namespace status")
182
+
183
+ poll_result = await workflow.execute_activity(
184
+ poll_namespace_status,
185
+ PollNamespaceStatusInput(
186
+ namespace_name=namespace_name,
187
+ max_attempts=60, # 5 minutes max
188
+ poll_interval_seconds=5,
189
+ ),
190
+ start_to_close_timeout=timedelta(minutes=6),
191
+ retry_policy=RetryPolicy(
192
+ maximum_attempts=2,
193
+ initial_interval=timedelta(seconds=5),
194
+ ),
195
+ )
196
+
197
+ if not poll_result.get("ready"):
198
+ error_msg = poll_result.get("error", "Namespace not ready")
199
+ workflow.logger.error(
200
+ f"Namespace provisioning timed out",
201
+ extra={"attempts": poll_result.get("attempts")}
202
+ )
203
+
204
+ # Update task queue with error
205
+ await workflow.execute_activity(
206
+ update_task_queue_status,
207
+ args=[input.task_queue_id, "error", error_msg, namespace_id],
208
+ start_to_close_timeout=timedelta(seconds=15),
209
+ )
210
+
211
+ return ProvisionNamespaceOutput(
212
+ success=False,
213
+ namespace_name=namespace_name,
214
+ namespace_id=namespace_id,
215
+ status="error",
216
+ error_message=error_msg,
217
+ )
218
+
219
+ # Step 4: Generate API key
220
+ workflow.logger.info("Step 4: Generating API key")
221
+
222
+ api_key_result = await workflow.execute_activity(
223
+ generate_namespace_api_key,
224
+ GenerateApiKeyInput(
225
+ namespace_name=namespace_name,
226
+ key_description=f"Control Plane API Key for {input.organization_name}",
227
+ ),
228
+ start_to_close_timeout=timedelta(seconds=30),
229
+ retry_policy=RetryPolicy(
230
+ maximum_attempts=3,
231
+ initial_interval=timedelta(seconds=2),
232
+ maximum_interval=timedelta(seconds=10),
233
+ ),
234
+ )
235
+
236
+ if not api_key_result.get("success"):
237
+ error_msg = api_key_result.get("error", "Failed to generate API key")
238
+ workflow.logger.error(
239
+ f"API key generation failed",
240
+ extra={"error": error_msg}
241
+ )
242
+
243
+ # Update task queue with error
244
+ await workflow.execute_activity(
245
+ update_task_queue_status,
246
+ args=[input.task_queue_id, "error", error_msg, namespace_id],
247
+ start_to_close_timeout=timedelta(seconds=15),
248
+ )
249
+
250
+ return ProvisionNamespaceOutput(
251
+ success=False,
252
+ namespace_name=namespace_name,
253
+ namespace_id=namespace_id,
254
+ status="error",
255
+ error_message=error_msg,
256
+ )
257
+
258
+ api_key = api_key_result.get("api_key")
259
+
260
+ # Step 5: Store credentials
261
+ workflow.logger.info("Step 5: Storing credentials")
262
+
263
+ store_result = await workflow.execute_activity(
264
+ store_namespace_credentials,
265
+ StoreNamespaceCredentialsInput(
266
+ organization_id=input.organization_id,
267
+ namespace_name=namespace_name,
268
+ api_key=api_key,
269
+ status="ready",
270
+ ),
271
+ start_to_close_timeout=timedelta(seconds=15),
272
+ retry_policy=RetryPolicy(
273
+ maximum_attempts=3,
274
+ initial_interval=timedelta(seconds=1),
275
+ ),
276
+ )
277
+
278
+ stored_namespace_id = store_result.get("namespace_id")
279
+
280
+ # Step 6: Update task queue status to ready
281
+ workflow.logger.info("Step 6: Updating task queue status")
282
+
283
+ await workflow.execute_activity(
284
+ update_task_queue_status,
285
+ args=[input.task_queue_id, "ready", None, stored_namespace_id],
286
+ start_to_close_timeout=timedelta(seconds=15),
287
+ )
288
+
289
+ workflow.logger.info(
290
+ f"Namespace provisioning complete",
291
+ extra={
292
+ "namespace_name": namespace_name,
293
+ "namespace_id": stored_namespace_id,
294
+ }
295
+ )
296
+
297
+ return ProvisionNamespaceOutput(
298
+ success=True,
299
+ namespace_name=namespace_name,
300
+ namespace_id=stored_namespace_id,
301
+ status="ready",
302
+ )
303
+
304
+ except Exception as e:
305
+ error_msg = f"Workflow failed: {str(e)}"
306
+ workflow.logger.error(
307
+ f"Namespace provisioning workflow failed",
308
+ extra={"error": str(e)}
309
+ )
310
+
311
+ # Update task queue with error
312
+ try:
313
+ await workflow.execute_activity(
314
+ update_task_queue_status,
315
+ args=[input.task_queue_id, "error", error_msg, None],
316
+ start_to_close_timeout=timedelta(seconds=15),
317
+ )
318
+ except Exception:
319
+ pass # Best effort
320
+
321
+ return ProvisionNamespaceOutput(
322
+ success=False,
323
+ namespace_name=namespace_name,
324
+ status="error",
325
+ error_message=error_msg,
326
+ )