Add subtask generation decision
Change-Id: If43efa882de08bc262fe6117af7307e97c4dfeda
diff --git a/server/subtasks/service.go b/server/subtasks/service.go
index ad397ff..710dfaf 100644
--- a/server/subtasks/service.go
+++ b/server/subtasks/service.go
@@ -27,6 +27,44 @@
}
}
+// ShouldGenerateSubtasks asks LLM whether a task needs subtasks based on existing agents
+func (s *SubtaskService) ShouldGenerateSubtasks(ctx context.Context, task *tm.Task) (*tm.SubtaskDecision, error) {
+ prompt := s.buildSubtaskDecisionPrompt(task)
+
+ req := llm.ChatCompletionRequest{
+ Model: "gpt-4",
+ Messages: []llm.Message{
+ {
+ Role: llm.RoleSystem,
+ Content: s.getSubtaskDecisionSystemPrompt(),
+ },
+ {
+ Role: llm.RoleUser,
+ Content: prompt,
+ },
+ },
+ MaxTokens: &[]int{1000}[0],
+ Temperature: &[]float64{0.3}[0],
+ }
+
+ resp, err := s.llmProvider.ChatCompletion(ctx, req)
+ if err != nil {
+ return nil, fmt.Errorf("LLM decision failed: %w", err)
+ }
+
+ if len(resp.Choices) == 0 {
+ return nil, fmt.Errorf("no response from LLM")
+ }
+
+ // Parse the LLM response
+ decision, err := s.parseSubtaskDecision(resp.Choices[0].Message.Content)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse LLM decision: %w", err)
+ }
+
+ return decision, nil
+}
+
// AnalyzeTaskForSubtasks uses LLM to analyze a task and propose subtasks
func (s *SubtaskService) AnalyzeTaskForSubtasks(ctx context.Context, task *tm.Task) (*tm.SubtaskAnalysis, error) {
prompt := s.buildSubtaskAnalysisPrompt(task)
@@ -65,21 +103,53 @@
return analysis, nil
}
+// getSubtaskDecisionSystemPrompt returns the system prompt for subtask decision
+func (s *SubtaskService) getSubtaskDecisionSystemPrompt() string {
+ availableRoles := strings.Join(s.agentRoles, ", ")
+
+ return fmt.Sprintf(`You are an expert project manager and task analyst. Your job is to determine whether a task needs to be broken down into subtasks.
+
+Currently available team roles and their capabilities: %s
+
+When evaluating a task, consider:
+1. Task complexity and scope
+2. Whether multiple specialized skills are needed
+3. If the task can be completed by a single agent with current capabilities
+4. Whether new agent roles might be needed for specialized skills
+
+Respond with a JSON object in this exact format:
+{
+ "needs_subtasks": true/false,
+ "reasoning": "Clear explanation of why subtasks are or aren't needed",
+ "complexity_score": 5,
+ "required_skills": ["skill1", "skill2", "skill3"]
+}
+
+Complexity score should be 1-10 where:
+- 1-3: Simple tasks that can be handled by one agent
+- 4-6: Moderate complexity, might benefit from subtasks
+- 7-10: Complex tasks that definitely need breaking down
+
+Required skills should list all technical/domain skills needed to complete the task.`, availableRoles)
+}
+
// getSubtaskAnalysisSystemPrompt returns the system prompt for subtask analysis
func (s *SubtaskService) getSubtaskAnalysisSystemPrompt() string {
availableRoles := strings.Join(s.agentRoles, ", ")
return fmt.Sprintf(`You are an expert project manager and technical architect. Your job is to analyze complex tasks and break them down into well-defined subtasks that can be assigned to specialized team members.
-Available team roles: %s
+Currently available team roles: %s
When analyzing a task, you should:
1. Understand the task requirements and scope
2. Break it down into logical, manageable subtasks
-3. Assign each subtask to the most appropriate team role
+3. Assign each subtask to the most appropriate team role OR propose creating new agents
4. Estimate effort and identify dependencies
5. Provide a clear execution strategy
+If you need specialized skills not covered by existing roles, propose new agent creation.
+
Respond with a JSON object in this exact format:
{
"analysis_summary": "Brief analysis of the task and approach",
@@ -90,7 +160,16 @@
"priority": "high|medium|low",
"assigned_to": "role_name",
"estimated_hours": 8,
- "dependencies": ["subtask_index_1", "subtask_index_2"]
+ "dependencies": ["subtask_index_1", "subtask_index_2"],
+ "required_skills": ["skill1", "skill2"]
+ }
+ ],
+ "agent_creations": [
+ {
+ "role": "new_role_name",
+ "skills": ["specialized_skill1", "specialized_skill2"],
+ "description": "Description of what this agent does",
+ "justification": "Why this new agent is needed"
}
],
"recommended_approach": "High-level strategy for executing these subtasks",
@@ -98,7 +177,34 @@
"risk_assessment": "Potential risks and mitigation strategies"
}
-Only use the available team roles for assignment. Dependencies should reference subtask indices (e.g., ["0", "1"] means depends on first and second subtasks).`, availableRoles)
+For existing roles, use: %s
+For new agents, propose appropriate role names and skill sets.
+Dependencies should reference subtask indices (e.g., ["0", "1"] means depends on first and second subtasks).`, availableRoles, availableRoles)
+}
+
+// buildSubtaskDecisionPrompt creates the user prompt for subtask decision
+func (s *SubtaskService) buildSubtaskDecisionPrompt(task *tm.Task) string {
+ return fmt.Sprintf(`Please evaluate whether the following task needs to be broken down into subtasks:
+
+**Task Title:** %s
+
+**Description:** %s
+
+**Priority:** %s
+
+**Current Status:** %s
+
+Consider:
+- Can this be completed by a single agent with existing capabilities?
+- Does it require multiple specialized skills?
+- Is the scope too large for one person?
+- Are there logical components that could be parallelized?
+
+Provide your decision in the JSON format specified in the system prompt.`,
+ task.Title,
+ task.Description,
+ task.Priority,
+ task.Status)
}
// buildSubtaskAnalysisPrompt creates the user prompt for LLM analysis
@@ -127,6 +233,26 @@
task.Status)
}
+// parseSubtaskDecision parses the LLM response into a SubtaskDecision struct
+func (s *SubtaskService) parseSubtaskDecision(response string) (*tm.SubtaskDecision, error) {
+ // Try to extract JSON from the response
+ jsonStart := strings.Index(response, "{")
+ jsonEnd := strings.LastIndex(response, "}")
+
+ if jsonStart == -1 || jsonEnd == -1 {
+ return nil, fmt.Errorf("no JSON found in LLM response")
+ }
+
+ jsonStr := response[jsonStart : jsonEnd+1]
+
+ var decision tm.SubtaskDecision
+ if err := json.Unmarshal([]byte(jsonStr), &decision); err != nil {
+ return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
+ }
+
+ return &decision, nil
+}
+
// parseSubtaskAnalysis parses the LLM response into a SubtaskAnalysis struct
func (s *SubtaskService) parseSubtaskAnalysis(response string, parentTaskID string) (*tm.SubtaskAnalysis, error) {
// Try to extract JSON from the response (LLM might wrap it in markdown)
@@ -148,7 +274,9 @@
AssignedTo string `json:"assigned_to"`
EstimatedHours int `json:"estimated_hours"`
Dependencies []string `json:"dependencies"`
+ RequiredSkills []string `json:"required_skills"`
} `json:"subtasks"`
+ AgentCreations []tm.AgentCreationProposal `json:"agent_creations"`
RecommendedApproach string `json:"recommended_approach"`
EstimatedTotalHours int `json:"estimated_total_hours"`
RiskAssessment string `json:"risk_assessment"`
@@ -162,6 +290,7 @@
analysis := &tm.SubtaskAnalysis{
ParentTaskID: parentTaskID,
AnalysisSummary: rawAnalysis.AnalysisSummary,
+ AgentCreations: rawAnalysis.AgentCreations,
RecommendedApproach: rawAnalysis.RecommendedApproach,
EstimatedTotalHours: rawAnalysis.EstimatedTotalHours,
RiskAssessment: rawAnalysis.RiskAssessment,
@@ -184,43 +313,81 @@
AssignedTo: st.AssignedTo,
EstimatedHours: st.EstimatedHours,
Dependencies: st.Dependencies,
+ RequiredSkills: st.RequiredSkills,
}
analysis.Subtasks = append(analysis.Subtasks, subtask)
}
- // Validate agent assignments
- if err := s.validateAgentAssignments(analysis); err != nil {
- log.Printf("Warning: Invalid agent assignments: %v", err)
- // Fix assignments by using first available role
- s.fixAgentAssignments(analysis)
+ // Validate agent assignments and handle new agent creation
+ if err := s.validateAndHandleAgentAssignments(analysis); err != nil {
+ log.Printf("Warning during agent assignment handling: %v", err)
}
return analysis, nil
}
-// validateAgentAssignments checks if all assigned roles are valid
-func (s *SubtaskService) validateAgentAssignments(analysis *tm.SubtaskAnalysis) error {
- for i, subtask := range analysis.Subtasks {
- if !s.isValidAgentRole(subtask.AssignedTo) {
- return fmt.Errorf("subtask %d has invalid agent role: %s", i, subtask.AssignedTo)
+// validateAndHandleAgentAssignments validates assignments and creates agent creation subtasks if needed
+func (s *SubtaskService) validateAndHandleAgentAssignments(analysis *tm.SubtaskAnalysis) error {
+ // Collect all agent roles that will be available (existing + proposed new ones)
+ availableRoles := make(map[string]bool)
+ for _, role := range s.agentRoles {
+ availableRoles[role] = true
+ }
+
+ // Add proposed new agent roles
+ for _, agentCreation := range analysis.AgentCreations {
+ availableRoles[agentCreation.Role] = true
+
+ // Create a subtask for agent creation
+ agentCreationSubtask := tm.SubtaskProposal{
+ Title: fmt.Sprintf("Create %s Agent", strings.Title(agentCreation.Role)),
+ Description: fmt.Sprintf("Create and configure a new %s agent with skills: %s. %s", agentCreation.Role, strings.Join(agentCreation.Skills, ", "), agentCreation.Justification),
+ Priority: tm.PriorityHigh, // Agent creation is high priority
+ AssignedTo: "ceo", // CEO creates new agents
+ EstimatedHours: 4, // Estimated time to set up new agent
+ Dependencies: []string{}, // No dependencies for agent creation
+ RequiredSkills: []string{"agent_configuration", "system_design"},
+ }
+
+ // Insert at the beginning so agent creation happens first
+ analysis.Subtasks = append([]tm.SubtaskProposal{agentCreationSubtask}, analysis.Subtasks...)
+
+ // Update dependencies to account for the new subtask at index 0
+ for i := 1; i < len(analysis.Subtasks); i++ {
+ for j, dep := range analysis.Subtasks[i].Dependencies {
+ // Convert dependency index and increment by 1
+ if depIndex := s.parseDependencyIndex(dep); depIndex >= 0 {
+ analysis.Subtasks[i].Dependencies[j] = fmt.Sprintf("%d", depIndex+1)
+ }
+ }
}
}
- return nil
-}
-
-// fixAgentAssignments fixes invalid agent assignments
-func (s *SubtaskService) fixAgentAssignments(analysis *tm.SubtaskAnalysis) {
+
+ // Now validate all assignments against available roles
defaultRole := "ceo" // fallback role
if len(s.agentRoles) > 0 {
defaultRole = s.agentRoles[0]
}
for i := range analysis.Subtasks {
- if !s.isValidAgentRole(analysis.Subtasks[i].AssignedTo) {
+ if !availableRoles[analysis.Subtasks[i].AssignedTo] {
+ log.Printf("Warning: Unknown agent role '%s' for subtask '%s', assigning to %s",
+ analysis.Subtasks[i].AssignedTo, analysis.Subtasks[i].Title, defaultRole)
analysis.Subtasks[i].AssignedTo = defaultRole
}
}
+
+ return nil
+}
+
+// parseDependencyIndex parses a dependency string to an integer index
+func (s *SubtaskService) parseDependencyIndex(dep string) int {
+ var idx int
+ if _, err := fmt.Sscanf(dep, "%d", &idx); err == nil {
+ return idx
+ }
+ return -1 // Invalid dependency format
}
// isValidAgentRole checks if a role is in the available agent roles