package coordinator import ( "context" "fmt" "strings" "sync" "time" "chorus/internal/logging" "chorus/pkg/ai" "chorus/pkg/config" "chorus/pkg/execution" "chorus/pkg/hmmm" "chorus/pkg/repository" "chorus/pubsub" "github.com/google/uuid" "github.com/libp2p/go-libp2p/core/peer" ) // TaskProgressTracker is notified when tasks start and complete so availability broadcasts stay accurate. type TaskProgressTracker interface { AddTask(taskID string) RemoveTask(taskID string) } // 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 taskTracker TaskProgressTracker // Task execution executionEngine execution.TaskExecutionEngine // 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, tracker TaskProgressTracker, ) *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{}, taskTracker: tracker, 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: map[string]interface{}{"score": 0.8}, // Default performance score Availability: "available", } 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) // Initialize task execution engine err := tc.initializeExecutionEngine() if err != nil { fmt.Printf("⚠️ Failed to initialize task execution engine: %v\n", err) fmt.Println("Task execution will fall back to mock implementation") } // 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) 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 claimed, err := provider.ClaimTask(task.Number, tc.agentInfo.ID) if err != nil || !claimed { fmt.Printf("⚠️ Failed to claim task %s #%d: %v\n", task.Repository, task.Number, err) return false } // Create active task activeTask := &ActiveTask{ Task: task, 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() if tc.taskTracker != nil { tc.taskTracker.AddTask(taskKey) } // 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.Body), } 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) } } // initializeExecutionEngine sets up the AI-powered task execution engine func (tc *TaskCoordinator) initializeExecutionEngine() error { // Create AI provider factory aiFactory := ai.NewProviderFactory() // Load AI configuration from config file configPath := "configs/models.yaml" configLoader := ai.NewConfigLoader(configPath, "production") _, err := configLoader.LoadConfig() if err != nil { return fmt.Errorf("failed to load AI config: %w", err) } // Initialize the factory with the loaded configuration // For now, we'll use a simplified initialization // In a complete implementation, the factory would have an Initialize method // Create task execution engine tc.executionEngine = execution.NewTaskExecutionEngine() // Configure execution engine engineConfig := &execution.EngineConfig{ AIProviderFactory: aiFactory, DefaultTimeout: 5 * time.Minute, MaxConcurrentTasks: tc.agentInfo.MaxTasks, EnableMetrics: true, LogLevel: "info", SandboxDefaults: &execution.SandboxConfig{ Type: "docker", Image: "alpine:latest", Architecture: "amd64", Resources: execution.ResourceLimits{ MemoryLimit: 512 * 1024 * 1024, // 512MB CPULimit: 1.0, ProcessLimit: 50, FileLimit: 1024, }, Security: execution.SecurityPolicy{ ReadOnlyRoot: false, NoNewPrivileges: true, AllowNetworking: true, IsolateNetwork: false, IsolateProcess: true, DropCapabilities: []string{"NET_ADMIN", "SYS_ADMIN"}, }, WorkingDir: "/workspace", Timeout: 5 * time.Minute, }, } err = tc.executionEngine.Initialize(tc.ctx, engineConfig) if err != nil { return fmt.Errorf("failed to initialize execution engine: %w", err) } fmt.Printf("✅ Task execution engine initialized successfully\n") return nil } // 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") // Execute task using AI-powered execution engine var taskResult *repository.TaskResult if tc.executionEngine != nil { // Use real AI-powered execution executionResult, err := tc.executeTaskWithAI(activeTask) if err != nil { fmt.Printf("⚠️ AI execution failed for task %s #%d: %v\n", activeTask.Task.Repository, activeTask.Task.Number, err) // Fall back to mock execution taskResult = tc.executeMockTask(activeTask) } else { // Convert execution result to task result taskResult = tc.convertExecutionResult(activeTask, executionResult) } } else { // Fall back to mock execution fmt.Printf("📝 Using mock execution for task %s #%d (engine not available)\n", activeTask.Task.Repository, activeTask.Task.Number) taskResult = tc.executeMockTask(activeTask) } err := activeTask.Provider.CompleteTask(activeTask.Task, taskResult) 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 = taskResult.Metadata delete(tc.activeTasks, taskKey) tc.agentInfo.CurrentTasks = len(tc.activeTasks) tc.taskLock.Unlock() if tc.taskTracker != nil { tc.taskTracker.RemoveTask(taskKey) } // 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": taskResult.Metadata, }) // Announce completion tc.announceTaskProgress(activeTask.Task, "completed") fmt.Printf("✅ Completed task %s #%d\n", activeTask.Task.Repository, activeTask.Task.Number) } // executeTaskWithAI executes a task using the AI-powered execution engine func (tc *TaskCoordinator) executeTaskWithAI(activeTask *ActiveTask) (*execution.TaskExecutionResult, error) { // Convert repository task to execution request executionRequest := &execution.TaskExecutionRequest{ ID: fmt.Sprintf("%s:%d", activeTask.Task.Repository, activeTask.Task.Number), Type: tc.determineTaskType(activeTask.Task), Description: tc.buildTaskDescription(activeTask.Task), Context: tc.buildTaskContext(activeTask.Task), Requirements: &execution.TaskRequirements{ AIModel: "", // Let the engine choose based on role SandboxType: "docker", RequiredTools: []string{"git", "curl"}, EnvironmentVars: map[string]string{ "TASK_ID": fmt.Sprintf("%d", activeTask.Task.Number), "REPOSITORY": activeTask.Task.Repository, "AGENT_ID": tc.agentInfo.ID, "AGENT_ROLE": tc.agentInfo.Role, }, }, Timeout: 10 * time.Minute, // Allow longer timeout for complex tasks } // Execute the task return tc.executionEngine.ExecuteTask(tc.ctx, executionRequest) } // executeMockTask provides fallback mock execution func (tc *TaskCoordinator) executeMockTask(activeTask *ActiveTask) *repository.TaskResult { // Simulate work time based on task complexity workTime := 5 * time.Second if strings.Contains(strings.ToLower(activeTask.Task.Title), "complex") { workTime = 15 * time.Second } fmt.Printf("🕐 Mock execution for task %s #%d (simulating %v)\n", activeTask.Task.Repository, activeTask.Task.Number, workTime) time.Sleep(workTime) results := map[string]interface{}{ "status": "completed", "execution_type": "mock", "completion_time": time.Now().Format(time.RFC3339), "agent_id": tc.agentInfo.ID, "agent_role": tc.agentInfo.Role, "simulated_work": workTime.String(), } return &repository.TaskResult{ Success: true, Message: "Task completed successfully (mock execution)", Metadata: results, } } // convertExecutionResult converts an execution result to a task result func (tc *TaskCoordinator) convertExecutionResult(activeTask *ActiveTask, result *execution.TaskExecutionResult) *repository.TaskResult { // Build result metadata metadata := map[string]interface{}{ "status": "completed", "execution_type": "ai_powered", "completion_time": time.Now().Format(time.RFC3339), "agent_id": tc.agentInfo.ID, "agent_role": tc.agentInfo.Role, "task_id": result.TaskID, "duration": result.Metrics.Duration.String(), "ai_provider_time": result.Metrics.AIProviderTime.String(), "sandbox_time": result.Metrics.SandboxTime.String(), "commands_executed": result.Metrics.CommandsExecuted, "files_generated": result.Metrics.FilesGenerated, } // Add execution metadata if available if result.Metadata != nil { metadata["ai_metadata"] = result.Metadata } // Add resource usage if available if result.Metrics.ResourceUsage != nil { metadata["resource_usage"] = map[string]interface{}{ "cpu_usage": result.Metrics.ResourceUsage.CPUUsage, "memory_usage": result.Metrics.ResourceUsage.MemoryUsage, "memory_percent": result.Metrics.ResourceUsage.MemoryPercent, } } // Handle artifacts if len(result.Artifacts) > 0 { artifactsList := make([]map[string]interface{}, len(result.Artifacts)) for i, artifact := range result.Artifacts { artifactsList[i] = map[string]interface{}{ "name": artifact.Name, "type": artifact.Type, "size": artifact.Size, "created_at": artifact.CreatedAt.Format(time.RFC3339), } } metadata["artifacts"] = artifactsList } // Determine success based on execution result success := result.Success message := "Task completed successfully with AI execution" if !success { message = fmt.Sprintf("Task failed: %s", result.ErrorMessage) } return &repository.TaskResult{ Success: success, Message: message, Metadata: metadata, } } // determineTaskType analyzes a task to determine its execution type func (tc *TaskCoordinator) determineTaskType(task *repository.Task) string { title := strings.ToLower(task.Title) description := strings.ToLower(task.Body) // Check for common task type keywords if strings.Contains(title, "bug") || strings.Contains(title, "fix") { return "bug_fix" } if strings.Contains(title, "feature") || strings.Contains(title, "implement") { return "feature_development" } if strings.Contains(title, "test") || strings.Contains(description, "test") { return "testing" } if strings.Contains(title, "doc") || strings.Contains(description, "documentation") { return "documentation" } if strings.Contains(title, "refactor") || strings.Contains(description, "refactor") { return "refactoring" } if strings.Contains(title, "review") || strings.Contains(description, "review") { return "code_review" } // Default to general development task return "development" } // buildTaskDescription creates a comprehensive description for AI execution func (tc *TaskCoordinator) buildTaskDescription(task *repository.Task) string { var description strings.Builder description.WriteString(fmt.Sprintf("Task: %s\n\n", task.Title)) if task.Body != "" { description.WriteString(fmt.Sprintf("Description:\n%s\n\n", task.Body)) } description.WriteString(fmt.Sprintf("Repository: %s\n", task.Repository)) description.WriteString(fmt.Sprintf("Task Number: %d\n", task.Number)) if len(task.RequiredExpertise) > 0 { description.WriteString(fmt.Sprintf("Required Expertise: %v\n", task.RequiredExpertise)) } if len(task.Labels) > 0 { description.WriteString(fmt.Sprintf("Labels: %v\n", task.Labels)) } description.WriteString("\nPlease analyze this task and provide appropriate commands or code to complete it.") return description.String() } // buildTaskContext creates context information for AI execution func (tc *TaskCoordinator) buildTaskContext(task *repository.Task) map[string]interface{} { context := map[string]interface{}{ "repository": task.Repository, "task_number": task.Number, "task_title": task.Title, "required_role": task.RequiredRole, "required_expertise": task.RequiredExpertise, "labels": task.Labels, "agent_info": map[string]interface{}{ "id": tc.agentInfo.ID, "role": tc.agentInfo.Role, "expertise": tc.agentInfo.Expertise, }, } // Add any additional metadata from the task if task.Metadata != nil { context["task_metadata"] = task.Metadata } return context } // 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, } }