mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-02-01 23:13:36 +00:00
Add concurrent agent
This commit is contained in:
@@ -81,6 +81,7 @@ class Schedule(Base):
|
|||||||
# Agent configuration for scheduled runs
|
# Agent configuration for scheduled runs
|
||||||
yolo_mode = Column(Boolean, nullable=False, default=False)
|
yolo_mode = Column(Boolean, nullable=False, default=False)
|
||||||
model = Column(String(50), nullable=True) # None = use global default
|
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 recovery tracking
|
||||||
crash_count = Column(Integer, nullable=False, default=0) # Resets at window start
|
crash_count = Column(Integer, nullable=False, default=0) # Resets at window start
|
||||||
@@ -104,6 +105,7 @@ class Schedule(Base):
|
|||||||
"enabled": self.enabled,
|
"enabled": self.enabled,
|
||||||
"yolo_mode": self.yolo_mode,
|
"yolo_mode": self.yolo_mode,
|
||||||
"model": self.model,
|
"model": self.model,
|
||||||
|
"max_concurrency": self.max_concurrency,
|
||||||
"crash_count": self.crash_count,
|
"crash_count": self.crash_count,
|
||||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
"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()
|
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:
|
def create_database(project_dir: Path) -> tuple:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -501,6 +501,12 @@ class ScheduleCreate(BaseModel):
|
|||||||
enabled: bool = True
|
enabled: bool = True
|
||||||
yolo_mode: bool = False
|
yolo_mode: bool = False
|
||||||
model: str | None = None
|
model: str | None = None
|
||||||
|
max_concurrency: int = Field(
|
||||||
|
default=3,
|
||||||
|
ge=1,
|
||||||
|
le=5,
|
||||||
|
description="Max concurrent agents (1-5)"
|
||||||
|
)
|
||||||
|
|
||||||
@field_validator('model')
|
@field_validator('model')
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -522,6 +528,7 @@ class ScheduleUpdate(BaseModel):
|
|||||||
enabled: bool | None = None
|
enabled: bool | None = None
|
||||||
yolo_mode: bool | None = None
|
yolo_mode: bool | None = None
|
||||||
model: str | None = None
|
model: str | None = None
|
||||||
|
max_concurrency: int | None = Field(None, ge=1, le=5)
|
||||||
|
|
||||||
@field_validator('model')
|
@field_validator('model')
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -542,6 +549,7 @@ class ScheduleResponse(BaseModel):
|
|||||||
enabled: bool
|
enabled: bool
|
||||||
yolo_mode: bool
|
yolo_mode: bool
|
||||||
model: str | None
|
model: str | None
|
||||||
|
max_concurrency: int
|
||||||
crash_count: int
|
crash_count: int
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
|
|
||||||
|
|||||||
@@ -368,10 +368,14 @@ class SchedulerService:
|
|||||||
logger.info(f"Agent already running for {project_name}, skipping scheduled start")
|
logger.info(f"Agent already running for {project_name}, skipping scheduled start")
|
||||||
return
|
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(
|
success, msg = await manager.start(
|
||||||
yolo_mode=schedule.yolo_mode,
|
yolo_mode=schedule.yolo_mode,
|
||||||
model=schedule.model,
|
model=schedule.model,
|
||||||
|
max_concurrency=schedule.max_concurrency,
|
||||||
)
|
)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect, useRef } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import { Clock, Trash2, X } from 'lucide-react'
|
import { Clock, GitBranch, Trash2, X } from 'lucide-react'
|
||||||
import {
|
import {
|
||||||
useSchedules,
|
useSchedules,
|
||||||
useCreateSchedule,
|
useCreateSchedule,
|
||||||
@@ -47,6 +47,7 @@ export function ScheduleModal({ projectName, isOpen, onClose }: ScheduleModalPro
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
yolo_mode: false,
|
yolo_mode: false,
|
||||||
model: null,
|
model: null,
|
||||||
|
max_concurrency: 3,
|
||||||
})
|
})
|
||||||
|
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
@@ -124,6 +125,7 @@ export function ScheduleModal({ projectName, isOpen, onClose }: ScheduleModalPro
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
yolo_mode: false,
|
yolo_mode: false,
|
||||||
model: null,
|
model: null,
|
||||||
|
max_concurrency: 3,
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to create schedule')
|
setError(err instanceof Error ? err.message : 'Failed to create schedule')
|
||||||
@@ -242,6 +244,10 @@ export function ScheduleModal({ projectName, isOpen, onClose }: ScheduleModalPro
|
|||||||
{schedule.yolo_mode && (
|
{schedule.yolo_mode && (
|
||||||
<span className="font-bold text-yellow-600">⚡ YOLO mode</span>
|
<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.model && <span>Model: {schedule.model}</span>}
|
||||||
{schedule.crash_count > 0 && (
|
{schedule.crash_count > 0 && (
|
||||||
<span className="text-red-600">Crashes: {schedule.crash_count}</span>
|
<span className="text-red-600">Crashes: {schedule.crash_count}</span>
|
||||||
@@ -369,6 +375,37 @@ export function ScheduleModal({ projectName, isOpen, onClose }: ScheduleModalPro
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</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) */}
|
{/* Model selection (optional) */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<label className="block text-sm font-bold text-gray-700 dark:text-gray-200 mb-2">
|
<label className="block text-sm font-bold text-gray-700 dark:text-gray-200 mb-2">
|
||||||
|
|||||||
@@ -503,6 +503,7 @@ export interface Schedule {
|
|||||||
enabled: boolean
|
enabled: boolean
|
||||||
yolo_mode: boolean
|
yolo_mode: boolean
|
||||||
model: string | null
|
model: string | null
|
||||||
|
max_concurrency: number // 1-5 concurrent agents
|
||||||
crash_count: number
|
crash_count: number
|
||||||
created_at: string
|
created_at: string
|
||||||
}
|
}
|
||||||
@@ -514,6 +515,7 @@ export interface ScheduleCreate {
|
|||||||
enabled: boolean
|
enabled: boolean
|
||||||
yolo_mode: boolean
|
yolo_mode: boolean
|
||||||
model: string | null
|
model: string | null
|
||||||
|
max_concurrency: number // 1-5 concurrent agents
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScheduleUpdate {
|
export interface ScheduleUpdate {
|
||||||
@@ -523,6 +525,7 @@ export interface ScheduleUpdate {
|
|||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
yolo_mode?: boolean
|
yolo_mode?: boolean
|
||||||
model?: string | null
|
model?: string | null
|
||||||
|
max_concurrency?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScheduleListResponse {
|
export interface ScheduleListResponse {
|
||||||
|
|||||||
Reference in New Issue
Block a user