package council import ( "context" "encoding/json" "fmt" "strings" "time" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" "github.com/rs/zerolog/log" "go.opentelemetry.io/otel/attribute" "github.com/chorus-services/whoosh/internal/tracing" ) // CouncilComposer manages the formation and orchestration of project kickoff councils type CouncilComposer struct { db *pgxpool.Pool ctx context.Context cancel context.CancelFunc } // NewCouncilComposer creates a new council composer service func NewCouncilComposer(db *pgxpool.Pool) *CouncilComposer { ctx, cancel := context.WithCancel(context.Background()) return &CouncilComposer{ db: db, ctx: ctx, cancel: cancel, } } // Close shuts down the council composer func (cc *CouncilComposer) Close() error { cc.cancel() return nil } // FormCouncil creates a council composition for a project kickoff func (cc *CouncilComposer) FormCouncil(ctx context.Context, request *CouncilFormationRequest) (*CouncilComposition, error) { ctx, span := tracing.StartCouncilSpan(ctx, "form_council", "") defer span.End() startTime := time.Now() councilID := uuid.New() // Add tracing attributes span.SetAttributes( attribute.String("council.id", councilID.String()), attribute.String("project.name", request.ProjectName), attribute.String("repository.name", request.Repository), attribute.String("project.brief", request.ProjectBrief), ) // Add goal.id and pulse.id if available in the request if request.GoalID != "" { span.SetAttributes(attribute.String("goal.id", request.GoalID)) } if request.PulseID != "" { span.SetAttributes(attribute.String("pulse.id", request.PulseID)) } log.Info(). Str("council_id", councilID.String()). Str("project_name", request.ProjectName). Str("repository", request.Repository). Msg("🎭 Forming project kickoff council") // Create core council agents (always required) coreAgents := make([]CouncilAgent, len(CoreCouncilRoles)) for i, roleName := range CoreCouncilRoles { agentID := fmt.Sprintf("council-%s-%s", strings.ReplaceAll(request.ProjectName, " ", "-"), roleName) coreAgents[i] = CouncilAgent{ AgentID: agentID, RoleName: roleName, AgentName: cc.formatRoleName(roleName), Required: true, Deployed: false, Status: "pending", } } // Determine optional agents based on project characteristics optionalAgents := cc.selectOptionalAgents(request) // Create council composition composition := &CouncilComposition{ CouncilID: councilID, ProjectName: request.ProjectName, CoreAgents: coreAgents, OptionalAgents: optionalAgents, CreatedAt: startTime, Status: "forming", } // Store council composition in database err := cc.storeCouncilComposition(ctx, composition, request) if err != nil { tracing.SetSpanError(span, err) span.SetAttributes(attribute.String("council.formation.status", "failed")) return nil, fmt.Errorf("failed to store council composition: %w", err) } // Add success metrics to span span.SetAttributes( attribute.Int("council.core_agents.count", len(coreAgents)), attribute.Int("council.optional_agents.count", len(optionalAgents)), attribute.Int64("council.formation.duration_ms", time.Since(startTime).Milliseconds()), attribute.String("council.formation.status", "completed"), ) log.Info(). Str("council_id", councilID.String()). Int("core_agents", len(coreAgents)). Int("optional_agents", len(optionalAgents)). Dur("formation_time", time.Since(startTime)). Msg("✅ Council composition formed") return composition, nil } // selectOptionalAgents determines which optional council agents should be included func (cc *CouncilComposer) selectOptionalAgents(request *CouncilFormationRequest) []CouncilAgent { var selectedAgents []CouncilAgent // Analyze project brief and characteristics to determine needed optional roles brief := strings.ToLower(request.ProjectBrief) // Data/AI projects if strings.Contains(brief, "ai") || strings.Contains(brief, "machine learning") || strings.Contains(brief, "data") || strings.Contains(brief, "analytics") { selectedAgents = append(selectedAgents, cc.createOptionalAgent("data-ai-architect", request.ProjectName)) } // Privacy/compliance sensitive projects if strings.Contains(brief, "privacy") || strings.Contains(brief, "personal data") || strings.Contains(brief, "gdpr") || strings.Contains(brief, "compliance") { selectedAgents = append(selectedAgents, cc.createOptionalAgent("privacy-data-governance-officer", request.ProjectName)) } // Regulated industries if strings.Contains(brief, "healthcare") || strings.Contains(brief, "finance") || strings.Contains(brief, "banking") || strings.Contains(brief, "regulated") { selectedAgents = append(selectedAgents, cc.createOptionalAgent("compliance-legal-liaison", request.ProjectName)) } // Performance-critical systems if strings.Contains(brief, "performance") || strings.Contains(brief, "high-load") || strings.Contains(brief, "scale") || strings.Contains(brief, "benchmark") { selectedAgents = append(selectedAgents, cc.createOptionalAgent("performance-benchmarking-analyst", request.ProjectName)) } // User-facing applications if strings.Contains(brief, "user interface") || strings.Contains(brief, "ui") || strings.Contains(brief, "ux") || strings.Contains(brief, "frontend") { selectedAgents = append(selectedAgents, cc.createOptionalAgent("ui-ux-designer", request.ProjectName)) } // Mobile applications if strings.Contains(brief, "mobile") || strings.Contains(brief, "ios") || strings.Contains(brief, "android") || strings.Contains(brief, "app store") { selectedAgents = append(selectedAgents, cc.createOptionalAgent("ios-macos-developer", request.ProjectName)) } // Games or graphics-intensive applications if strings.Contains(brief, "game") || strings.Contains(brief, "graphics") || strings.Contains(brief, "rendering") || strings.Contains(brief, "3d") { selectedAgents = append(selectedAgents, cc.createOptionalAgent("engine-programmer", request.ProjectName)) } // Integration-heavy projects if strings.Contains(brief, "integration") || strings.Contains(brief, "api") || strings.Contains(brief, "microservice") || strings.Contains(brief, "third-party") { selectedAgents = append(selectedAgents, cc.createOptionalAgent("integration-architect", request.ProjectName)) } // Cost-sensitive or enterprise projects if strings.Contains(brief, "budget") || strings.Contains(brief, "cost") || strings.Contains(brief, "enterprise") || strings.Contains(brief, "licensing") { selectedAgents = append(selectedAgents, cc.createOptionalAgent("cost-licensing-steward", request.ProjectName)) } return selectedAgents } // createOptionalAgent creates an optional council agent func (cc *CouncilComposer) createOptionalAgent(roleName, projectName string) CouncilAgent { agentID := fmt.Sprintf("council-%s-%s", strings.ReplaceAll(projectName, " ", "-"), roleName) return CouncilAgent{ AgentID: agentID, RoleName: roleName, AgentName: cc.formatRoleName(roleName), Required: false, Deployed: false, Status: "pending", } } // formatRoleName converts role key to human-readable name func (cc *CouncilComposer) formatRoleName(roleName string) string { // Convert kebab-case to Title Case parts := strings.Split(roleName, "-") for i, part := range parts { parts[i] = strings.Title(part) } return strings.Join(parts, " ") } // storeCouncilComposition stores the council composition in the database func (cc *CouncilComposer) storeCouncilComposition(ctx context.Context, composition *CouncilComposition, request *CouncilFormationRequest) error { // Store council metadata councilQuery := ` INSERT INTO councils (id, project_name, repository, project_brief, status, created_at, task_id, issue_id, external_url, metadata) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ` metadataJSON, _ := json.Marshal(request.Metadata) _, err := cc.db.Exec(ctx, councilQuery, composition.CouncilID, composition.ProjectName, request.Repository, request.ProjectBrief, composition.Status, composition.CreatedAt, request.TaskID, request.IssueID, request.ExternalURL, metadataJSON, ) if err != nil { return fmt.Errorf("failed to store council metadata: %w", err) } // Store council agents for _, agent := range composition.CoreAgents { err = cc.storeCouncilAgent(ctx, composition.CouncilID, agent) if err != nil { return fmt.Errorf("failed to store core agent %s: %w", agent.AgentID, err) } } for _, agent := range composition.OptionalAgents { err = cc.storeCouncilAgent(ctx, composition.CouncilID, agent) if err != nil { return fmt.Errorf("failed to store optional agent %s: %w", agent.AgentID, err) } } return nil } // storeCouncilAgent stores a single council agent in the database func (cc *CouncilComposer) storeCouncilAgent(ctx context.Context, councilID uuid.UUID, agent CouncilAgent) error { query := ` INSERT INTO council_agents (council_id, agent_id, role_name, agent_name, required, deployed, status, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW()) ` _, err := cc.db.Exec(ctx, query, councilID, agent.AgentID, agent.RoleName, agent.AgentName, agent.Required, agent.Deployed, agent.Status, ) return err } // GetCouncilComposition retrieves a council composition by ID func (cc *CouncilComposer) GetCouncilComposition(ctx context.Context, councilID uuid.UUID) (*CouncilComposition, error) { // First, get the council metadata councilQuery := ` SELECT id, project_name, status, created_at FROM councils WHERE id = $1 ` var composition CouncilComposition var status string var createdAt time.Time err := cc.db.QueryRow(ctx, councilQuery, councilID).Scan( &composition.CouncilID, &composition.ProjectName, &status, &createdAt, ) if err != nil { return nil, fmt.Errorf("failed to query council: %w", err) } composition.Status = status composition.CreatedAt = createdAt // Get all agents for this council agentQuery := ` SELECT agent_id, role_name, agent_name, required, deployed, status, deployed_at FROM council_agents WHERE council_id = $1 ORDER BY required DESC, role_name ASC ` rows, err := cc.db.Query(ctx, agentQuery, councilID) if err != nil { return nil, fmt.Errorf("failed to query council agents: %w", err) } defer rows.Close() // Separate core and optional agents var coreAgents []CouncilAgent var optionalAgents []CouncilAgent for rows.Next() { var agent CouncilAgent var deployedAt *time.Time err := rows.Scan( &agent.AgentID, &agent.RoleName, &agent.AgentName, &agent.Required, &agent.Deployed, &agent.Status, &deployedAt, ) if err != nil { return nil, fmt.Errorf("failed to scan agent row: %w", err) } agent.DeployedAt = deployedAt if agent.Required { coreAgents = append(coreAgents, agent) } else { optionalAgents = append(optionalAgents, agent) } } if err = rows.Err(); err != nil { return nil, fmt.Errorf("error iterating agent rows: %w", err) } composition.CoreAgents = coreAgents composition.OptionalAgents = optionalAgents log.Info(). Str("council_id", councilID.String()). Str("project_name", composition.ProjectName). Int("core_agents", len(coreAgents)). Int("optional_agents", len(optionalAgents)). Msg("Retrieved council composition") return &composition, nil } // UpdateCouncilStatus updates the status of a council func (cc *CouncilComposer) UpdateCouncilStatus(ctx context.Context, councilID uuid.UUID, status string) error { query := `UPDATE councils SET status = $1, updated_at = NOW() WHERE id = $2` _, err := cc.db.Exec(ctx, query, status, councilID) return err }