package coordinator import ( "context" "fmt" "strings" "sync" "time" "chorus.services/bzzz/logging" "chorus.services/bzzz/pkg/config" "chorus.services/bzzz/pubsub" "chorus.services/bzzz/repository" "chorus.services/hmmm/pkg/hmmm" "github.com/google/uuid" "github.com/libp2p/go-libp2p/core/peer" ) // TaskCoordinator manages task discovery, assignment, and execution across multiple repositories type TaskCoordinator struct { pubsub *pubsub.PubSub hlog *logging.HypercoreLog ctx context.Context config *config.Config hmmmRouter *hmmm.Router // Repository management providers map[int]repository.TaskProvider // projectID -> provider providerLock sync.RWMutex factory repository.ProviderFactory // Task management activeTasks map[string]*ActiveTask // taskKey -> active task taskLock sync.RWMutex taskMatcher repository.TaskMatcher // Agent tracking nodeID string agentInfo *repository.AgentInfo // Sync settings syncInterval time.Duration lastSync map[int]time.Time syncLock sync.RWMutex } // ActiveTask represents a task currently being worked on type ActiveTask struct { Task *repository.Task Provider repository.TaskProvider ProjectID int ClaimedAt time.Time Status string // claimed, working, completed, failed AgentID string Results map[string]interface{} } // NewTaskCoordinator creates a new task coordinator func NewTaskCoordinator( ctx context.Context, ps *pubsub.PubSub, hlog *logging.HypercoreLog, cfg *config.Config, nodeID string, hmmmRouter *hmmm.Router, ) *TaskCoordinator { coordinator := &TaskCoordinator{ pubsub: ps, hlog: hlog, ctx: ctx, config: cfg, hmmmRouter: hmmmRouter, providers: make(map[int]repository.TaskProvider), activeTasks: make(map[string]*ActiveTask), lastSync: make(map[int]time.Time), factory: &repository.DefaultProviderFactory{}, taskMatcher: &repository.DefaultTaskMatcher{}, nodeID: nodeID, syncInterval: 30 * time.Second, } // Create agent info from config coordinator.agentInfo = &repository.AgentInfo{ ID: cfg.Agent.ID, Role: cfg.Agent.Role, Expertise: cfg.Agent.Expertise, CurrentTasks: 0, MaxTasks: cfg.Agent.MaxTasks, Status: "ready", LastSeen: time.Now(), Performance: 0.8, // Default performance score Availability: 1.0, } return coordinator } // Start begins the task coordination process func (tc *TaskCoordinator) Start() { fmt.Printf("🎯 Starting task coordinator for agent %s (%s)\n", tc.agentInfo.ID, tc.agentInfo.Role) // Announce role and capabilities tc.announceAgentRole() // Start periodic task discovery and sync go tc.taskDiscoveryLoop() // Start role-based message handling tc.pubsub.SetAntennaeMessageHandler(tc.handleRoleMessage) fmt.Printf("✅ Task coordinator started\n") } // taskDiscoveryLoop periodically discovers and processes tasks func (tc *TaskCoordinator) taskDiscoveryLoop() { ticker := time.NewTicker(tc.syncInterval) defer ticker.Stop() for { select { case <-tc.ctx.Done(): return case <-ticker.C: // Task discovery is now handled by WHOOSH } } } // shouldProcessTask determines if we should process a task func (tc *TaskCoordinator) shouldProcessTask(task *repository.Task) bool { // Check if we're already at capacity tc.taskLock.RLock() currentTasks := len(tc.activeTasks) tc.taskLock.RUnlock() if currentTasks >= tc.agentInfo.MaxTasks { return false } // Check if task is already assigned to us taskKey := fmt.Sprintf("%s:%d", task.Repository, task.Number) tc.taskLock.RLock() _, alreadyActive := tc.activeTasks[taskKey] tc.taskLock.RUnlock() if alreadyActive { return false } // Check minimum score threshold score := tc.taskMatcher.ScoreTaskForAgent(task, tc.agentInfo.Role, tc.agentInfo.Expertise) return score > 0.5 // Only process tasks with good fit } // processTask attempts to claim and process a task func (tc *TaskCoordinator) processTask(task *repository.Task, provider repository.TaskProvider, projectID int) bool { taskKey := fmt.Sprintf("%s:%d", task.Repository, task.Number) // Request collaboration if needed if tc.shouldRequestCollaboration(task) { tc.requestTaskCollaboration(task) } // Attempt to claim the task claimedTask, err := provider.ClaimTask(task.Number, tc.agentInfo.ID) if err != nil { fmt.Printf("⚠️ Failed to claim task %s #%d: %v\n", task.Repository, task.Number, err) return false } // Create active task activeTask := &ActiveTask{ Task: claimedTask, Provider: provider, ProjectID: projectID, ClaimedAt: time.Now(), Status: "claimed", AgentID: tc.agentInfo.ID, Results: make(map[string]interface{}), } // Store active task tc.taskLock.Lock() tc.activeTasks[taskKey] = activeTask tc.agentInfo.CurrentTasks = len(tc.activeTasks) tc.taskLock.Unlock() // Log task claim tc.hlog.Append(logging.TaskClaimed, map[string]interface{}{ "task_number": task.Number, "repository": task.Repository, "title": task.Title, "required_role": task.RequiredRole, "priority": task.Priority, }) // Announce task claim tc.announceTaskClaim(task) // Seed HMMM meta-discussion room if tc.hmmmRouter != nil { seedMsg := hmmm.Message{ Version: 1, Type: "meta_msg", IssueID: int64(task.Number), ThreadID: fmt.Sprintf("issue-%d", task.Number), MsgID: uuid.New().String(), NodeID: tc.nodeID, HopCount: 0, Timestamp: time.Now().UTC(), Message: fmt.Sprintf("Seed: Task '%s' claimed. Description: %s", task.Title, task.Description), } if err := tc.hmmmRouter.Publish(tc.ctx, seedMsg); err != nil { fmt.Printf("⚠️ Failed to seed HMMM room for task %d: %v\n", task.Number, err) tc.hlog.AppendString("system_error", map[string]interface{}{ "error": "hmmm_seed_failed", "task_number": task.Number, "repository": task.Repository, "message": err.Error(), }) } else { fmt.Printf("🐜 Seeded HMMM room for task %d\n", task.Number) } } // Start processing the task go tc.executeTask(activeTask) fmt.Printf("✅ Claimed task %s #%d: %s\n", task.Repository, task.Number, task.Title) return true } // shouldRequestCollaboration determines if we should request collaboration for a task func (tc *TaskCoordinator) shouldRequestCollaboration(task *repository.Task) bool { // Request collaboration for high-priority or complex tasks if task.Priority >= 8 { return true } // Request collaboration if task requires expertise we don't have if len(task.RequiredExpertise) > 0 { for _, required := range task.RequiredExpertise { hasExpertise := false for _, expertise := range tc.agentInfo.Expertise { if strings.EqualFold(required, expertise) { hasExpertise = true break } } if !hasExpertise { return true } } } return false } // requestTaskCollaboration requests collaboration for a task func (tc *TaskCoordinator) requestTaskCollaboration(task *repository.Task) { data := map[string]interface{}{ "task_number": task.Number, "repository": task.Repository, "title": task.Title, "required_role": task.RequiredRole, "required_expertise": task.RequiredExpertise, "priority": task.Priority, "requester_role": tc.agentInfo.Role, "reason": "expertise_gap", } opts := pubsub.MessageOptions{ FromRole: tc.agentInfo.Role, ToRoles: []string{task.RequiredRole}, RequiredExpertise: task.RequiredExpertise, Priority: "high", ThreadID: fmt.Sprintf("task-%s-%d", task.Repository, task.Number), } err := tc.pubsub.PublishRoleBasedMessage(pubsub.TaskHelpRequest, data, opts) if err != nil { fmt.Printf("⚠️ Failed to request collaboration: %v\n", err) } else { fmt.Printf("🤝 Requested collaboration for task %s #%d\n", task.Repository, task.Number) } } // executeTask executes a claimed task func (tc *TaskCoordinator) executeTask(activeTask *ActiveTask) { taskKey := fmt.Sprintf("%s:%d", activeTask.Task.Repository, activeTask.Task.Number) // Update status tc.taskLock.Lock() activeTask.Status = "working" tc.taskLock.Unlock() // Announce work start tc.announceTaskProgress(activeTask.Task, "started") // Simulate task execution (in real implementation, this would call actual execution logic) time.Sleep(10 * time.Second) // Simulate work // Complete the task results := map[string]interface{}{ "status": "completed", "completion_time": time.Now().Format(time.RFC3339), "agent_id": tc.agentInfo.ID, "agent_role": tc.agentInfo.Role, } err := activeTask.Provider.CompleteTask(activeTask.Task.Number, tc.agentInfo.ID, results) if err != nil { fmt.Printf("❌ Failed to complete task %s #%d: %v\n", activeTask.Task.Repository, activeTask.Task.Number, err) // Update status to failed tc.taskLock.Lock() activeTask.Status = "failed" activeTask.Results = map[string]interface{}{"error": err.Error()} tc.taskLock.Unlock() return } // Update status and remove from active tasks tc.taskLock.Lock() activeTask.Status = "completed" activeTask.Results = results delete(tc.activeTasks, taskKey) tc.agentInfo.CurrentTasks = len(tc.activeTasks) tc.taskLock.Unlock() // Log completion tc.hlog.Append(logging.TaskCompleted, map[string]interface{}{ "task_number": activeTask.Task.Number, "repository": activeTask.Task.Repository, "duration": time.Since(activeTask.ClaimedAt).Seconds(), "results": results, }) // Announce completion tc.announceTaskProgress(activeTask.Task, "completed") fmt.Printf("✅ Completed task %s #%d\n", activeTask.Task.Repository, activeTask.Task.Number) } // announceAgentRole announces this agent's role and capabilities func (tc *TaskCoordinator) announceAgentRole() { data := map[string]interface{}{ "agent_id": tc.agentInfo.ID, "node_id": tc.nodeID, "role": tc.agentInfo.Role, "expertise": tc.agentInfo.Expertise, "capabilities": tc.config.Agent.Capabilities, "max_tasks": tc.agentInfo.MaxTasks, "current_tasks": tc.agentInfo.CurrentTasks, "status": tc.agentInfo.Status, "specialization": tc.config.Agent.Specialization, } opts := pubsub.MessageOptions{ FromRole: tc.agentInfo.Role, Priority: "medium", } err := tc.pubsub.PublishRoleBasedMessage(pubsub.RoleAnnouncement, data, opts) if err != nil { fmt.Printf("⚠️ Failed to announce role: %v\n", err) } else { fmt.Printf("📢 Announced role: %s with expertise in %v\n", tc.agentInfo.Role, tc.agentInfo.Expertise) } } // announceTaskClaim announces that this agent has claimed a task func (tc *TaskCoordinator) announceTaskClaim(task *repository.Task) { data := map[string]interface{}{ "task_number": task.Number, "repository": task.Repository, "title": task.Title, "agent_id": tc.agentInfo.ID, "agent_role": tc.agentInfo.Role, "claim_time": time.Now().Format(time.RFC3339), "estimated_completion": time.Now().Add(time.Hour).Format(time.RFC3339), } opts := pubsub.MessageOptions{ FromRole: tc.agentInfo.Role, Priority: "medium", ThreadID: fmt.Sprintf("task-%s-%d", task.Repository, task.Number), } err := tc.pubsub.PublishRoleBasedMessage(pubsub.TaskProgress, data, opts) if err != nil { fmt.Printf("⚠️ Failed to announce task claim: %v\n", err) } } // announceTaskProgress announces task progress updates func (tc *TaskCoordinator) announceTaskProgress(task *repository.Task, status string) { data := map[string]interface{}{ "task_number": task.Number, "repository": task.Repository, "agent_id": tc.agentInfo.ID, "agent_role": tc.agentInfo.Role, "status": status, "timestamp": time.Now().Format(time.RFC3339), } opts := pubsub.MessageOptions{ FromRole: tc.agentInfo.Role, Priority: "low", ThreadID: fmt.Sprintf("task-%s-%d", task.Repository, task.Number), } err := tc.pubsub.PublishRoleBasedMessage(pubsub.TaskProgress, data, opts) if err != nil { fmt.Printf("⚠️ Failed to announce task progress: %v\n", err) } } // handleRoleMessage handles incoming role-based messages func (tc *TaskCoordinator) handleRoleMessage(msg pubsub.Message, from peer.ID) { switch msg.Type { case pubsub.TaskHelpRequest: tc.handleTaskHelpRequest(msg, from) case pubsub.ExpertiseRequest: tc.handleExpertiseRequest(msg, from) case pubsub.CoordinationRequest: tc.handleCoordinationRequest(msg, from) case pubsub.RoleAnnouncement: tc.handleRoleAnnouncement(msg, from) default: fmt.Printf("🎯 Received %s from %s: %v\n", msg.Type, from.ShortString(), msg.Data) } } // handleTaskHelpRequest handles requests for task assistance func (tc *TaskCoordinator) handleTaskHelpRequest(msg pubsub.Message, from peer.ID) { // Check if we can help with this task requiredExpertise, ok := msg.Data["required_expertise"].([]interface{}) if !ok { return } canHelp := false for _, required := range requiredExpertise { reqStr, ok := required.(string) if !ok { continue } for _, expertise := range tc.agentInfo.Expertise { if strings.EqualFold(reqStr, expertise) { canHelp = true break } } if canHelp { break } } if canHelp && tc.agentInfo.CurrentTasks < tc.agentInfo.MaxTasks { // Offer help responseData := map[string]interface{}{ "agent_id": tc.agentInfo.ID, "agent_role": tc.agentInfo.Role, "expertise": tc.agentInfo.Expertise, "availability": tc.agentInfo.MaxTasks - tc.agentInfo.CurrentTasks, "offer_type": "collaboration", "response_to": msg.Data, } opts := pubsub.MessageOptions{ FromRole: tc.agentInfo.Role, Priority: "medium", ThreadID: msg.ThreadID, } err := tc.pubsub.PublishRoleBasedMessage(pubsub.TaskHelpResponse, responseData, opts) if err != nil { fmt.Printf("⚠️ Failed to offer help: %v\n", err) } else { fmt.Printf("🤝 Offered help for task collaboration\n") } // Also reflect the help offer into the HMMM per-issue room (best-effort) if tc.hmmmRouter != nil { if tn, ok := msg.Data["task_number"].(float64); ok { issueID := int64(tn) hmsg := hmmm.Message{ Version: 1, Type: "meta_msg", IssueID: issueID, ThreadID: fmt.Sprintf("issue-%d", issueID), MsgID: uuid.New().String(), NodeID: tc.nodeID, HopCount: 0, Timestamp: time.Now().UTC(), Message: fmt.Sprintf("Help offer from %s (availability %d)", tc.agentInfo.Role, tc.agentInfo.MaxTasks-tc.agentInfo.CurrentTasks), } if err := tc.hmmmRouter.Publish(tc.ctx, hmsg); err != nil { fmt.Printf("⚠️ Failed to reflect help into HMMM: %v\n", err) } } } } } // handleExpertiseRequest handles requests for specific expertise func (tc *TaskCoordinator) handleExpertiseRequest(msg pubsub.Message, from peer.ID) { // Similar to task help request but more focused on expertise fmt.Printf("🎯 Expertise request from %s: %v\n", from.ShortString(), msg.Data) } // handleCoordinationRequest handles coordination requests func (tc *TaskCoordinator) handleCoordinationRequest(msg pubsub.Message, from peer.ID) { fmt.Printf("🎯 Coordination request from %s: %v\n", from.ShortString(), msg.Data) } // handleRoleAnnouncement handles role announcements from other agents func (tc *TaskCoordinator) handleRoleAnnouncement(msg pubsub.Message, from peer.ID) { role, _ := msg.Data["role"].(string) expertise, _ := msg.Data["expertise"].([]interface{}) fmt.Printf("📢 Agent %s announced role: %s with expertise: %v\n", from.ShortString(), role, expertise) } // GetStatus returns current coordinator status func (tc *TaskCoordinator) GetStatus() map[string]interface{} { tc.taskLock.RLock() activeTasks := len(tc.activeTasks) taskList := make([]map[string]interface{}, 0, len(tc.activeTasks)) for _, task := range tc.activeTasks { taskList = append(taskList, map[string]interface{}{ "repository": task.Task.Repository, "number": task.Task.Number, "title": task.Task.Title, "status": task.Status, "claimed_at": task.ClaimedAt.Format(time.RFC3339), }) } tc.taskLock.RUnlock() tc.providerLock.RLock() providers := len(tc.providers) tc.providerLock.RUnlock() return map[string]interface{}{ "agent_id": tc.agentInfo.ID, "role": tc.agentInfo.Role, "expertise": tc.agentInfo.Expertise, "current_tasks": activeTasks, "max_tasks": tc.agentInfo.MaxTasks, "active_providers": providers, "status": tc.agentInfo.Status, "active_tasks": taskList, } }