blob: 4bd56320f90eced283a1f7f9e3acdc628f776252 [file] [log] [blame]
package task
import (
"fmt"
"sort"
"strings"
"github.com/iomodo/staff/config"
"github.com/iomodo/staff/tm"
)
// AutoAssigner handles intelligent task assignment to agents
type AutoAssigner struct {
agents []config.AgentConfig
}
// NewAutoAssigner creates a new auto-assignment system
func NewAutoAssigner(agents []config.AgentConfig) *AutoAssigner {
return &AutoAssigner{
agents: agents,
}
}
// AssignmentScore represents how well an agent matches a task
type AssignmentScore struct {
AgentName string
Score float64
Reasons []string
}
// AssignTask automatically assigns a task to the best matching agent
func (a *AutoAssigner) AssignTask(task *tm.Task) (string, error) {
if len(a.agents) == 0 {
return "", fmt.Errorf("no agents available for assignment")
}
scores := a.calculateScores(task)
if len(scores) == 0 {
return "", fmt.Errorf("no suitable agent found for task")
}
// Sort by score (highest first)
sort.Slice(scores, func(i, j int) bool {
return scores[i].Score > scores[j].Score
})
bestMatch := scores[0]
if bestMatch.Score == 0 {
// No good match, assign to CEO as fallback
return "ceo", nil
}
return bestMatch.AgentName, nil
}
// GetAssignmentRecommendations returns ranked recommendations for task assignment
func (a *AutoAssigner) GetAssignmentRecommendations(task *tm.Task) []AssignmentScore {
scores := a.calculateScores(task)
// Sort by score (highest first)
sort.Slice(scores, func(i, j int) bool {
return scores[i].Score > scores[j].Score
})
return scores
}
// calculateScores calculates assignment scores for all agents
func (a *AutoAssigner) calculateScores(task *tm.Task) []AssignmentScore {
scores := make([]AssignmentScore, 0, len(a.agents))
taskText := strings.ToLower(task.Title + " " + task.Description)
taskKeywords := extractKeywords(taskText)
for _, agent := range a.agents {
score := &AssignmentScore{
AgentName: agent.Name,
Score: 0,
Reasons: make([]string, 0),
}
// Score based on task type keywords
score.Score += a.scoreTaskTypes(agent.TaskTypes, taskKeywords, score)
// Score based on capabilities
score.Score += a.scoreCapabilities(agent.Capabilities, taskKeywords, score)
// Score based on task priority and agent model
score.Score += a.scorePriorityModelMatch(task.Priority, agent.Model, score)
// Score based on explicit agent mention in task
score.Score += a.scoreExplicitMention(agent.Name, agent.Role, taskText, score)
scores = append(scores, *score)
}
return scores
}
// scoreTaskTypes scores based on task type matching
func (a *AutoAssigner) scoreTaskTypes(agentTypes []string, taskKeywords []string, score *AssignmentScore) float64 {
typeScore := 0.0
for _, agentType := range agentTypes {
for _, keyword := range taskKeywords {
if strings.Contains(keyword, agentType) || strings.Contains(agentType, keyword) {
typeScore += 3.0
score.Reasons = append(score.Reasons, fmt.Sprintf("matches task type: %s", agentType))
}
}
}
return typeScore
}
// scoreCapabilities scores based on capability matching
func (a *AutoAssigner) scoreCapabilities(capabilities []string, taskKeywords []string, score *AssignmentScore) float64 {
capScore := 0.0
for _, capability := range capabilities {
for _, keyword := range taskKeywords {
if strings.Contains(keyword, capability) || strings.Contains(capability, keyword) {
capScore += 2.0
score.Reasons = append(score.Reasons, fmt.Sprintf("has capability: %s", capability))
}
}
}
return capScore
}
// scorePriorityModelMatch scores based on priority and model sophistication
func (a *AutoAssigner) scorePriorityModelMatch(priority tm.TaskPriority, model string, score *AssignmentScore) float64 {
priorityScore := 0.0
// High priority tasks prefer more capable models
if priority == tm.PriorityHigh && strings.Contains(model, "gpt-4") {
priorityScore += 1.0
score.Reasons = append(score.Reasons, "high priority task matches advanced model")
}
// Medium/low priority can use efficient models
if priority != tm.PriorityHigh && strings.Contains(model, "gpt-3.5") {
priorityScore += 0.5
score.Reasons = append(score.Reasons, "priority matches model efficiency")
}
return priorityScore
}
// scoreExplicitMention scores based on explicit agent/role mentions
func (a *AutoAssigner) scoreExplicitMention(agentName, agentRole, taskText string, score *AssignmentScore) float64 {
mentionScore := 0.0
// Check for explicit agent name mention
if strings.Contains(taskText, agentName) {
mentionScore += 5.0
score.Reasons = append(score.Reasons, "explicitly mentioned by name")
}
// Check for role mention
roleLower := strings.ToLower(agentRole)
if strings.Contains(taskText, roleLower) {
mentionScore += 4.0
score.Reasons = append(score.Reasons, "role mentioned in task")
}
return mentionScore
}
// extractKeywords extracts relevant keywords from task text
func extractKeywords(text string) []string {
// Simple keyword extraction - split by common delimiters
words := strings.FieldsFunc(text, func(c rune) bool {
return c == ' ' || c == ',' || c == '.' || c == ':' || c == ';' || c == '\n' || c == '\t'
})
// Filter out common stop words and short words
stopWords := map[string]bool{
"the": true, "a": true, "an": true, "and": true, "or": true, "but": true,
"in": true, "on": true, "at": true, "to": true, "for": true, "of": true,
"with": true, "by": true, "is": true, "are": true, "was": true, "were": true,
"be": true, "been": true, "have": true, "has": true, "had": true, "do": true,
"does": true, "did": true, "will": true, "would": true, "could": true, "should": true,
}
keywords := make([]string, 0)
for _, word := range words {
word = strings.ToLower(strings.TrimSpace(word))
if len(word) > 2 && !stopWords[word] {
keywords = append(keywords, word)
}
}
return keywords
}
// ValidateAssignment checks if an assignment is valid
func (a *AutoAssigner) ValidateAssignment(agentName string, task *tm.Task) error {
// Check if agent exists
for _, agent := range a.agents {
if agent.Name == agentName {
return nil
}
}
return fmt.Errorf("agent '%s' not found", agentName)
}
// GetAgentCapabilities returns the capabilities of a specific agent
func (a *AutoAssigner) GetAgentCapabilities(agentName string) ([]string, error) {
for _, agent := range a.agents {
if agent.Name == agentName {
return agent.Capabilities, nil
}
}
return nil, fmt.Errorf("agent '%s' not found", agentName)
}
// GetRecommendationExplanation returns a human-readable explanation for assignment
func (a *AutoAssigner) GetRecommendationExplanation(task *tm.Task, agentName string) string {
recommendations := a.GetAssignmentRecommendations(task)
for _, rec := range recommendations {
if rec.AgentName == agentName {
if len(rec.Reasons) == 0 {
return fmt.Sprintf("Agent %s assigned (score: %.1f)", agentName, rec.Score)
}
reasons := strings.Join(rec.Reasons, ", ")
return fmt.Sprintf("Agent %s assigned (score: %.1f) because: %s", agentName, rec.Score, reasons)
}
}
return fmt.Sprintf("Agent %s assigned (manual override)", agentName)
}