feat: Implement council brief processing loop for task execution

Add processBriefs() polling loop that checks for assigned council briefs
and executes them using the ExecutionEngine infrastructure.

Changes:
- Add GetCurrentAssignment() public method to council.Manager
- Make HTTPServer.CouncilManager public for brief access
- Add processBriefs() 15-second polling loop in agent_support.go
- Add executeBrief() to initialize and run ExecutionEngine
- Add buildExecutionRequest() to convert briefs to execution requests
- Add uploadResults() to send completed work to WHOOSH
- Wire processBriefs() into StartAgentMode() as background goroutine

This addresses the root cause of task execution not happening: briefs
were being stored but never polled or executed. The execution
infrastructure (ExecutionEngine, AI providers, prompt system) was
complete but not connected to the council workflow.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-10-11 12:15:49 +11:00
parent 7381137db5
commit f7130b327c
3 changed files with 500 additions and 9 deletions

View File

@@ -1,12 +1,18 @@
package runtime
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"chorus/internal/council"
"chorus/internal/logging"
"chorus/pkg/ai"
"chorus/pkg/dht"
"chorus/pkg/execution"
"chorus/pkg/health"
"chorus/pkg/shutdown"
"chorus/pubsub"
@@ -39,6 +45,10 @@ func (r *SharedRuntime) StartAgentMode() error {
// Start status reporting
go r.statusReporter()
// Start council brief processing
ctx := context.Background()
go r.processBriefs(ctx)
r.Logger.Info("🔍 Listening for peers on container network...")
r.Logger.Info("📡 Ready for task coordination and meta-discussion")
r.Logger.Info("🎯 HMMM collaborative reasoning enabled")
@@ -321,3 +331,185 @@ func (r *SharedRuntime) setupGracefulShutdown(shutdownManager *shutdown.Manager,
r.Logger.Info("🛡️ Graceful shutdown components registered")
}
// processBriefs polls for council briefs and executes them
func (r *SharedRuntime) processBriefs(ctx context.Context) {
ticker := time.NewTicker(15 * time.Second)
defer ticker.Stop()
r.Logger.Info("📦 Brief processing loop started")
for {
select {
case <-ctx.Done():
r.Logger.Info("📦 Brief processing loop stopped")
return
case <-ticker.C:
if r.HTTPServer == nil || r.HTTPServer.CouncilManager == nil {
continue
}
assignment := r.HTTPServer.CouncilManager.GetCurrentAssignment()
if assignment == nil || assignment.Brief == nil {
continue
}
// Check if we have a brief to execute
brief := assignment.Brief
if brief.BriefURL == "" && brief.Summary == "" {
continue
}
r.Logger.Info("📦 Processing design brief for council %s, role %s", assignment.CouncilID, assignment.RoleName)
// Execute the brief
if err := r.executeBrief(ctx, assignment); err != nil {
r.Logger.Error("❌ Failed to execute brief: %v", err)
continue
}
r.Logger.Info("✅ Brief execution completed for council %s", assignment.CouncilID)
// Clear the brief after execution to prevent re-execution
assignment.Brief = nil
}
}
}
// executeBrief executes a council brief using the ExecutionEngine
func (r *SharedRuntime) executeBrief(ctx context.Context, assignment *council.RoleAssignment) error {
brief := assignment.Brief
if brief == nil {
return fmt.Errorf("no brief to execute")
}
// Create execution engine
engine := execution.NewTaskExecutionEngine()
// Create AI provider factory
aiFactory := ai.NewProviderFactory()
engineConfig := &execution.EngineConfig{
AIProviderFactory: aiFactory,
MaxConcurrentTasks: 1,
DefaultTimeout: time.Hour,
EnableMetrics: true,
LogLevel: "info",
}
if err := engine.Initialize(ctx, engineConfig); err != nil {
return fmt.Errorf("failed to initialize execution engine: %w", err)
}
defer engine.Shutdown()
// Build execution request
request := r.buildExecutionRequest(assignment)
r.Logger.Info("🚀 Executing brief for council %s, role %s", assignment.CouncilID, assignment.RoleName)
// Track task
taskID := fmt.Sprintf("council-%s-%s", assignment.CouncilID, assignment.RoleName)
r.TaskTracker.AddTask(taskID)
defer r.TaskTracker.RemoveTask(taskID)
// Execute the task
result, err := engine.ExecuteTask(ctx, request)
if err != nil {
return fmt.Errorf("task execution failed: %w", err)
}
r.Logger.Info("✅ Task execution successful. Output: %s", result.Output)
// Upload results to WHOOSH
if err := r.uploadResults(assignment, result); err != nil {
r.Logger.Error("⚠️ Failed to upload results to WHOOSH: %v", err)
// Don't fail the execution if upload fails
}
return nil
}
// buildExecutionRequest converts a council brief to an execution request
func (r *SharedRuntime) buildExecutionRequest(assignment *council.RoleAssignment) *execution.TaskExecutionRequest {
brief := assignment.Brief
// Build task description from brief
taskDescription := brief.Summary
if taskDescription == "" {
taskDescription = "Execute council brief"
}
// Add additional context
additionalContext := map[string]interface{}{
"council_id": assignment.CouncilID,
"role_name": assignment.RoleName,
"brief_url": brief.BriefURL,
"expected_artifacts": brief.ExpectedArtifacts,
"hmmm_topic": brief.HMMMTopic,
"persona": assignment.Persona,
}
return &execution.TaskExecutionRequest{
ID: fmt.Sprintf("council-%s-%s", assignment.CouncilID, assignment.RoleName),
Type: "council_brief",
Description: taskDescription,
Context: additionalContext,
Requirements: &execution.TaskRequirements{
AIModel: r.Config.AI.Provider,
SandboxType: "docker",
RequiredTools: []string{},
},
Timeout: time.Hour,
}
}
// uploadResults uploads execution results to WHOOSH
func (r *SharedRuntime) uploadResults(assignment *council.RoleAssignment, result *execution.TaskExecutionResult) error {
// Get WHOOSH endpoint from environment or config
whooshEndpoint := r.Config.WHOOSHAPI.BaseURL
if whooshEndpoint == "" {
whooshEndpoint = "http://whoosh:8080"
}
// Build result payload
payload := map[string]interface{}{
"council_id": assignment.CouncilID,
"role_name": assignment.RoleName,
"agent_id": r.Config.Agent.ID,
"ucxl_address": assignment.UCXLAddress,
"output": result.Output,
"artifacts": result.Artifacts,
"success": result.Success,
"error_message": result.ErrorMessage,
"execution_time": result.Metrics.Duration.Seconds(),
"timestamp": time.Now().Unix(),
}
jsonData, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("failed to marshal result payload: %w", err)
}
// Send to WHOOSH
url := fmt.Sprintf("%s/api/councils/%s/results", whooshEndpoint, assignment.CouncilID)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create HTTP request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("failed to send results to WHOOSH: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
return fmt.Errorf("WHOOSH returned status %d", resp.StatusCode)
}
r.Logger.Info("📤 Results uploaded to WHOOSH for council %s", assignment.CouncilID)
return nil
}