Add concurrent agent

This commit is contained in:
Marian Paul
2026-01-21 15:17:01 +01:00
parent 71f3271274
commit 409560fb97
5 changed files with 64 additions and 2 deletions

View File

@@ -81,6 +81,7 @@ class Schedule(Base):
# Agent configuration for scheduled runs
yolo_mode = Column(Boolean, nullable=False, default=False)
model = Column(String(50), nullable=True) # None = use global default
max_concurrency = Column(Integer, nullable=False, default=3) # 1-5 concurrent agents
# Crash recovery tracking
crash_count = Column(Integer, nullable=False, default=0) # Resets at window start
@@ -104,6 +105,7 @@ class Schedule(Base):
"enabled": self.enabled,
"yolo_mode": self.yolo_mode,
"model": self.model,
"max_concurrency": self.max_concurrency,
"crash_count": self.crash_count,
"created_at": self.created_at.isoformat() if self.created_at else None,
}
@@ -275,6 +277,14 @@ def _migrate_add_schedules_tables(engine) -> None:
)
conn.commit()
# Add max_concurrency column if missing (for upgrades)
if "max_concurrency" not in columns:
with engine.connect() as conn:
conn.execute(
text("ALTER TABLE schedules ADD COLUMN max_concurrency INTEGER DEFAULT 3")
)
conn.commit()
def create_database(project_dir: Path) -> tuple:
"""

View File

@@ -501,6 +501,12 @@ class ScheduleCreate(BaseModel):
enabled: bool = True
yolo_mode: bool = False
model: str | None = None
max_concurrency: int = Field(
default=3,
ge=1,
le=5,
description="Max concurrent agents (1-5)"
)
@field_validator('model')
@classmethod
@@ -522,6 +528,7 @@ class ScheduleUpdate(BaseModel):
enabled: bool | None = None
yolo_mode: bool | None = None
model: str | None = None
max_concurrency: int | None = Field(None, ge=1, le=5)
@field_validator('model')
@classmethod
@@ -542,6 +549,7 @@ class ScheduleResponse(BaseModel):
enabled: bool
yolo_mode: bool
model: str | None
max_concurrency: int
crash_count: int
created_at: datetime

View File

@@ -368,10 +368,14 @@ class SchedulerService:
logger.info(f"Agent already running for {project_name}, skipping scheduled start")
return
logger.info(f"Starting agent for {project_name} (schedule {schedule.id}, yolo={schedule.yolo_mode})")
logger.info(
f"Starting agent for {project_name} "
f"(schedule {schedule.id}, yolo={schedule.yolo_mode}, concurrency={schedule.max_concurrency})"
)
success, msg = await manager.start(
yolo_mode=schedule.yolo_mode,
model=schedule.model,
max_concurrency=schedule.max_concurrency,
)
if success:

View File

@@ -6,7 +6,7 @@
*/
import { useState, useEffect, useRef } from 'react'
import { Clock, Trash2, X } from 'lucide-react'
import { Clock, GitBranch, Trash2, X } from 'lucide-react'
import {
useSchedules,
useCreateSchedule,
@@ -47,6 +47,7 @@ export function ScheduleModal({ projectName, isOpen, onClose }: ScheduleModalPro
enabled: true,
yolo_mode: false,
model: null,
max_concurrency: 3,
})
const [error, setError] = useState<string | null>(null)
@@ -124,6 +125,7 @@ export function ScheduleModal({ projectName, isOpen, onClose }: ScheduleModalPro
enabled: true,
yolo_mode: false,
model: null,
max_concurrency: 3,
})
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to create schedule')
@@ -242,6 +244,10 @@ export function ScheduleModal({ projectName, isOpen, onClose }: ScheduleModalPro
{schedule.yolo_mode && (
<span className="font-bold text-yellow-600"> YOLO mode</span>
)}
<span className="flex items-center gap-1">
<GitBranch size={12} />
{schedule.max_concurrency}x
</span>
{schedule.model && <span>Model: {schedule.model}</span>}
{schedule.crash_count > 0 && (
<span className="text-red-600">Crashes: {schedule.crash_count}</span>
@@ -369,6 +375,37 @@ export function ScheduleModal({ projectName, isOpen, onClose }: ScheduleModalPro
</label>
</div>
{/* Concurrency slider */}
<div className="mb-4">
<label className="block text-sm font-bold text-gray-700 dark:text-gray-200 mb-2">
Concurrent Agents (1-5)
</label>
<div className="flex items-center gap-3">
<GitBranch
size={16}
className={newSchedule.max_concurrency > 1 ? 'text-[var(--color-neo-primary)]' : 'text-gray-400'}
/>
<input
type="range"
min={1}
max={5}
value={newSchedule.max_concurrency}
onChange={(e) =>
setNewSchedule((prev) => ({ ...prev, max_concurrency: Number(e.target.value) }))
}
className="flex-1 h-2 accent-[var(--color-neo-primary)] cursor-pointer"
title={`${newSchedule.max_concurrency} concurrent agent${newSchedule.max_concurrency > 1 ? 's' : ''}`}
aria-label="Set number of concurrent agents"
/>
<span className="text-sm font-bold min-w-[2rem] text-center text-gray-900 dark:text-white">
{newSchedule.max_concurrency}x
</span>
</div>
<div className="text-xs text-gray-600 dark:text-gray-400 mt-1">
Run {newSchedule.max_concurrency} agent{newSchedule.max_concurrency > 1 ? 's' : ''} in parallel for faster feature completion
</div>
</div>
{/* Model selection (optional) */}
<div className="mb-6">
<label className="block text-sm font-bold text-gray-700 dark:text-gray-200 mb-2">

View File

@@ -503,6 +503,7 @@ export interface Schedule {
enabled: boolean
yolo_mode: boolean
model: string | null
max_concurrency: number // 1-5 concurrent agents
crash_count: number
created_at: string
}
@@ -514,6 +515,7 @@ export interface ScheduleCreate {
enabled: boolean
yolo_mode: boolean
model: string | null
max_concurrency: number // 1-5 concurrent agents
}
export interface ScheduleUpdate {
@@ -523,6 +525,7 @@ export interface ScheduleUpdate {
enabled?: boolean
yolo_mode?: boolean
model?: string | null
max_concurrency?: number
}
export interface ScheduleListResponse {