mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-02-02 15:23:37 +00:00
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:
@@ -112,12 +112,25 @@ export function AgentCard({ agent, onShowLogs }: AgentCardProps) {
|
||||
|
||||
{/* Feature info */}
|
||||
<div>
|
||||
<div className="text-xs text-muted-foreground mb-0.5">
|
||||
Feature #{agent.featureId}
|
||||
</div>
|
||||
<div className="text-sm font-medium truncate" title={agent.featureName}>
|
||||
{agent.featureName}
|
||||
</div>
|
||||
{agent.featureIds && agent.featureIds.length > 1 ? (
|
||||
<>
|
||||
<div className="text-xs text-muted-foreground mb-0.5">
|
||||
Batch: {agent.featureIds.map(id => `#${id}`).join(', ')}
|
||||
</div>
|
||||
<div className="text-sm font-bold truncate">
|
||||
Active: Feature #{agent.featureId}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-xs text-muted-foreground mb-0.5">
|
||||
Feature #{agent.featureId}
|
||||
</div>
|
||||
<div className="text-sm font-medium truncate" title={agent.featureName}>
|
||||
{agent.featureName}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Thought bubble */}
|
||||
@@ -195,7 +208,10 @@ export function AgentLogModal({ agent, logs, onClose }: AgentLogModalProps) {
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Feature #{agent.featureId}: {agent.featureName}
|
||||
{agent.featureIds && agent.featureIds.length > 1
|
||||
? `Batch: ${agent.featureIds.map(id => `#${id}`).join(', ')}`
|
||||
: `Feature #${agent.featureId}: ${agent.featureName}`
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -227,10 +227,14 @@ function DependencyGraphInner({ graphData, onNodeClick, activeAgents = [] }: Dep
|
||||
}, [])
|
||||
|
||||
// Create a map of featureId to agent info for quick lookup
|
||||
// Maps ALL batch feature IDs to the same agent
|
||||
const agentByFeatureId = useMemo(() => {
|
||||
const map = new Map<number, NodeAgentInfo>()
|
||||
for (const agent of activeAgents) {
|
||||
map.set(agent.featureId, { name: agent.agentName, state: agent.state })
|
||||
const ids = agent.featureIds || [agent.featureId]
|
||||
for (const fid of ids) {
|
||||
map.set(fid, { name: agent.agentName, state: agent.state })
|
||||
}
|
||||
}
|
||||
return map
|
||||
}, [activeAgents])
|
||||
|
||||
@@ -41,9 +41,14 @@ export function KanbanColumn({
|
||||
showCreateSpec,
|
||||
}: KanbanColumnProps) {
|
||||
// Create a map of feature ID to active agent for quick lookup
|
||||
const agentByFeatureId = new Map(
|
||||
activeAgents.map(agent => [agent.featureId, agent])
|
||||
)
|
||||
// Maps ALL batch feature IDs to the same agent
|
||||
const agentByFeatureId = new Map<number, ActiveAgent>()
|
||||
for (const agent of activeAgents) {
|
||||
const ids = agent.featureIds || [agent.featureId]
|
||||
for (const fid of ids) {
|
||||
agentByFeatureId.set(fid, agent)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className={`overflow-hidden ${colorMap[color]} py-0`}>
|
||||
|
||||
@@ -41,6 +41,12 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
|
||||
}
|
||||
}
|
||||
|
||||
const handleBatchSizeChange = (size: number) => {
|
||||
if (!updateSettings.isPending) {
|
||||
updateSettings.mutate({ batch_size: size })
|
||||
}
|
||||
}
|
||||
|
||||
const models = modelsData?.models ?? []
|
||||
const isSaving = updateSettings.isPending
|
||||
|
||||
@@ -234,6 +240,30 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Features per Agent */}
|
||||
<div className="space-y-2">
|
||||
<Label className="font-medium">Features per Agent</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Number of features assigned to each coding agent
|
||||
</p>
|
||||
<div className="flex rounded-lg border overflow-hidden">
|
||||
{[1, 2, 3].map((size) => (
|
||||
<button
|
||||
key={size}
|
||||
onClick={() => handleBatchSizeChange(size)}
|
||||
disabled={isSaving}
|
||||
className={`flex-1 py-2 px-3 text-sm font-medium transition-colors ${
|
||||
(settings.batch_size ?? 1) === size
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'bg-background text-foreground hover:bg-muted'
|
||||
} ${isSaving ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
{size}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Update Error */}
|
||||
{updateSettings.isError && (
|
||||
<Alert variant="destructive">
|
||||
|
||||
@@ -267,6 +267,7 @@ const DEFAULT_SETTINGS: Settings = {
|
||||
ollama_mode: false,
|
||||
testing_agent_ratio: 1,
|
||||
playwright_headless: true,
|
||||
batch_size: 3,
|
||||
}
|
||||
|
||||
export function useAvailableModels() {
|
||||
|
||||
@@ -210,6 +210,7 @@ export function useProjectWebSocket(projectName: string | null) {
|
||||
agentName: message.agentName,
|
||||
agentType: message.agentType || 'coding', // Default to coding for backwards compat
|
||||
featureId: message.featureId,
|
||||
featureIds: message.featureIds || [message.featureId],
|
||||
featureName: message.featureName,
|
||||
state: message.state,
|
||||
thought: message.thought,
|
||||
@@ -225,6 +226,7 @@ export function useProjectWebSocket(projectName: string | null) {
|
||||
agentName: message.agentName,
|
||||
agentType: message.agentType || 'coding', // Default to coding for backwards compat
|
||||
featureId: message.featureId,
|
||||
featureIds: message.featureIds || [message.featureId],
|
||||
featureName: message.featureName,
|
||||
state: message.state,
|
||||
thought: message.thought,
|
||||
|
||||
@@ -199,7 +199,8 @@ export interface ActiveAgent {
|
||||
agentIndex: number // -1 for synthetic completions
|
||||
agentName: AgentMascot | 'Unknown'
|
||||
agentType: AgentType // "coding" or "testing"
|
||||
featureId: number
|
||||
featureId: number // Current/primary feature (backward compat)
|
||||
featureIds: number[] // All features in batch
|
||||
featureName: string
|
||||
state: AgentState
|
||||
thought?: string
|
||||
@@ -270,6 +271,7 @@ export interface WSAgentUpdateMessage {
|
||||
agentName: AgentMascot | 'Unknown'
|
||||
agentType: AgentType // "coding" or "testing"
|
||||
featureId: number
|
||||
featureIds?: number[] // All features in batch (may be absent for backward compat)
|
||||
featureName: string
|
||||
state: AgentState
|
||||
thought?: string
|
||||
@@ -530,6 +532,7 @@ 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)
|
||||
}
|
||||
|
||||
export interface SettingsUpdate {
|
||||
@@ -537,6 +540,7 @@ export interface SettingsUpdate {
|
||||
model?: string
|
||||
testing_agent_ratio?: number
|
||||
playwright_headless?: boolean
|
||||
batch_size?: number
|
||||
}
|
||||
|
||||
export interface ProjectSettingsUpdate {
|
||||
|
||||
Reference in New Issue
Block a user