Skip to content

Commit 0cc4416

Browse files
Merge pull request #549 from phenobarbital/codex/add-tenant-attribute-to-crew
Add tenant isolation for Crew storage (Redis, Manager, Handlers)
2 parents d835100 + 0d6ac1b commit 0cc4416

File tree

5 files changed

+291
-92
lines changed

5 files changed

+291
-92
lines changed

parrot/handlers/crew/execution_handler.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,12 +557,22 @@ async def execute_crew(self, data: Dict[str, Any]):
557557
query = data.get('query')
558558
if not query:
559559
return self.error(response={"message": "query is required"}, status=400)
560+
tenant = data.get('tenant')
561+
if not tenant:
562+
self.logger.warning(
563+
"Missing 'tenant' in crew execution request; rejecting to avoid defaulting to 'global'."
564+
)
565+
return self.error(response={"message": "tenant is required"}, status=400)
560566

561567
if not self.bot_manager:
562568
return self.error(response={"message": "BotManager not available"}, status=500)
563569

564570
# Load Crew
565-
crew, crew_def = await self.bot_manager.get_crew(crew_id, as_new=True)
571+
crew, crew_def = await self.bot_manager.get_crew(
572+
crew_id,
573+
as_new=True,
574+
tenant=tenant
575+
)
566576
if not crew:
567577
return self.error(response={"message": f"Crew '{crew_id}' not found"}, status=404)
568578

@@ -590,6 +600,7 @@ async def execute_crew(self, data: Dict[str, Any]):
590600

591601
# Store crew name in metadata for future persistence
592602
job.metadata['crew_name'] = crew_def.name
603+
job.metadata['tenant'] = crew_def.tenant
593604

594605
# Cache the running crew
595606
self._active_crews[job_id] = crew

parrot/handlers/crew/handler.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ async def upload(self):
230230
{
231231
"message": "Crew uploaded and created successfully",
232232
"crew_id": crew_def.crew_id,
233+
"tenant": crew_def.tenant,
233234
"name": crew_def.name,
234235
"execution_mode": crew_def.execution_mode.value, # pylint: disable=E1101 #noqa
235236
"agents": [agent.agent_id for agent in crew_def.agents],
@@ -285,6 +286,7 @@ async def put(self):
285286
# Parse request body
286287
data = await self.request.json()
287288
crew_def = CrewDefinition(**data)
289+
tenant = crew_def.tenant
288290

289291
# Validate bot manager availability
290292
if not self.bot_manager:
@@ -296,7 +298,7 @@ async def put(self):
296298
)
297299
# if crew_id is provided, then is an update
298300
if url_crew_id:
299-
existing_crew = await self.bot_manager.get_crew(url_crew_id)
301+
existing_crew = await self.bot_manager.get_crew(url_crew_id, tenant=tenant)
300302
if not existing_crew:
301303
return self.error(
302304
response={
@@ -311,7 +313,7 @@ async def put(self):
311313
crew_def.updated_at = None # Will be set on save
312314

313315
# Remove old crew
314-
await self.bot_manager.remove_crew(url_crew_id)
316+
await self.bot_manager.remove_crew(url_crew_id, tenant=tenant)
315317

316318
self.logger.info(f"Updating crew '{url_crew_id}'")
317319

@@ -335,6 +337,7 @@ async def put(self):
335337
{
336338
"message": f"Crew {action} successfully",
337339
"crew_id": crew_def.crew_id,
340+
"tenant": crew_def.tenant,
338341
"name": crew_def.name,
339342
"execution_mode": crew_def.execution_mode.value, # pylint: disable=E1101
340343
"agents": [agent.agent_id for agent in crew_def.agents],
@@ -380,6 +383,7 @@ async def get(self):
380383
match_params = self.match_parameters(self.request)
381384
crew_id = match_params.get('id') or qs.get('crew_id')
382385
crew_name = qs.get('name')
386+
tenant = qs.get('tenant') or "global"
383387

384388
if not self.bot_manager:
385389
return self.error(
@@ -390,7 +394,7 @@ async def get(self):
390394
# Get specific crew
391395
if crew_name or crew_id:
392396
identifier = crew_name or crew_id
393-
crew_data = await self.bot_manager.get_crew(identifier)
397+
crew_data = await self.bot_manager.get_crew(identifier, tenant=tenant)
394398

395399
if not crew_data:
396400
return self.error(
@@ -403,6 +407,7 @@ async def get(self):
403407
crew, crew_def = crew_data
404408
return self.json_response({
405409
"crew_id": crew_def.crew_id,
410+
"tenant": crew_def.tenant,
406411
"name": crew_def.name,
407412
"description": crew_def.description,
408413
"execution_mode": crew_def.execution_mode.value,
@@ -421,12 +426,13 @@ async def get(self):
421426
await self.bot_manager.sync_crews()
422427

423428
# List all crews
424-
crews = self.bot_manager.list_crews()
429+
crews = self.bot_manager.list_crews(tenant=tenant)
425430
crew_list = []
426431

427432
crew_list.extend(
428433
{
429434
"crew_id": crew_def.crew_id,
435+
"tenant": crew_def.tenant,
430436
"name": crew_def.name,
431437
"description": crew_def.description,
432438
"execution_mode": crew_def.execution_mode.value,
@@ -467,6 +473,7 @@ async def delete(self):
467473
qs = self.get_arguments(self.request)
468474
crew_id = match_params.get('id') or qs.get('crew_id')
469475
crew_name = qs.get('name')
476+
tenant = qs.get('tenant') or "global"
470477

471478
if not crew_name and not crew_id:
472479
return self.error(
@@ -483,15 +490,15 @@ async def delete(self):
483490
identifier = crew_name or crew_id
484491

485492
# Check if exists first
486-
crew_data = await self.bot_manager.get_crew(identifier)
493+
crew_data = await self.bot_manager.get_crew(identifier, tenant=tenant)
487494
if not crew_data:
488495
return self.error(
489496
response={"message": f"Crew '{identifier}' not found"},
490497
status=404
491498
)
492499

493500
# Remove crew
494-
await self.bot_manager.remove_crew(identifier)
501+
await self.bot_manager.remove_crew(identifier, tenant=tenant)
495502

496503
return self.json_response({
497504
"message": f"Crew '{identifier}' deleted successfully"

parrot/handlers/crew/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ class CrewDefinition(BaseModel):
6969
default_factory=lambda: str(uuid.uuid4()),
7070
description="Unique identifier for the crew"
7171
)
72+
tenant: str = Field(
73+
default="global",
74+
description="Tenant identifier for crew isolation"
75+
)
7276
name: str = Field(description="Name of the crew")
7377
description: Optional[str] = Field(
7478
default=None,

0 commit comments

Comments
 (0)