Add subtasks
Change-Id: Ica6afd9eef38bcf29135bf2c8a2e4bf0407ccfa1
diff --git a/server/agent/manager.go b/server/agent/manager.go
index 74e6771..3d8277f 100644
--- a/server/agent/manager.go
+++ b/server/agent/manager.go
@@ -15,19 +15,21 @@
"github.com/iomodo/staff/git"
"github.com/iomodo/staff/llm"
_ "github.com/iomodo/staff/llm/providers" // Auto-register all providers
+ "github.com/iomodo/staff/subtasks"
"github.com/iomodo/staff/tm"
)
// Manager manages multiple AI agents with Git operations and task processing
type Manager struct {
- config *config.Config
- agents map[string]*Agent
- taskManager tm.TaskManager
- autoAssigner *assignment.AutoAssigner
- prProvider git.PullRequestProvider
- cloneManager *git.CloneManager
- isRunning map[string]bool
- stopChannels map[string]chan struct{}
+ config *config.Config
+ agents map[string]*Agent
+ taskManager tm.TaskManager
+ autoAssigner *assignment.AutoAssigner
+ prProvider git.PullRequestProvider
+ cloneManager *git.CloneManager
+ subtaskService *subtasks.SubtaskService
+ isRunning map[string]bool
+ stopChannels map[string]chan struct{}
}
// NewManager creates a new agent manager
@@ -62,6 +64,11 @@
return nil, fmt.Errorf("failed to initialize agents: %w", err)
}
+ // Initialize subtask service after agents are created
+ if err := manager.initializeSubtaskService(); err != nil {
+ return nil, fmt.Errorf("failed to initialize subtask service: %w", err)
+ }
+
return manager, nil
}
@@ -77,6 +84,34 @@
return nil
}
+// initializeSubtaskService creates the subtask service with available agent roles
+func (m *Manager) initializeSubtaskService() error {
+ // Get agent roles from configuration
+ agentRoles := make([]string, 0, len(m.config.Agents))
+ for _, agentConfig := range m.config.Agents {
+ agentRoles = append(agentRoles, agentConfig.Name)
+ }
+
+ // Use the first agent's LLM provider for subtask analysis
+ if len(m.agents) == 0 {
+ return fmt.Errorf("no agents available for subtask service")
+ }
+
+ var firstAgent *Agent
+ for _, agent := range m.agents {
+ firstAgent = agent
+ break
+ }
+
+ m.subtaskService = subtasks.NewSubtaskService(
+ firstAgent.Provider,
+ m.taskManager,
+ agentRoles,
+ )
+
+ return nil
+}
+
// createAgent creates a single agent instance
func (m *Manager) createAgent(agentConfig config.AgentConfig) (*Agent, error) {
// Load system prompt
@@ -226,6 +261,29 @@
return fmt.Errorf("failed to update task status: %w", err)
}
+ // Check if this task should generate subtasks
+ if m.shouldGenerateSubtasks(task) {
+ log.Printf("Analyzing task %s for subtask generation", task.ID)
+ if err := m.generateSubtasksForTask(ctx, task); err != nil {
+ log.Printf("Warning: Failed to generate subtasks for task %s: %v", task.ID, err)
+ // Continue with normal processing if subtask generation fails
+ } else {
+ // Task has been converted to subtask management, mark as completed
+ task.Status = tm.StatusCompleted
+ task.Solution = "Task broken down into subtasks. See subtasks PR for details."
+ completedAt := time.Now()
+ task.CompletedAt = &completedAt
+ agent.CurrentTask = nil
+
+ if err := m.taskManager.UpdateTask(task); err != nil {
+ return fmt.Errorf("failed to update task with subtasks: %w", err)
+ }
+
+ log.Printf("Task %s converted to subtasks by agent %s", task.ID, agent.Name)
+ return nil
+ }
+ }
+
// Generate solution using LLM
solution, err := m.generateSolution(ctx, agent, task)
if err != nil {
@@ -553,6 +611,54 @@
return status
}
+// shouldGenerateSubtasks determines if a task should be broken down into subtasks
+func (m *Manager) shouldGenerateSubtasks(task *tm.Task) bool {
+ // Don't generate subtasks for subtasks
+ if task.ParentTaskID != "" {
+ return false
+ }
+
+ // Don't generate if already generated
+ if task.SubtasksGenerated {
+ return false
+ }
+
+ // Only generate for high priority tasks or complex descriptions
+ if task.Priority == tm.PriorityHigh || len(task.Description) > 200 {
+ return true
+ }
+
+ return false
+}
+
+// generateSubtasksForTask analyzes a task and creates a PR with proposed subtasks
+func (m *Manager) generateSubtasksForTask(ctx context.Context, task *tm.Task) error {
+ if m.subtaskService == nil {
+ return fmt.Errorf("subtask service not initialized")
+ }
+
+ // Analyze the task for subtasks
+ analysis, err := m.subtaskService.AnalyzeTaskForSubtasks(ctx, task)
+ if err != nil {
+ return fmt.Errorf("failed to analyze task for subtasks: %w", err)
+ }
+
+ // Generate a PR with the subtask proposals
+ prURL, err := m.subtaskService.GenerateSubtaskPR(ctx, analysis)
+ if err != nil {
+ return fmt.Errorf("failed to generate subtask PR: %w", err)
+ }
+
+ // Update the task with subtask information
+ task.SubtasksPRURL = prURL
+ task.SubtasksGenerated = true
+
+ log.Printf("Generated subtask PR for task %s: %s", task.ID, prURL)
+ log.Printf("Proposed %d subtasks for task %s", len(analysis.Subtasks), task.ID)
+
+ return nil
+}
+
// IsAgentRunning checks if an agent is currently running
func (m *Manager) IsAgentRunning(agentName string) bool {
return m.isRunning[agentName]
@@ -579,5 +685,12 @@
log.Printf("Error cleaning up agent clones: %v", err)
}
+ // Cleanup subtask service
+ if m.subtaskService != nil {
+ if err := m.subtaskService.Close(); err != nil {
+ log.Printf("Error closing subtask service: %v", err)
+ }
+ }
+
return nil
}