diff --git a/src/backend/api/router.py b/src/backend/api/router.py index 886eaf4f1..6af07485a 100644 --- a/src/backend/api/router.py +++ b/src/backend/api/router.py @@ -341,19 +341,31 @@ async def process_request( # Ensure the workflow is valid (rebuild if terminated or stuck from a prior run) current_workflow = orchestration_config.get_current_orchestration(user_id) + + cached_team_id = getattr(current_workflow, "_team_id", None) + team_mismatch = ( + current_workflow is not None and cached_team_id != team_id + ) + workflow_unusable = ( current_workflow is None or getattr(current_workflow, "_terminated", False) or getattr(current_workflow, "_is_running", False) + or team_mismatch ) if workflow_unusable: logger.info( - "Workflow unusable for user '%s' (None=%s, terminated=%s, is_running=%s) — rebuilding", + "Workflow unusable for user '%s' (None=%s, terminated=%s, is_running=%s, " + "team_mismatch=%s cached_team=%s selected_team=%s) — rebuilding", user_id, current_workflow is None, getattr(current_workflow, "_terminated", False), getattr(current_workflow, "_is_running", False), + team_mismatch, + cached_team_id, + team_id, ) + # Force-clear the running flag so get_current_or_new_orchestration # sees it as terminated and takes the lightweight reset path. if current_workflow is not None and getattr(current_workflow, "_is_running", False): @@ -1454,4 +1466,4 @@ async def get_generated_image(blob_name: str): return Response(content=data, media_type="image/png") except Exception as exc: logging.error(f"Error retrieving image '{blob_name}': {exc}") - raise HTTPException(status_code=404, detail="Image not found") + raise HTTPException(status_code=404, detail="Image not found") \ No newline at end of file diff --git a/src/backend/orchestration/orchestration_manager.py b/src/backend/orchestration/orchestration_manager.py index 3c352a3e3..1eddd40d9 100644 --- a/src/backend/orchestration/orchestration_manager.py +++ b/src/backend/orchestration/orchestration_manager.py @@ -202,10 +202,35 @@ async def get_current_or_new_orchestration( current = orchestration_config.get_current_orchestration(user_id) workflow_terminated = getattr(current, "_terminated", False) - # Full rebuild: no workflow exists or team explicitly changed - needs_full_rebuild = current is None or team_switched + # Detect a stale cached orchestration: it was built for a different team + # than the one now selected. Without this, /select_team leaves the prior + # team's workflow cached and the next run executes the wrong agents until + # a page refresh rebuilds it. The team_id tag is set on every workflow we + # build/reset below. + current_team_id = getattr(current, "_team_id", None) + team_changed = ( + current is not None and current_team_id != team_config.team_id + ) + + + cls.logger.info( + "get_current_or_new_orchestration: user='%s' selected_team='%s' " + "cached_team='%s' team_switched=%s team_changed=%s current_is_none=%s", + user_id, team_config.team_id, current_team_id, + team_switched, team_changed, current is None, + ) + + + # Full rebuild: no workflow exists, team explicitly switched, or the + # cached workflow belongs to a different team than the selected one. + needs_full_rebuild = current is None or team_switched or team_changed + + + # Lightweight reset: workflow finished but agents are still valid for the + # same team (a team change always routes to full rebuild above so we + # never reuse the previous team's agents here). + - # Lightweight reset: workflow finished but agents are still valid needs_workflow_reset = not needs_full_rebuild and workflow_terminated if needs_full_rebuild: @@ -808,4 +833,4 @@ async def _process_event_stream( if tool_approvals: result["tool_approvals"] = tool_approvals return result - return None + return None \ No newline at end of file