Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/backend/api/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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")
33 changes: 29 additions & 4 deletions src/backend/orchestration/orchestration_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -808,4 +833,4 @@ async def _process_event_stream(
if tool_approvals:
result["tool_approvals"] = tool_approvals
return result
return None
return None