Skip to content

Commit fca1f6a

Browse files
authored
Merge pull request #226 from AutoForgeAI/feat/batch-size-limits-and-testing-batch-setting
feat: increase batch size limits to 15 and add testing_batch_size setting
2 parents 7f875c3 + b15f45c commit fca1f6a

14 files changed

Lines changed: 174 additions & 39 deletions

File tree

CLAUDE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ python autonomous_agent_demo.py --project-dir my-app --yolo
6565
# Parallel mode: run multiple agents concurrently (1-5 agents)
6666
python autonomous_agent_demo.py --project-dir my-app --parallel --max-concurrency 3
6767

68-
# Batch mode: implement multiple features per agent session (1-3)
68+
# Batch mode: implement multiple features per agent session (1-15)
6969
python autonomous_agent_demo.py --project-dir my-app --batch-size 3
7070

7171
# Batch specific features by ID
@@ -496,9 +496,9 @@ The orchestrator enforces strict bounds on concurrent processes:
496496

497497
### Multi-Feature Batching
498498

499-
Agents can implement multiple features per session using `--batch-size` (1-3, default: 3):
499+
Agents can implement multiple features per session using `--batch-size` (1-15, default: 3):
500500
- `--batch-size N` - Max features per coding agent batch
501-
- `--testing-batch-size N` - Features per testing batch (1-5, default: 3)
501+
- `--testing-batch-size N` - Features per testing batch (1-15, default: 3)
502502
- `--batch-features 1,2,3` - Specific feature IDs for batch implementation
503503
- `--testing-batch-features 1,2,3` - Specific feature IDs for batch regression testing
504504
- `prompts.py` provides `get_batch_feature_prompt()` for multi-feature prompt generation

autonomous_agent_demo.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,14 +176,14 @@ def parse_args() -> argparse.Namespace:
176176
"--testing-batch-size",
177177
type=int,
178178
default=3,
179-
help="Number of features per testing batch (1-5, default: 3)",
179+
help="Number of features per testing batch (1-15, default: 3)",
180180
)
181181

182182
parser.add_argument(
183183
"--batch-size",
184184
type=int,
185185
default=3,
186-
help="Max features per coding agent batch (1-3, default: 3)",
186+
help="Max features per coding agent batch (1-15, default: 3)",
187187
)
188188

189189
return parser.parse_args()

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "autoforge-ai",
3-
"version": "0.1.16",
3+
"version": "0.1.17",
44
"description": "Autonomous coding agent with web UI - build complete apps with AI",
55
"license": "AGPL-3.0",
66
"bin": {

parallel_orchestrator.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def _dump_database_state(feature_dicts: list[dict], label: str = ""):
131131
MAX_PARALLEL_AGENTS = 5
132132
MAX_TOTAL_AGENTS = 10
133133
DEFAULT_CONCURRENCY = 3
134-
DEFAULT_TESTING_BATCH_SIZE = 3 # Number of features per testing batch (1-5)
134+
DEFAULT_TESTING_BATCH_SIZE = 3 # Number of features per testing batch (1-15)
135135
POLL_INTERVAL = 5 # seconds between checking for ready features
136136
MAX_FEATURE_RETRIES = 3 # Maximum times to retry a failed feature
137137
INITIALIZER_TIMEOUT = 1800 # 30 minutes timeout for initializer
@@ -168,7 +168,7 @@ def __init__(
168168
yolo_mode: Whether to run in YOLO mode (skip testing agents entirely)
169169
testing_agent_ratio: Number of regression testing agents to maintain (0-3).
170170
0 = disabled, 1-3 = maintain that many testing agents running independently.
171-
testing_batch_size: Number of features to include per testing session (1-5).
171+
testing_batch_size: Number of features to include per testing session (1-15).
172172
Each testing agent receives this many features to regression test.
173173
on_output: Callback for agent output (feature_id, line)
174174
on_status: Callback for agent status changes (feature_id, status)
@@ -178,8 +178,8 @@ def __init__(
178178
self.model = model
179179
self.yolo_mode = yolo_mode
180180
self.testing_agent_ratio = min(max(testing_agent_ratio, 0), 3) # Clamp 0-3
181-
self.testing_batch_size = min(max(testing_batch_size, 1), 5) # Clamp 1-5
182-
self.batch_size = min(max(batch_size, 1), 3) # Clamp 1-3
181+
self.testing_batch_size = min(max(testing_batch_size, 1), 15) # Clamp 1-15
182+
self.batch_size = min(max(batch_size, 1), 15) # Clamp 1-15
183183
self.on_output = on_output
184184
self.on_status = on_status
185185

server/routers/agent.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@
1717
from ..utils.validation import validate_project_name
1818

1919

20-
def _get_settings_defaults() -> tuple[bool, str, int, bool, int]:
20+
def _get_settings_defaults() -> tuple[bool, str, int, bool, int, int]:
2121
"""Get defaults from global settings.
2222
2323
Returns:
24-
Tuple of (yolo_mode, model, testing_agent_ratio, playwright_headless, batch_size)
24+
Tuple of (yolo_mode, model, testing_agent_ratio, playwright_headless, batch_size, testing_batch_size)
2525
"""
2626
import sys
2727
root = Path(__file__).parent.parent.parent
@@ -47,7 +47,12 @@ def _get_settings_defaults() -> tuple[bool, str, int, bool, int]:
4747
except (ValueError, TypeError):
4848
batch_size = 3
4949

50-
return yolo_mode, model, testing_agent_ratio, playwright_headless, batch_size
50+
try:
51+
testing_batch_size = int(settings.get("testing_batch_size", "3"))
52+
except (ValueError, TypeError):
53+
testing_batch_size = 3
54+
55+
return yolo_mode, model, testing_agent_ratio, playwright_headless, batch_size, testing_batch_size
5156

5257

5358
router = APIRouter(prefix="/api/projects/{project_name}/agent", tags=["agent"])
@@ -96,14 +101,15 @@ async def start_agent(
96101
manager = get_project_manager(project_name)
97102

98103
# Get defaults from global settings if not provided in request
99-
default_yolo, default_model, default_testing_ratio, playwright_headless, default_batch_size = _get_settings_defaults()
104+
default_yolo, default_model, default_testing_ratio, playwright_headless, default_batch_size, default_testing_batch_size = _get_settings_defaults()
100105

101106
yolo_mode = request.yolo_mode if request.yolo_mode is not None else default_yolo
102107
model = request.model if request.model else default_model
103108
max_concurrency = request.max_concurrency or 1
104109
testing_agent_ratio = request.testing_agent_ratio if request.testing_agent_ratio is not None else default_testing_ratio
105110

106111
batch_size = default_batch_size
112+
testing_batch_size = default_testing_batch_size
107113

108114
success, message = await manager.start(
109115
yolo_mode=yolo_mode,
@@ -112,6 +118,7 @@ async def start_agent(
112118
testing_agent_ratio=testing_agent_ratio,
113119
playwright_headless=playwright_headless,
114120
batch_size=batch_size,
121+
testing_batch_size=testing_batch_size,
115122
)
116123

117124
# Notify scheduler of manual start (to prevent auto-stop during scheduled window)

server/routers/settings.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ async def get_settings():
113113
testing_agent_ratio=_parse_int(all_settings.get("testing_agent_ratio"), 1),
114114
playwright_headless=_parse_bool(all_settings.get("playwright_headless"), default=True),
115115
batch_size=_parse_int(all_settings.get("batch_size"), 3),
116+
testing_batch_size=_parse_int(all_settings.get("testing_batch_size"), 3),
116117
api_provider=api_provider,
117118
api_base_url=all_settings.get("api_base_url"),
118119
api_has_auth_token=bool(all_settings.get("api_auth_token")),
@@ -138,6 +139,9 @@ async def update_settings(update: SettingsUpdate):
138139
if update.batch_size is not None:
139140
set_setting("batch_size", str(update.batch_size))
140141

142+
if update.testing_batch_size is not None:
143+
set_setting("testing_batch_size", str(update.testing_batch_size))
144+
141145
# API provider settings
142146
if update.api_provider is not None:
143147
old_provider = get_setting("api_provider", "claude")
@@ -177,6 +181,7 @@ async def update_settings(update: SettingsUpdate):
177181
testing_agent_ratio=_parse_int(all_settings.get("testing_agent_ratio"), 1),
178182
playwright_headless=_parse_bool(all_settings.get("playwright_headless"), default=True),
179183
batch_size=_parse_int(all_settings.get("batch_size"), 3),
184+
testing_batch_size=_parse_int(all_settings.get("testing_batch_size"), 3),
180185
api_provider=api_provider,
181186
api_base_url=all_settings.get("api_base_url"),
182187
api_has_auth_token=bool(all_settings.get("api_auth_token")),

server/schemas.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,8 @@ class SettingsResponse(BaseModel):
444444
ollama_mode: bool = False # True when api_provider is "ollama"
445445
testing_agent_ratio: int = 1 # Regression testing agents (0-3)
446446
playwright_headless: bool = True
447-
batch_size: int = 3 # Features per coding agent batch (1-3)
447+
batch_size: int = 3 # Features per coding agent batch (1-15)
448+
testing_batch_size: int = 3 # Features per testing agent batch (1-15)
448449
api_provider: str = "claude"
449450
api_base_url: str | None = None
450451
api_has_auth_token: bool = False # Never expose actual token
@@ -463,7 +464,8 @@ class SettingsUpdate(BaseModel):
463464
model: str | None = None
464465
testing_agent_ratio: int | None = None # 0-3
465466
playwright_headless: bool | None = None
466-
batch_size: int | None = None # Features per agent batch (1-3)
467+
batch_size: int | None = None # Features per agent batch (1-15)
468+
testing_batch_size: int | None = None # Features per testing agent batch (1-15)
467469
api_provider: str | None = None
468470
api_base_url: str | None = Field(None, max_length=500)
469471
api_auth_token: str | None = Field(None, max_length=500) # Write-only, never returned
@@ -500,8 +502,15 @@ def validate_testing_ratio(cls, v: int | None) -> int | None:
500502
@field_validator('batch_size')
501503
@classmethod
502504
def validate_batch_size(cls, v: int | None) -> int | None:
503-
if v is not None and (v < 1 or v > 3):
504-
raise ValueError("batch_size must be between 1 and 3")
505+
if v is not None and (v < 1 or v > 15):
506+
raise ValueError("batch_size must be between 1 and 15")
507+
return v
508+
509+
@field_validator('testing_batch_size')
510+
@classmethod
511+
def validate_testing_batch_size(cls, v: int | None) -> int | None:
512+
if v is not None and (v < 1 or v > 15):
513+
raise ValueError("testing_batch_size must be between 1 and 15")
505514
return v
506515

507516

server/services/process_manager.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ async def start(
374374
testing_agent_ratio: int = 1,
375375
playwright_headless: bool = True,
376376
batch_size: int = 3,
377+
testing_batch_size: int = 3,
377378
) -> tuple[bool, str]:
378379
"""
379380
Start the agent as a subprocess.
@@ -440,6 +441,9 @@ async def start(
440441
# Add --batch-size flag for multi-feature batching
441442
cmd.extend(["--batch-size", str(batch_size)])
442443

444+
# Add --testing-batch-size flag for testing agent batching
445+
cmd.extend(["--testing-batch-size", str(testing_batch_size)])
446+
443447
# Apply headless setting to .playwright/cli.config.json so playwright-cli
444448
# picks it up (the only mechanism it supports for headless control)
445449
self._apply_playwright_headless(playwright_headless)

ui/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui/src/components/SettingsModal.tsx

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
DialogTitle,
1111
} from '@/components/ui/dialog'
1212
import { Switch } from '@/components/ui/switch'
13+
import { Slider } from '@/components/ui/slider'
1314
import { Label } from '@/components/ui/label'
1415
import { Alert, AlertDescription } from '@/components/ui/alert'
1516
import { Button } from '@/components/ui/button'
@@ -63,6 +64,12 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
6364
}
6465
}
6566

67+
const handleTestingBatchSizeChange = (size: number) => {
68+
if (!updateSettings.isPending) {
69+
updateSettings.mutate({ testing_batch_size: size })
70+
}
71+
}
72+
6673
const handleProviderChange = (providerId: string) => {
6774
if (!updateSettings.isPending) {
6875
updateSettings.mutate({ api_provider: providerId })
@@ -432,28 +439,34 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
432439
</div>
433440
</div>
434441

435-
{/* Features per Agent */}
442+
{/* Features per Coding Agent */}
436443
<div className="space-y-2">
437-
<Label className="font-medium">Features per Agent</Label>
444+
<Label className="font-medium">Features per Coding Agent</Label>
438445
<p className="text-sm text-muted-foreground">
439-
Number of features assigned to each coding agent
446+
Number of features assigned to each coding agent session
440447
</p>
441-
<div className="flex rounded-lg border overflow-hidden">
442-
{[1, 2, 3].map((size) => (
443-
<button
444-
key={size}
445-
onClick={() => handleBatchSizeChange(size)}
446-
disabled={isSaving}
447-
className={`flex-1 py-2 px-3 text-sm font-medium transition-colors ${
448-
(settings.batch_size ?? 1) === size
449-
? 'bg-primary text-primary-foreground'
450-
: 'bg-background text-foreground hover:bg-muted'
451-
} ${isSaving ? 'opacity-50 cursor-not-allowed' : ''}`}
452-
>
453-
{size}
454-
</button>
455-
))}
456-
</div>
448+
<Slider
449+
min={1}
450+
max={15}
451+
value={settings.batch_size ?? 3}
452+
onChange={handleBatchSizeChange}
453+
disabled={isSaving}
454+
/>
455+
</div>
456+
457+
{/* Features per Testing Agent */}
458+
<div className="space-y-2">
459+
<Label className="font-medium">Features per Testing Agent</Label>
460+
<p className="text-sm text-muted-foreground">
461+
Number of features assigned to each testing agent session
462+
</p>
463+
<Slider
464+
min={1}
465+
max={15}
466+
value={settings.testing_batch_size ?? 3}
467+
onChange={handleTestingBatchSizeChange}
468+
disabled={isSaving}
469+
/>
457470
</div>
458471

459472
{/* Update Error */}

0 commit comments

Comments
 (0)