feat: add multi-feature batching for coding agents

Enable the orchestrator to assign 1-3 features per coding agent subprocess,
selected via dependency chain extension + same-category fill. This reduces
cold-start overhead and leverages shared context across related features.

Orchestrator (parallel_orchestrator.py):
- Add batch tracking: _batch_features and _feature_to_primary data structures
- Add build_feature_batches() with dependency chain + category fill algorithm
- Add start_feature_batch() and _spawn_coding_agent_batch() methods
- Update _on_agent_complete() for batch cleanup across all features
- Update stop_feature() with _feature_to_primary lookup
- Update get_ready_features() to exclude all batch feature IDs
- Update main loop to build batches then spawn per available slot

CLI and agent layer:
- Add --feature-ids (comma-separated) and --batch-size CLI args
- Add feature_ids parameter to run_autonomous_agent() with batch prompt selection
- Add get_batch_feature_prompt() with sequential workflow instructions

WebSocket layer (server/websocket.py):
- Add BATCH_CODING_AGENT_START_PATTERN and BATCH_FEATURES_COMPLETE_PATTERN
- Add _handle_batch_agent_start() and _handle_batch_agent_complete() methods
- Add featureIds field to all agent_update messages
- Track current_feature_id updates as agent moves through batch

Frontend (React UI):
- Add featureIds to ActiveAgent and WSAgentUpdateMessage types
- Update KanbanColumn and DependencyGraph agent-feature maps for batch
- Update AgentCard to show "Batch: #X, #Y, #Z" with active feature highlight
- Add "Features per Agent" segmented control (1-3) in SettingsModal

Settings integration (full stack):
- Add batch_size to schemas, settings router, agent router, process manager
- Default batch_size=3, user-configurable 1-3 via settings UI
- batch_size=1 is functionally identical to pre-batching behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Auto
2026-02-01 16:35:07 +02:00
parent e1e5209866
commit 1607fc8175
16 changed files with 654 additions and 82 deletions

View File

@@ -31,6 +31,7 @@ from progress import (
)
from prompts import (
copy_spec_to_project,
get_batch_feature_prompt,
get_coding_prompt,
get_initializer_prompt,
get_single_feature_prompt,
@@ -139,6 +140,7 @@ async def run_autonomous_agent(
max_iterations: Optional[int] = None,
yolo_mode: bool = False,
feature_id: Optional[int] = None,
feature_ids: Optional[list[int]] = None,
agent_type: Optional[str] = None,
testing_feature_id: Optional[int] = None,
testing_feature_ids: Optional[list[int]] = None,
@@ -152,6 +154,7 @@ async def run_autonomous_agent(
max_iterations: Maximum number of iterations (None for unlimited)
yolo_mode: If True, skip browser testing in coding agent prompts
feature_id: If set, work only on this specific feature (used by orchestrator for coding agents)
feature_ids: If set, work on these features in batch (used by orchestrator for batch mode)
agent_type: Type of agent: "initializer", "coding", "testing", or None (auto-detect)
testing_feature_id: For testing agents, the pre-claimed feature ID to test (legacy single mode)
testing_feature_ids: For testing agents, list of feature IDs to batch test
@@ -165,7 +168,9 @@ async def run_autonomous_agent(
print(f"Agent type: {agent_type}")
if yolo_mode:
print("Mode: YOLO (testing agents disabled)")
if feature_id:
if feature_ids and len(feature_ids) > 1:
print(f"Feature batch: {', '.join(f'#{fid}' for fid in feature_ids)}")
elif feature_id:
print(f"Feature assignment: #{feature_id}")
if max_iterations:
print(f"Max iterations: {max_iterations}")
@@ -239,6 +244,8 @@ async def run_autonomous_agent(
import os
if agent_type == "testing":
agent_id = f"testing-{os.getpid()}" # Unique ID for testing agents
elif feature_ids and len(feature_ids) > 1:
agent_id = f"batch-{feature_ids[0]}"
elif feature_id:
agent_id = f"feature-{feature_id}"
else:
@@ -250,9 +257,13 @@ async def run_autonomous_agent(
prompt = get_initializer_prompt(project_dir)
elif agent_type == "testing":
prompt = get_testing_prompt(project_dir, testing_feature_id, testing_feature_ids)
elif feature_id:
elif feature_ids and len(feature_ids) > 1:
# Batch mode (used by orchestrator for multi-feature coding agents)
prompt = get_batch_feature_prompt(feature_ids, project_dir, yolo_mode)
elif feature_id or (feature_ids is not None and len(feature_ids) == 1):
# Single-feature mode (used by orchestrator for coding agents)
prompt = get_single_feature_prompt(feature_id, project_dir, yolo_mode)
fid = feature_id if feature_id is not None else feature_ids[0] # type: ignore[index]
prompt = get_single_feature_prompt(fid, project_dir, yolo_mode)
else:
# General coding prompt (legacy path)
prompt = get_coding_prompt(project_dir, yolo_mode=yolo_mode)
@@ -356,12 +367,19 @@ async def run_autonomous_agent(
print("The autonomous agent has finished its work.")
break
# Single-feature mode OR testing agent: exit after one session
if feature_id is not None or agent_type == "testing":
# Single-feature mode, batch mode, or testing agent: exit after one session
if feature_ids and len(feature_ids) > 1:
print(f"\nBatch mode: Features {', '.join(f'#{fid}' for fid in feature_ids)} session complete.")
break
elif feature_id is not None or (feature_ids is not None and len(feature_ids) == 1):
fid = feature_id if feature_id is not None else feature_ids[0] # type: ignore[index]
if agent_type == "testing":
print("\nTesting agent complete. Terminating session.")
else:
print(f"\nSingle-feature mode: Feature #{feature_id} session complete.")
print(f"\nSingle-feature mode: Feature #{fid} session complete.")
break
elif agent_type == "testing":
print("\nTesting agent complete. Terminating session.")
break
# Reset rate limit retries only if no rate limit signal was detected