- Agent roles and coordination features - Chat API integration testing - New configuration and workspace management 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
334 lines
9.7 KiB
Go
334 lines
9.7 KiB
Go
package repository
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// DefaultTaskMatcher implements TaskMatcher interface
|
|
type DefaultTaskMatcher struct{}
|
|
|
|
// MatchTasksToRole filters tasks suitable for a specific role and expertise
|
|
func (m *DefaultTaskMatcher) MatchTasksToRole(tasks []*Task, role string, expertise []string) ([]*Task, error) {
|
|
var matchedTasks []*Task
|
|
|
|
for _, task := range tasks {
|
|
score := m.ScoreTaskForAgent(task, role, expertise)
|
|
if score > 0.3 { // Minimum threshold for task suitability
|
|
matchedTasks = append(matchedTasks, task)
|
|
}
|
|
}
|
|
|
|
// Sort by score (highest first)
|
|
sort.Slice(matchedTasks, func(i, j int) bool {
|
|
scoreI := m.ScoreTaskForAgent(matchedTasks[i], role, expertise)
|
|
scoreJ := m.ScoreTaskForAgent(matchedTasks[j], role, expertise)
|
|
return scoreI > scoreJ
|
|
})
|
|
|
|
return matchedTasks, nil
|
|
}
|
|
|
|
// ScoreTaskForAgent calculates how suitable a task is for an agent based on role and expertise
|
|
func (m *DefaultTaskMatcher) ScoreTaskForAgent(task *Task, agentRole string, agentExpertise []string) float64 {
|
|
score := 0.0
|
|
|
|
// Base score for role matching
|
|
if task.RequiredRole != "" {
|
|
if task.RequiredRole == agentRole {
|
|
score += 0.5 // Perfect role match
|
|
} else if m.isCompatibleRole(task.RequiredRole, agentRole) {
|
|
score += 0.3 // Compatible role
|
|
}
|
|
} else {
|
|
// No specific role required, default bonus for general roles
|
|
if m.isGeneralRole(agentRole) {
|
|
score += 0.2
|
|
}
|
|
}
|
|
|
|
// Expertise matching
|
|
expertiseScore := m.calculateExpertiseScore(task.RequiredExpertise, agentExpertise)
|
|
score += expertiseScore * 0.4
|
|
|
|
// Priority bonus (higher priority tasks get small bonus)
|
|
priorityBonus := float64(task.Priority) / 10.0 * 0.1
|
|
score += priorityBonus
|
|
|
|
// Task type bonuses based on agent role
|
|
taskTypeScore := m.calculateTaskTypeScore(task, agentRole)
|
|
score += taskTypeScore * 0.1
|
|
|
|
// Label-based scoring
|
|
labelScore := m.calculateLabelScore(task.Labels, agentRole, agentExpertise)
|
|
score += labelScore * 0.1
|
|
|
|
// Ensure score is between 0 and 1
|
|
if score > 1.0 {
|
|
score = 1.0
|
|
}
|
|
if score < 0.0 {
|
|
score = 0.0
|
|
}
|
|
|
|
return score
|
|
}
|
|
|
|
// GetRecommendedAgents returns agents best suited for a task
|
|
func (m *DefaultTaskMatcher) GetRecommendedAgents(task *Task, availableAgents []AgentInfo) ([]AgentInfo, error) {
|
|
type agentScore struct {
|
|
agent AgentInfo
|
|
score float64
|
|
}
|
|
|
|
var scoredAgents []agentScore
|
|
|
|
for _, agent := range availableAgents {
|
|
// Skip agents that are offline or at capacity
|
|
if agent.Status != "online" && agent.Status != "ready" {
|
|
continue
|
|
}
|
|
if agent.CurrentTasks >= agent.MaxTasks {
|
|
continue
|
|
}
|
|
|
|
// Calculate base task suitability score
|
|
taskScore := m.ScoreTaskForAgent(task, agent.Role, agent.Expertise)
|
|
|
|
// Apply agent-specific modifiers
|
|
finalScore := taskScore
|
|
|
|
// Performance modifier (0.5 to 1.5 multiplier)
|
|
performanceMod := 0.5 + agent.Performance
|
|
finalScore *= performanceMod
|
|
|
|
// Availability modifier
|
|
availabilityMod := agent.Availability
|
|
finalScore *= availabilityMod
|
|
|
|
// Workload penalty (agents with fewer current tasks get slight bonus)
|
|
workloadRatio := float64(agent.CurrentTasks) / float64(agent.MaxTasks)
|
|
workloadBonus := (1.0 - workloadRatio) * 0.1
|
|
finalScore += workloadBonus
|
|
|
|
// Recent activity bonus (agents seen recently get small bonus)
|
|
timeSinceLastSeen := time.Since(agent.LastSeen)
|
|
if timeSinceLastSeen < 5*time.Minute {
|
|
finalScore += 0.05
|
|
}
|
|
|
|
if finalScore > 0.1 { // Minimum threshold
|
|
scoredAgents = append(scoredAgents, agentScore{agent: agent, score: finalScore})
|
|
}
|
|
}
|
|
|
|
// Sort by score (highest first)
|
|
sort.Slice(scoredAgents, func(i, j int) bool {
|
|
return scoredAgents[i].score > scoredAgents[j].score
|
|
})
|
|
|
|
// Return top agents (up to 5)
|
|
maxAgents := 5
|
|
if len(scoredAgents) < maxAgents {
|
|
maxAgents = len(scoredAgents)
|
|
}
|
|
|
|
result := make([]AgentInfo, maxAgents)
|
|
for i := 0; i < maxAgents; i++ {
|
|
result[i] = scoredAgents[i].agent
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// calculateExpertiseScore computes overlap between required and agent expertise
|
|
func (m *DefaultTaskMatcher) calculateExpertiseScore(requiredExpertise, agentExpertise []string) float64 {
|
|
if len(requiredExpertise) == 0 {
|
|
return 0.5 // No specific expertise required
|
|
}
|
|
|
|
matches := 0
|
|
for _, required := range requiredExpertise {
|
|
for _, agent := range agentExpertise {
|
|
if strings.EqualFold(required, agent) || m.isRelatedExpertise(required, agent) {
|
|
matches++
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Score based on percentage of required expertise covered
|
|
score := float64(matches) / float64(len(requiredExpertise))
|
|
|
|
// Bonus for having additional relevant expertise
|
|
if len(agentExpertise) > len(requiredExpertise) {
|
|
bonus := 0.1 * float64(len(agentExpertise)-len(requiredExpertise)) / float64(len(agentExpertise))
|
|
score += bonus
|
|
}
|
|
|
|
return score
|
|
}
|
|
|
|
// calculateTaskTypeScore gives bonuses based on task type and agent role compatibility
|
|
func (m *DefaultTaskMatcher) calculateTaskTypeScore(task *Task, agentRole string) float64 {
|
|
taskType := strings.ToLower(task.TaskType)
|
|
role := strings.ToLower(agentRole)
|
|
|
|
switch taskType {
|
|
case "bug_fix", "bug":
|
|
if strings.Contains(role, "qa") || strings.Contains(role, "test") {
|
|
return 0.8
|
|
}
|
|
if strings.Contains(role, "backend") || strings.Contains(role, "full_stack") {
|
|
return 0.6
|
|
}
|
|
case "feature", "enhancement":
|
|
if strings.Contains(role, "developer") || strings.Contains(role, "engineer") {
|
|
return 0.7
|
|
}
|
|
case "documentation", "docs":
|
|
if strings.Contains(role, "writer") || strings.Contains(role, "documentation") {
|
|
return 0.9
|
|
}
|
|
case "security":
|
|
if strings.Contains(role, "security") {
|
|
return 0.9
|
|
}
|
|
case "design":
|
|
if strings.Contains(role, "designer") || strings.Contains(role, "ux") {
|
|
return 0.9
|
|
}
|
|
case "infrastructure", "devops":
|
|
if strings.Contains(role, "devops") || strings.Contains(role, "systems") {
|
|
return 0.9
|
|
}
|
|
}
|
|
|
|
return 0.0
|
|
}
|
|
|
|
// calculateLabelScore analyzes task labels for additional role matching
|
|
func (m *DefaultTaskMatcher) calculateLabelScore(labels []string, agentRole string, agentExpertise []string) float64 {
|
|
score := 0.0
|
|
role := strings.ToLower(agentRole)
|
|
|
|
for _, label := range labels {
|
|
label = strings.ToLower(label)
|
|
|
|
// Direct role matches
|
|
if strings.Contains(role, label) || strings.Contains(label, role) {
|
|
score += 0.3
|
|
}
|
|
|
|
// Expertise matches
|
|
for _, expertise := range agentExpertise {
|
|
if strings.Contains(strings.ToLower(expertise), label) || strings.Contains(label, strings.ToLower(expertise)) {
|
|
score += 0.2
|
|
}
|
|
}
|
|
|
|
// Specific label bonuses
|
|
switch label {
|
|
case "frontend":
|
|
if strings.Contains(role, "frontend") || strings.Contains(role, "ui") {
|
|
score += 0.4
|
|
}
|
|
case "backend":
|
|
if strings.Contains(role, "backend") || strings.Contains(role, "api") {
|
|
score += 0.4
|
|
}
|
|
case "urgent", "critical":
|
|
score += 0.1 // Small bonus for urgent tasks
|
|
case "good first issue", "beginner":
|
|
if strings.Contains(role, "junior") {
|
|
score += 0.3
|
|
}
|
|
}
|
|
}
|
|
|
|
return score
|
|
}
|
|
|
|
// isCompatibleRole checks if two roles are compatible
|
|
func (m *DefaultTaskMatcher) isCompatibleRole(requiredRole, agentRole string) bool {
|
|
compatibilityMap := map[string][]string{
|
|
"frontend_developer": {"full_stack_engineer", "ui_ux_designer"},
|
|
"backend_developer": {"full_stack_engineer", "database_engineer"},
|
|
"full_stack_engineer": {"frontend_developer", "backend_developer"},
|
|
"qa_engineer": {"full_stack_engineer", "backend_developer"},
|
|
"devops_engineer": {"systems_engineer", "backend_developer"},
|
|
"ui_ux_designer": {"frontend_developer", "lead_designer"},
|
|
"security_expert": {"backend_developer", "senior_software_architect"},
|
|
"technical_writer": {"full_stack_engineer"},
|
|
"database_engineer": {"backend_developer", "full_stack_engineer"},
|
|
"senior_software_architect": {"full_stack_engineer", "backend_developer", "frontend_developer"},
|
|
}
|
|
|
|
compatibleRoles, exists := compatibilityMap[requiredRole]
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
for _, compatible := range compatibleRoles {
|
|
if compatible == agentRole {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// isGeneralRole checks if a role is considered general-purpose
|
|
func (m *DefaultTaskMatcher) isGeneralRole(role string) bool {
|
|
generalRoles := []string{
|
|
"full_stack_engineer",
|
|
"senior_software_architect",
|
|
"general_developer",
|
|
}
|
|
|
|
for _, general := range generalRoles {
|
|
if general == role {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// isRelatedExpertise checks if two expertise areas are related
|
|
func (m *DefaultTaskMatcher) isRelatedExpertise(required, agent string) bool {
|
|
relatedMap := map[string][]string{
|
|
"frontend": {"javascript", "typescript", "react", "vue", "angular", "css", "html"},
|
|
"backend": {"api_development", "server_frameworks", "databases", "microservices"},
|
|
"database": {"sql", "nosql", "data_modeling", "query_optimization"},
|
|
"security": {"cybersecurity", "owasp", "vulnerability_analysis", "penetration_testing"},
|
|
"devops": {"deployment", "infrastructure", "docker", "kubernetes", "cicd"},
|
|
"testing": {"qa_methodologies", "test_automation", "debugging"},
|
|
"design": {"ui_ux", "user_experience", "prototyping", "design_systems"},
|
|
"documentation": {"technical_writing", "api_documentation"},
|
|
}
|
|
|
|
required = strings.ToLower(required)
|
|
agent = strings.ToLower(agent)
|
|
|
|
// Check direct related expertise
|
|
if related, exists := relatedMap[required]; exists {
|
|
for _, rel := range related {
|
|
if strings.Contains(agent, rel) || strings.Contains(rel, agent) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check reverse mapping
|
|
if related, exists := relatedMap[agent]; exists {
|
|
for _, rel := range related {
|
|
if strings.Contains(required, rel) || strings.Contains(rel, required) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
} |