foxy-dev-team/backend/app/workflows.py

182 lines
6.6 KiB
Python

"""
Dynamic Workflow Engine for Foxy Dev Team.
Replaces the static STATUS_TRANSITIONS dict from foxy-autopilot.py with a flexible
engine that supports 4 workflow types and routes tasks between agents accordingly.
"""
from dataclasses import dataclass
from typing import Optional
from app.models import ProjectStatus, WorkflowType
@dataclass(frozen=True)
class WorkflowStep:
"""A single step in a workflow pipeline."""
agent_name: str # e.g. "Foxy-Conductor"
awaiting_status: str # e.g. "AWAITING_CONDUCTOR"
running_status: str # e.g. "CONDUCTOR_RUNNING"
# ─── Workflow Definitions ──────────────────────────────────────────────────────
# Workflow 1: Standard Software Design
# Conductor → Architect → Dev → UIUX → QA → Admin
SOFTWARE_DESIGN_STEPS = [
WorkflowStep("Foxy-Conductor", "AWAITING_CONDUCTOR", "CONDUCTOR_RUNNING"),
WorkflowStep("Foxy-Architect", "AWAITING_ARCHITECT", "ARCHITECT_RUNNING"),
WorkflowStep("Foxy-Dev", "AWAITING_DEV", "DEV_RUNNING"),
WorkflowStep("Foxy-UIUX", "AWAITING_UIUX", "UIUX_RUNNING"),
WorkflowStep("Foxy-QA", "AWAITING_QA", "QA_RUNNING"),
WorkflowStep("Foxy-Admin", "AWAITING_DEPLOY", "DEPLOY_RUNNING"),
]
# Workflow 2: Sysadmin Debug
# Conductor → Admin → QA
SYSADMIN_DEBUG_STEPS = [
WorkflowStep("Foxy-Conductor", "AWAITING_CONDUCTOR", "CONDUCTOR_RUNNING"),
WorkflowStep("Foxy-Admin", "AWAITING_DEPLOY", "DEPLOY_RUNNING"),
WorkflowStep("Foxy-QA", "AWAITING_QA", "QA_RUNNING"),
]
# Workflow 3: DevOps Setup
# Conductor → Admin → QA
DEVOPS_SETUP_STEPS = [
WorkflowStep("Foxy-Conductor", "AWAITING_CONDUCTOR", "CONDUCTOR_RUNNING"),
WorkflowStep("Foxy-Admin", "AWAITING_DEPLOY", "DEPLOY_RUNNING"),
WorkflowStep("Foxy-QA", "AWAITING_QA", "QA_RUNNING"),
]
# Workflow 4: Sysadmin Adjust
# Conductor → Admin → QA
SYSADMIN_ADJUST_STEPS = [
WorkflowStep("Foxy-Conductor", "AWAITING_CONDUCTOR", "CONDUCTOR_RUNNING"),
WorkflowStep("Foxy-Admin", "AWAITING_DEPLOY", "DEPLOY_RUNNING"),
WorkflowStep("Foxy-QA", "AWAITING_QA", "QA_RUNNING"),
]
WORKFLOW_REGISTRY: dict[WorkflowType, list[WorkflowStep]] = {
WorkflowType.SOFTWARE_DESIGN: SOFTWARE_DESIGN_STEPS,
WorkflowType.SYSADMIN_DEBUG: SYSADMIN_DEBUG_STEPS,
WorkflowType.DEVOPS_SETUP: DEVOPS_SETUP_STEPS,
WorkflowType.SYSADMIN_ADJUST: SYSADMIN_ADJUST_STEPS,
}
# ─── Agent Label Mapping ──────────────────────────────────────────────────────
AGENT_LABELS = {
"Foxy-Conductor": "foxy-conductor",
"Foxy-Architect": "foxy-architect",
"Foxy-Dev": "foxy-dev",
"Foxy-UIUX": "foxy-uiux",
"Foxy-QA": "foxy-qa",
"Foxy-Admin": "foxy-admin",
}
AGENT_DISPLAY_NAMES = {v: k for k, v in AGENT_LABELS.items()}
AGENT_MODELS = {
"Foxy-Conductor": "grok-4.1-fast",
"Foxy-Architect": "grok-4.1-fast",
"Foxy-Dev": "minimax-m2.5",
"Foxy-UIUX": "qwen3-30b-a3b",
"Foxy-QA": "qwen3.5-flash",
"Foxy-Admin": "grok-4.1-fast",
}
# ─── Engine Functions ──────────────────────────────────────────────────────────
def get_workflow_steps(workflow_type: WorkflowType) -> list[WorkflowStep]:
"""Get the ordered list of steps for a workflow type."""
return WORKFLOW_REGISTRY.get(workflow_type, SOFTWARE_DESIGN_STEPS)
def get_current_step(workflow_type: WorkflowType, status: str) -> Optional[WorkflowStep]:
"""Find the step matching the current project status (awaiting or running)."""
steps = get_workflow_steps(workflow_type)
for step in steps:
if status in (step.awaiting_status, step.running_status):
return step
return None
def get_next_step(workflow_type: WorkflowType, status: str) -> Optional[WorkflowStep]:
"""
Given a 'RUNNING' status, determine the next awaiting step in the workflow.
Returns None if the current step is the last one (project should be COMPLETED).
"""
steps = get_workflow_steps(workflow_type)
for i, step in enumerate(steps):
if status == step.running_status:
if i + 1 < len(steps):
return steps[i + 1]
else:
return None # Last step — project is complete
return None
def get_next_awaiting_status(workflow_type: WorkflowType, current_running_status: str) -> str:
"""
After a running agent finishes, what's the next status?
Returns COMPLETED if the workflow is done.
"""
next_step = get_next_step(workflow_type, current_running_status)
if next_step:
return next_step.awaiting_status
return "COMPLETED"
def is_awaiting_status(status: str) -> bool:
"""Check if a status represents an 'awaiting' state."""
return status.startswith("AWAITING_")
def is_running_status(status: str) -> bool:
"""Check if a status represents a 'running' state."""
return status.endswith("_RUNNING")
def is_terminal_status(status: str) -> bool:
"""Check if a status is terminal (COMPLETED / FAILED)."""
return status in ("COMPLETED", "FAILED")
def get_workflow_progress(workflow_type: WorkflowType, status: str) -> dict:
"""
Calculate the progress of a project through its workflow.
Returns {current_step, total_steps, percentage, completed_agents, remaining_agents}.
"""
steps = get_workflow_steps(workflow_type)
total = len(steps)
if is_terminal_status(status):
return {
"current_step": total if status == "COMPLETED" else 0,
"total_steps": total,
"percentage": 100 if status == "COMPLETED" else 0,
"completed_agents": [s.agent_name for s in steps] if status == "COMPLETED" else [],
"remaining_agents": [],
}
current_idx = 0
for i, step in enumerate(steps):
if status in (step.awaiting_status, step.running_status):
current_idx = i
break
completed = [s.agent_name for s in steps[:current_idx]]
remaining = [s.agent_name for s in steps[current_idx:]]
percentage = int((current_idx / total) * 100) if total > 0 else 0
return {
"current_step": current_idx,
"total_steps": total,
"percentage": percentage,
"completed_agents": completed,
"remaining_agents": remaining,
}