-
Notifications
You must be signed in to change notification settings - Fork 2
feat(agents): trigger multi-agent execution after task approval #246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+1,024
−161
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
9aa2e61
feat(agents): trigger multi-agent execution after task approval
1167390
fix(tests): remove unused imports in test_task_approval_execution
6fe801d
fix(tests): address code review feedback
c1632dd
refactor(tests): enforce no test.skip() inside test logic policy
e9365af
fix(tasks): guard broadcast calls in exception handlers
5ec3044
fix(e2e): address remaining test quality issues
b717788
fix(e2e): wrap task approval tests in serial describe
92cbe18
fix(e2e): handle shared project state in serial test suite
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,12 +5,16 @@ | |||||||
| - Task updates | ||||||||
| - Task status management | ||||||||
| - Task approval (for planning phase automation) | ||||||||
| - Multi-agent execution trigger (P0 fix) | ||||||||
| """ | ||||||||
|
|
||||||||
| import asyncio | ||||||||
| import logging | ||||||||
| from typing import List, Optional | ||||||||
| import os | ||||||||
| from datetime import datetime, UTC | ||||||||
| from typing import Any, List, Optional | ||||||||
|
|
||||||||
| from fastapi import APIRouter, Depends, HTTPException | ||||||||
| from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException | ||||||||
| from pydantic import BaseModel, Field | ||||||||
|
|
||||||||
| from codeframe.core.models import Task, TaskStatus | ||||||||
|
|
@@ -21,6 +25,7 @@ | |||||||
| from codeframe.ui.websocket_broadcasts import broadcast_development_started | ||||||||
| from codeframe.auth.dependencies import get_current_user | ||||||||
| from codeframe.auth.models import User | ||||||||
| from codeframe.agents.lead_agent import LeadAgent | ||||||||
|
|
||||||||
| logger = logging.getLogger(__name__) | ||||||||
|
|
||||||||
|
|
@@ -30,6 +35,107 @@ | |||||||
| project_router = APIRouter(prefix="/api/projects", tags=["tasks"]) | ||||||||
|
|
||||||||
|
|
||||||||
| # ============================================================================ | ||||||||
| # Background Task for Multi-Agent Execution (P0 Fix) | ||||||||
| # ============================================================================ | ||||||||
|
|
||||||||
|
|
||||||||
| async def start_development_execution( | ||||||||
| project_id: int, | ||||||||
| db: Database, | ||||||||
| ws_manager: Any, | ||||||||
| api_key: str | ||||||||
| ) -> None: | ||||||||
| """ | ||||||||
| Background task to start multi-agent execution after task approval. | ||||||||
|
|
||||||||
| This function: | ||||||||
| 1. Creates a LeadAgent instance for the project | ||||||||
| 2. Calls start_multi_agent_execution() to create agents and assign tasks | ||||||||
| 3. Handles errors gracefully with logging and WebSocket notifications | ||||||||
|
|
||||||||
| Workflow: | ||||||||
| - LeadAgent loads all approved tasks from database | ||||||||
| - Builds dependency graph for task ordering | ||||||||
| - Creates agents on-demand via AgentPoolManager | ||||||||
| - Assigns tasks to agents and executes in parallel | ||||||||
| - Broadcasts agent_created and task_assigned events via WebSocket | ||||||||
| - Continues until all tasks complete or fail | ||||||||
|
|
||||||||
| Args: | ||||||||
| project_id: Project ID to start execution for | ||||||||
| db: Database instance | ||||||||
| ws_manager: WebSocket manager for broadcasts | ||||||||
| api_key: Anthropic API key for agent creation | ||||||||
| """ | ||||||||
| try: | ||||||||
| logger.info(f"🚀 Starting multi-agent execution for project {project_id}") | ||||||||
|
|
||||||||
| # Create LeadAgent instance with WebSocket manager for event broadcasts | ||||||||
| lead_agent = LeadAgent( | ||||||||
| project_id=project_id, | ||||||||
| db=db, | ||||||||
| api_key=api_key, | ||||||||
| ws_manager=ws_manager | ||||||||
| ) | ||||||||
|
|
||||||||
| # Start multi-agent execution (creates agents and assigns tasks) | ||||||||
| # This is the main coordination loop that: | ||||||||
| # 1. Loads all tasks and builds dependency graph | ||||||||
| # 2. Creates agents on-demand via agent_pool_manager.get_or_create_agent() | ||||||||
| # 3. Assigns tasks to agents and executes in parallel | ||||||||
| # 4. Broadcasts agent_created events when agents are created | ||||||||
| # 5. Broadcasts task_assigned events when tasks are assigned | ||||||||
| # 6. Continues until all tasks complete or fail | ||||||||
| summary = await lead_agent.start_multi_agent_execution( | ||||||||
| max_retries=3, | ||||||||
| max_concurrent=5, | ||||||||
| timeout=300 | ||||||||
| ) | ||||||||
|
|
||||||||
| logger.info( | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| f"✅ Multi-agent execution completed for project {project_id}: " | ||||||||
| f"{summary.get('completed', 0)}/{summary.get('total_tasks', 0)} tasks completed, " | ||||||||
| f"{summary.get('failed', 0)} failed, {summary.get('execution_time', 0):.2f}s" | ||||||||
| ) | ||||||||
|
|
||||||||
| except asyncio.TimeoutError: | ||||||||
| logger.error( | ||||||||
| f"❌ Multi-agent execution timed out for project {project_id} after 300s" | ||||||||
| ) | ||||||||
| # Broadcast timeout error to UI (guarded to prevent masking original error) | ||||||||
| try: | ||||||||
| await ws_manager.broadcast( | ||||||||
| { | ||||||||
| "type": "development_failed", | ||||||||
| "project_id": project_id, | ||||||||
| "error": "Multi-agent execution timed out after 300 seconds", | ||||||||
| "timestamp": datetime.now(UTC).isoformat().replace("+00:00", "Z") | ||||||||
| }, | ||||||||
| project_id=project_id | ||||||||
| ) | ||||||||
| except Exception: | ||||||||
| logger.exception("Failed to broadcast development_failed (timeout)") | ||||||||
| except Exception as e: | ||||||||
| logger.error( | ||||||||
| f"❌ Failed to start multi-agent execution for project {project_id}: {e}", | ||||||||
| exc_info=True | ||||||||
| ) | ||||||||
| # Broadcast error to UI (guarded to prevent masking original error) | ||||||||
| try: | ||||||||
| await ws_manager.broadcast( | ||||||||
| { | ||||||||
| "type": "development_failed", | ||||||||
| "project_id": project_id, | ||||||||
| "error": str(e), | ||||||||
| "timestamp": datetime.now(UTC).isoformat().replace("+00:00", "Z") | ||||||||
| }, | ||||||||
| project_id=project_id | ||||||||
| ) | ||||||||
| except Exception: | ||||||||
| logger.exception("Failed to broadcast development_failed (error)") | ||||||||
|
|
||||||||
|
|
||||||||
| class TaskCreateRequest(BaseModel): | ||||||||
| """Request model for creating a task.""" | ||||||||
| project_id: int | ||||||||
|
|
@@ -136,18 +242,21 @@ class TaskApprovalResponse(BaseModel): | |||||||
| async def approve_tasks( | ||||||||
| project_id: int, | ||||||||
| request: TaskApprovalRequest, | ||||||||
| background_tasks: BackgroundTasks, | ||||||||
| db: Database = Depends(get_db), | ||||||||
| current_user: User = Depends(get_current_user), | ||||||||
| ) -> TaskApprovalResponse: | ||||||||
| """Approve tasks and transition project to development phase. | ||||||||
|
|
||||||||
| This endpoint allows users to approve generated tasks after reviewing them. | ||||||||
| Approved tasks are updated to 'pending' status and the project phase | ||||||||
| transitions to 'active' (development). | ||||||||
| transitions to 'active' (development). After approval, multi-agent execution | ||||||||
| is triggered in the background. | ||||||||
|
|
||||||||
| Args: | ||||||||
| project_id: Project ID | ||||||||
| request: Approval request with approved flag and optional exclusions | ||||||||
| background_tasks: FastAPI background tasks for async execution | ||||||||
| db: Database connection | ||||||||
| current_user: Authenticated user | ||||||||
|
|
||||||||
|
|
@@ -230,6 +339,25 @@ async def approve_tasks( | |||||||
| excluded_count=len(excluded_tasks), | ||||||||
| ) | ||||||||
|
|
||||||||
| # START MULTI-AGENT EXECUTION IN BACKGROUND | ||||||||
| # Schedule background task to create agents and start task execution | ||||||||
| # This follows the same pattern as start_project_agent in agents.py | ||||||||
| api_key = os.environ.get("ANTHROPIC_API_KEY") | ||||||||
| if api_key: | ||||||||
| # Schedule background task to start multi-agent execution | ||||||||
| background_tasks.add_task( | ||||||||
| start_development_execution, | ||||||||
| project_id, | ||||||||
| db, | ||||||||
| manager, | ||||||||
| api_key | ||||||||
| ) | ||||||||
| logger.info(f"✅ Scheduled multi-agent execution for project {project_id}") | ||||||||
| else: | ||||||||
| logger.warning( | ||||||||
| f"⚠️ ANTHROPIC_API_KEY not configured - cannot start agents for project {project_id}" | ||||||||
| ) | ||||||||
|
|
||||||||
| logger.info( | ||||||||
| f"Tasks approved for project {project_id}: " | ||||||||
| f"{len(approved_tasks)} approved, {len(excluded_tasks)} excluded" | ||||||||
|
|
||||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good policy; tighten scope wording + avoid duplicated policy text in-file.
beforeEach/afterEachhooks (same “inside test execution” pitfall).🤖 Prompt for AI Agents