From 8b2251331d8ad7719d481f8880d3a9653bdaf9d7 Mon Sep 17 00:00:00 2001 From: Auto Date: Fri, 20 Mar 2026 13:39:19 +0200 Subject: [PATCH 1/3] feat: increase batch size limits to 15 and add testing_batch_size setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Batch size configuration: - Increase coding agent batch size limit from 1-3 to 1-15 - Increase testing agent batch size limit from 1-5 to 1-15 - Add separate `testing_batch_size` setting (previously only CLI-configurable) - Pass testing_batch_size through full stack: schema → settings router → agent router → process manager → CLI flag UI changes: - Replace 3-button batch size selector with range slider (1-15) - Add new Slider component (ui/src/components/ui/slider.tsx) - Add "Features per Testing Agent" slider in settings panel - Add custom slider CSS styling for webkit and mozilla Updated across: CLAUDE.md, autonomous_agent_demo.py, parallel_orchestrator.py, server/{schemas,routers,services}, and UI types/hooks/components. Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 6 ++-- autonomous_agent_demo.py | 4 +-- parallel_orchestrator.py | 8 ++--- server/routers/agent.py | 15 ++++++--- server/routers/settings.py | 5 +++ server/schemas.py | 17 +++++++--- server/services/process_manager.py | 4 +++ ui/src/components/SettingsModal.tsx | 51 ++++++++++++++++++----------- ui/src/components/ui/slider.tsx | 44 +++++++++++++++++++++++++ ui/src/hooks/useProjects.ts | 1 + ui/src/lib/types.ts | 4 ++- ui/src/styles/globals.css | 50 ++++++++++++++++++++++++++++ 12 files changed, 172 insertions(+), 37 deletions(-) create mode 100644 ui/src/components/ui/slider.tsx diff --git a/CLAUDE.md b/CLAUDE.md index 8665260..6f59910 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -65,7 +65,7 @@ python autonomous_agent_demo.py --project-dir my-app --yolo # Parallel mode: run multiple agents concurrently (1-5 agents) python autonomous_agent_demo.py --project-dir my-app --parallel --max-concurrency 3 -# Batch mode: implement multiple features per agent session (1-3) +# Batch mode: implement multiple features per agent session (1-15) python autonomous_agent_demo.py --project-dir my-app --batch-size 3 # Batch specific features by ID @@ -496,9 +496,9 @@ The orchestrator enforces strict bounds on concurrent processes: ### Multi-Feature Batching -Agents can implement multiple features per session using `--batch-size` (1-3, default: 3): +Agents can implement multiple features per session using `--batch-size` (1-15, default: 3): - `--batch-size N` - Max features per coding agent batch -- `--testing-batch-size N` - Features per testing batch (1-5, default: 3) +- `--testing-batch-size N` - Features per testing batch (1-15, default: 3) - `--batch-features 1,2,3` - Specific feature IDs for batch implementation - `--testing-batch-features 1,2,3` - Specific feature IDs for batch regression testing - `prompts.py` provides `get_batch_feature_prompt()` for multi-feature prompt generation diff --git a/autonomous_agent_demo.py b/autonomous_agent_demo.py index f24908f..3a6ae92 100644 --- a/autonomous_agent_demo.py +++ b/autonomous_agent_demo.py @@ -176,14 +176,14 @@ Authentication: "--testing-batch-size", type=int, default=3, - help="Number of features per testing batch (1-5, default: 3)", + help="Number of features per testing batch (1-15, default: 3)", ) parser.add_argument( "--batch-size", type=int, default=3, - help="Max features per coding agent batch (1-3, default: 3)", + help="Max features per coding agent batch (1-15, default: 3)", ) return parser.parse_args() diff --git a/parallel_orchestrator.py b/parallel_orchestrator.py index b7f2bac..e39ef79 100644 --- a/parallel_orchestrator.py +++ b/parallel_orchestrator.py @@ -131,7 +131,7 @@ def _dump_database_state(feature_dicts: list[dict], label: str = ""): MAX_PARALLEL_AGENTS = 5 MAX_TOTAL_AGENTS = 10 DEFAULT_CONCURRENCY = 3 -DEFAULT_TESTING_BATCH_SIZE = 3 # Number of features per testing batch (1-5) +DEFAULT_TESTING_BATCH_SIZE = 3 # Number of features per testing batch (1-15) POLL_INTERVAL = 5 # seconds between checking for ready features MAX_FEATURE_RETRIES = 3 # Maximum times to retry a failed feature INITIALIZER_TIMEOUT = 1800 # 30 minutes timeout for initializer @@ -168,7 +168,7 @@ class ParallelOrchestrator: yolo_mode: Whether to run in YOLO mode (skip testing agents entirely) testing_agent_ratio: Number of regression testing agents to maintain (0-3). 0 = disabled, 1-3 = maintain that many testing agents running independently. - testing_batch_size: Number of features to include per testing session (1-5). + testing_batch_size: Number of features to include per testing session (1-15). Each testing agent receives this many features to regression test. on_output: Callback for agent output (feature_id, line) on_status: Callback for agent status changes (feature_id, status) @@ -178,8 +178,8 @@ class ParallelOrchestrator: self.model = model self.yolo_mode = yolo_mode self.testing_agent_ratio = min(max(testing_agent_ratio, 0), 3) # Clamp 0-3 - self.testing_batch_size = min(max(testing_batch_size, 1), 5) # Clamp 1-5 - self.batch_size = min(max(batch_size, 1), 3) # Clamp 1-3 + self.testing_batch_size = min(max(testing_batch_size, 1), 15) # Clamp 1-15 + self.batch_size = min(max(batch_size, 1), 15) # Clamp 1-15 self.on_output = on_output self.on_status = on_status diff --git a/server/routers/agent.py b/server/routers/agent.py index ea96166..0e370f6 100644 --- a/server/routers/agent.py +++ b/server/routers/agent.py @@ -17,11 +17,11 @@ from ..utils.project_helpers import get_project_path as _get_project_path from ..utils.validation import validate_project_name -def _get_settings_defaults() -> tuple[bool, str, int, bool, int]: +def _get_settings_defaults() -> tuple[bool, str, int, bool, int, int]: """Get defaults from global settings. Returns: - Tuple of (yolo_mode, model, testing_agent_ratio, playwright_headless, batch_size) + Tuple of (yolo_mode, model, testing_agent_ratio, playwright_headless, batch_size, testing_batch_size) """ import sys root = Path(__file__).parent.parent.parent @@ -47,7 +47,12 @@ def _get_settings_defaults() -> tuple[bool, str, int, bool, int]: except (ValueError, TypeError): batch_size = 3 - return yolo_mode, model, testing_agent_ratio, playwright_headless, batch_size + try: + testing_batch_size = int(settings.get("testing_batch_size", "3")) + except (ValueError, TypeError): + testing_batch_size = 3 + + return yolo_mode, model, testing_agent_ratio, playwright_headless, batch_size, testing_batch_size router = APIRouter(prefix="/api/projects/{project_name}/agent", tags=["agent"]) @@ -96,7 +101,7 @@ async def start_agent( manager = get_project_manager(project_name) # Get defaults from global settings if not provided in request - default_yolo, default_model, default_testing_ratio, playwright_headless, default_batch_size = _get_settings_defaults() + default_yolo, default_model, default_testing_ratio, playwright_headless, default_batch_size, default_testing_batch_size = _get_settings_defaults() yolo_mode = request.yolo_mode if request.yolo_mode is not None else default_yolo model = request.model if request.model else default_model @@ -104,6 +109,7 @@ async def start_agent( testing_agent_ratio = request.testing_agent_ratio if request.testing_agent_ratio is not None else default_testing_ratio batch_size = default_batch_size + testing_batch_size = default_testing_batch_size success, message = await manager.start( yolo_mode=yolo_mode, @@ -112,6 +118,7 @@ async def start_agent( testing_agent_ratio=testing_agent_ratio, playwright_headless=playwright_headless, batch_size=batch_size, + testing_batch_size=testing_batch_size, ) # Notify scheduler of manual start (to prevent auto-stop during scheduled window) diff --git a/server/routers/settings.py b/server/routers/settings.py index 6137c63..8fd3c16 100644 --- a/server/routers/settings.py +++ b/server/routers/settings.py @@ -113,6 +113,7 @@ async def get_settings(): testing_agent_ratio=_parse_int(all_settings.get("testing_agent_ratio"), 1), playwright_headless=_parse_bool(all_settings.get("playwright_headless"), default=True), batch_size=_parse_int(all_settings.get("batch_size"), 3), + testing_batch_size=_parse_int(all_settings.get("testing_batch_size"), 3), api_provider=api_provider, api_base_url=all_settings.get("api_base_url"), api_has_auth_token=bool(all_settings.get("api_auth_token")), @@ -138,6 +139,9 @@ async def update_settings(update: SettingsUpdate): if update.batch_size is not None: set_setting("batch_size", str(update.batch_size)) + if update.testing_batch_size is not None: + set_setting("testing_batch_size", str(update.testing_batch_size)) + # API provider settings if update.api_provider is not None: old_provider = get_setting("api_provider", "claude") @@ -177,6 +181,7 @@ async def update_settings(update: SettingsUpdate): testing_agent_ratio=_parse_int(all_settings.get("testing_agent_ratio"), 1), playwright_headless=_parse_bool(all_settings.get("playwright_headless"), default=True), batch_size=_parse_int(all_settings.get("batch_size"), 3), + testing_batch_size=_parse_int(all_settings.get("testing_batch_size"), 3), api_provider=api_provider, api_base_url=all_settings.get("api_base_url"), api_has_auth_token=bool(all_settings.get("api_auth_token")), diff --git a/server/schemas.py b/server/schemas.py index b00c9c4..72124a5 100644 --- a/server/schemas.py +++ b/server/schemas.py @@ -444,7 +444,8 @@ class SettingsResponse(BaseModel): ollama_mode: bool = False # True when api_provider is "ollama" testing_agent_ratio: int = 1 # Regression testing agents (0-3) playwright_headless: bool = True - batch_size: int = 3 # Features per coding agent batch (1-3) + batch_size: int = 3 # Features per coding agent batch (1-15) + testing_batch_size: int = 3 # Features per testing agent batch (1-15) api_provider: str = "claude" api_base_url: str | None = None api_has_auth_token: bool = False # Never expose actual token @@ -463,7 +464,8 @@ class SettingsUpdate(BaseModel): model: str | None = None testing_agent_ratio: int | None = None # 0-3 playwright_headless: bool | None = None - batch_size: int | None = None # Features per agent batch (1-3) + batch_size: int | None = None # Features per agent batch (1-15) + testing_batch_size: int | None = None # Features per testing agent batch (1-15) api_provider: str | None = None api_base_url: str | None = Field(None, max_length=500) api_auth_token: str | None = Field(None, max_length=500) # Write-only, never returned @@ -500,8 +502,15 @@ class SettingsUpdate(BaseModel): @field_validator('batch_size') @classmethod def validate_batch_size(cls, v: int | None) -> int | None: - if v is not None and (v < 1 or v > 3): - raise ValueError("batch_size must be between 1 and 3") + if v is not None and (v < 1 or v > 15): + raise ValueError("batch_size must be between 1 and 15") + return v + + @field_validator('testing_batch_size') + @classmethod + def validate_testing_batch_size(cls, v: int | None) -> int | None: + if v is not None and (v < 1 or v > 15): + raise ValueError("testing_batch_size must be between 1 and 15") return v diff --git a/server/services/process_manager.py b/server/services/process_manager.py index e21ffef..9891916 100644 --- a/server/services/process_manager.py +++ b/server/services/process_manager.py @@ -374,6 +374,7 @@ class AgentProcessManager: testing_agent_ratio: int = 1, playwright_headless: bool = True, batch_size: int = 3, + testing_batch_size: int = 3, ) -> tuple[bool, str]: """ Start the agent as a subprocess. @@ -440,6 +441,9 @@ class AgentProcessManager: # Add --batch-size flag for multi-feature batching cmd.extend(["--batch-size", str(batch_size)]) + # Add --testing-batch-size flag for testing agent batching + cmd.extend(["--testing-batch-size", str(testing_batch_size)]) + # Apply headless setting to .playwright/cli.config.json so playwright-cli # picks it up (the only mechanism it supports for headless control) self._apply_playwright_headless(playwright_headless) diff --git a/ui/src/components/SettingsModal.tsx b/ui/src/components/SettingsModal.tsx index 284e6f1..966095b 100644 --- a/ui/src/components/SettingsModal.tsx +++ b/ui/src/components/SettingsModal.tsx @@ -10,6 +10,7 @@ import { DialogTitle, } from '@/components/ui/dialog' import { Switch } from '@/components/ui/switch' +import { Slider } from '@/components/ui/slider' import { Label } from '@/components/ui/label' import { Alert, AlertDescription } from '@/components/ui/alert' import { Button } from '@/components/ui/button' @@ -63,6 +64,12 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { } } + const handleTestingBatchSizeChange = (size: number) => { + if (!updateSettings.isPending) { + updateSettings.mutate({ testing_batch_size: size }) + } + } + const handleProviderChange = (providerId: string) => { if (!updateSettings.isPending) { updateSettings.mutate({ api_provider: providerId }) @@ -432,28 +439,34 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { - {/* Features per Agent */} + {/* Features per Coding Agent */}
- +

- Number of features assigned to each coding agent + Number of features assigned to each coding agent session

-
- {[1, 2, 3].map((size) => ( - - ))} -
+ +
+ + {/* Features per Testing Agent */} +
+ +

+ Number of features assigned to each testing agent session +

+
{/* Update Error */} diff --git a/ui/src/components/ui/slider.tsx b/ui/src/components/ui/slider.tsx new file mode 100644 index 0000000..ea05491 --- /dev/null +++ b/ui/src/components/ui/slider.tsx @@ -0,0 +1,44 @@ +import * as React from "react" +import { cn } from "@/lib/utils" + +interface SliderProps extends Omit, 'onChange'> { + min: number + max: number + value: number + onChange: (value: number) => void + label?: string +} + +function Slider({ + className, + min, + max, + value, + onChange, + disabled, + ...props +}: SliderProps) { + return ( +
+ onChange(Number(e.target.value))} + disabled={disabled} + className={cn( + "slider-input h-2 w-full cursor-pointer appearance-none rounded-full bg-input transition-colors", + "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", + disabled && "cursor-not-allowed opacity-50" + )} + {...props} + /> + + {value} + +
+ ) +} + +export { Slider } diff --git a/ui/src/hooks/useProjects.ts b/ui/src/hooks/useProjects.ts index 8ccd064..707f15a 100644 --- a/ui/src/hooks/useProjects.ts +++ b/ui/src/hooks/useProjects.ts @@ -302,6 +302,7 @@ const DEFAULT_SETTINGS: Settings = { testing_agent_ratio: 1, playwright_headless: true, batch_size: 3, + testing_batch_size: 3, api_provider: 'claude', api_base_url: null, api_has_auth_token: false, diff --git a/ui/src/lib/types.ts b/ui/src/lib/types.ts index 504888a..236a8ab 100644 --- a/ui/src/lib/types.ts +++ b/ui/src/lib/types.ts @@ -579,7 +579,8 @@ export interface Settings { ollama_mode: boolean testing_agent_ratio: number // Regression testing agents (0-3) playwright_headless: boolean - batch_size: number // Features per coding agent batch (1-3) + batch_size: number // Features per coding agent batch (1-15) + testing_batch_size: number // Features per testing agent batch (1-15) api_provider: string api_base_url: string | null api_has_auth_token: boolean @@ -592,6 +593,7 @@ export interface SettingsUpdate { testing_agent_ratio?: number playwright_headless?: boolean batch_size?: number + testing_batch_size?: number api_provider?: string api_base_url?: string api_auth_token?: string diff --git a/ui/src/styles/globals.css b/ui/src/styles/globals.css index 257b93b..8980eca 100644 --- a/ui/src/styles/globals.css +++ b/ui/src/styles/globals.css @@ -1472,3 +1472,53 @@ ::-webkit-scrollbar-thumb:hover { background: var(--muted-foreground); } + +/* ============================================================================ + Slider (range input) styling + ============================================================================ */ + +.slider-input::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 16px; + height: 16px; + border-radius: 50%; + background: var(--primary); + border: 2px solid var(--primary-foreground); + box-shadow: var(--shadow-sm); + cursor: pointer; + transition: transform 150ms, box-shadow 150ms; +} + +.slider-input::-webkit-slider-thumb:hover { + transform: scale(1.15); + box-shadow: var(--shadow); +} + +.slider-input::-moz-range-thumb { + width: 16px; + height: 16px; + border-radius: 50%; + background: var(--primary); + border: 2px solid var(--primary-foreground); + box-shadow: var(--shadow-sm); + cursor: pointer; + transition: transform 150ms, box-shadow 150ms; +} + +.slider-input::-moz-range-thumb:hover { + transform: scale(1.15); + box-shadow: var(--shadow); +} + +.slider-input::-webkit-slider-runnable-track { + height: 8px; + border-radius: 9999px; + background: var(--input); +} + +.slider-input::-moz-range-track { + height: 8px; + border-radius: 9999px; + background: var(--input); +} From f999e1937d4da5c4a2275593ba37c3f1a2bb4192 Mon Sep 17 00:00:00 2001 From: Auto Date: Fri, 20 Mar 2026 13:39:23 +0200 Subject: [PATCH 2/3] 0.1.17 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dd33d91..45fb476 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "autoforge-ai", - "version": "0.1.16", + "version": "0.1.17", "description": "Autonomous coding agent with web UI - build complete apps with AI", "license": "AGPL-3.0", "bin": { From b15f45c0947218887976fdc4dec99557160e5ccb Mon Sep 17 00:00:00 2001 From: Auto Date: Fri, 20 Mar 2026 13:39:56 +0200 Subject: [PATCH 3/3] version patch --- ui/package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index 12d9de3..b9a5cd2 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -56,7 +56,7 @@ }, "..": { "name": "autoforge-ai", - "version": "0.1.16", + "version": "0.1.17", "license": "AGPL-3.0", "bin": { "autoforge": "bin/autoforge.js"