diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c57704e --- /dev/null +++ b/Makefile @@ -0,0 +1,130 @@ +# CHORUS Multi-Binary Makefile +# Builds both chorus-agent and chorus-hap binaries + +# Build configuration +BINARY_NAME_AGENT = chorus-agent +BINARY_NAME_HAP = chorus-hap +BINARY_NAME_COMPAT = chorus +VERSION ?= 0.1.0-dev +COMMIT_HASH ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") +BUILD_DATE ?= $(shell date -u '+%Y-%m-%d_%H:%M:%S') + +# Go build flags +LDFLAGS = -ldflags "-X main.version=$(VERSION) -X main.commitHash=$(COMMIT_HASH) -X main.buildDate=$(BUILD_DATE)" +BUILD_FLAGS = -v $(LDFLAGS) + +# Directories +BUILD_DIR = build +CMD_DIR = cmd + +# Default target +.PHONY: all +all: clean build + +# Build all binaries (including compatibility wrapper) +.PHONY: build +build: build-agent build-hap build-compat + +# Build autonomous agent binary +.PHONY: build-agent +build-agent: + @echo "πŸ€– Building CHORUS autonomous agent..." + @mkdir -p $(BUILD_DIR) + go build $(BUILD_FLAGS) -o $(BUILD_DIR)/$(BINARY_NAME_AGENT) ./$(CMD_DIR)/agent + @echo "βœ… Agent binary built: $(BUILD_DIR)/$(BINARY_NAME_AGENT)" + +# Build human agent portal binary +.PHONY: build-hap +build-hap: + @echo "πŸ‘€ Building CHORUS human agent portal..." + @mkdir -p $(BUILD_DIR) + go build $(BUILD_FLAGS) -o $(BUILD_DIR)/$(BINARY_NAME_HAP) ./$(CMD_DIR)/hap + @echo "βœ… HAP binary built: $(BUILD_DIR)/$(BINARY_NAME_HAP)" + +# Build compatibility wrapper (deprecated) +.PHONY: build-compat +build-compat: + @echo "⚠️ Building CHORUS compatibility wrapper (deprecated)..." + @mkdir -p $(BUILD_DIR) + go build $(BUILD_FLAGS) -o $(BUILD_DIR)/$(BINARY_NAME_COMPAT) ./$(CMD_DIR)/chorus + @echo "βœ… Compatibility wrapper built: $(BUILD_DIR)/$(BINARY_NAME_COMPAT)" + +# Test compilation without building +.PHONY: test-compile +test-compile: + @echo "πŸ” Testing compilation of both binaries..." + go build -o /dev/null ./$(CMD_DIR)/agent + go build -o /dev/null ./$(CMD_DIR)/hap + @echo "βœ… Both binaries compile successfully" + +# Run tests +.PHONY: test +test: + @echo "πŸ§ͺ Running tests..." + go test -v ./... + +# Clean build artifacts +.PHONY: clean +clean: + @echo "🧹 Cleaning build artifacts..." + rm -rf $(BUILD_DIR) + @echo "βœ… Clean complete" + +# Install both binaries to GOPATH/bin +.PHONY: install +install: build + @echo "πŸ“¦ Installing binaries to GOPATH/bin..." + cp $(BUILD_DIR)/$(BINARY_NAME_AGENT) $(shell go env GOPATH)/bin/ + cp $(BUILD_DIR)/$(BINARY_NAME_HAP) $(shell go env GOPATH)/bin/ + @echo "βœ… Binaries installed" + +# Development helpers +.PHONY: run-agent +run-agent: build-agent + @echo "πŸš€ Running CHORUS agent..." + ./$(BUILD_DIR)/$(BINARY_NAME_AGENT) + +.PHONY: run-hap +run-hap: build-hap + @echo "πŸš€ Running CHORUS HAP..." + ./$(BUILD_DIR)/$(BINARY_NAME_HAP) + +# Docker builds +.PHONY: docker-agent +docker-agent: + @echo "🐳 Building Docker image for CHORUS agent..." + docker build -f docker/Dockerfile.agent -t chorus-agent:$(VERSION) . + +.PHONY: docker-hap +docker-hap: + @echo "🐳 Building Docker image for CHORUS HAP..." + docker build -f docker/Dockerfile.hap -t chorus-hap:$(VERSION) . + +.PHONY: docker +docker: docker-agent docker-hap + +# Help +.PHONY: help +help: + @echo "CHORUS Multi-Binary Build System" + @echo "" + @echo "Targets:" + @echo " all - Clean and build both binaries (default)" + @echo " build - Build both binaries" + @echo " build-agent - Build autonomous agent binary only" + @echo " build-hap - Build human agent portal binary only" + @echo " test-compile - Test that both binaries compile" + @echo " test - Run tests" + @echo " clean - Remove build artifacts" + @echo " install - Install binaries to GOPATH/bin" + @echo " run-agent - Build and run agent" + @echo " run-hap - Build and run HAP" + @echo " docker - Build Docker images for both binaries" + @echo " docker-agent - Build Docker image for agent only" + @echo " docker-hap - Build Docker image for HAP only" + @echo " help - Show this help" + @echo "" + @echo "Environment Variables:" + @echo " VERSION - Version string (default: 0.1.0-dev)" + @echo " COMMIT_HASH - Git commit hash (auto-detected)" + @echo " BUILD_DATE - Build timestamp (auto-generated)" \ No newline at end of file diff --git a/cmd/agent/main.go b/cmd/agent/main.go new file mode 100644 index 0000000..c682772 --- /dev/null +++ b/cmd/agent/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "chorus/internal/runtime" +) + +func main() { + // Early CLI handling: print help/version without requiring env/config + for _, a := range os.Args[1:] { + switch a { + case "--help", "-h", "help": + fmt.Printf("%s-agent %s\n\n", runtime.AppName, runtime.AppVersion) + fmt.Println("Usage:") + fmt.Printf(" %s [--help] [--version]\n\n", filepath.Base(os.Args[0])) + fmt.Println("CHORUS Autonomous Agent - P2P Task Coordination") + fmt.Println() + fmt.Println("This binary runs autonomous AI agents that participate in P2P task coordination,") + fmt.Println("collaborative reasoning via HMMM, and distributed decision making.") + fmt.Println() + fmt.Println("Environment (common):") + fmt.Println(" CHORUS_LICENSE_ID (required)") + fmt.Println(" CHORUS_AGENT_ID (optional; auto-generated if empty)") + fmt.Println(" CHORUS_P2P_PORT (default 9000)") + fmt.Println(" CHORUS_API_PORT (default 8080)") + fmt.Println(" CHORUS_HEALTH_PORT (default 8081)") + fmt.Println(" CHORUS_DHT_ENABLED (default true)") + fmt.Println(" CHORUS_BOOTSTRAP_PEERS (comma-separated multiaddrs)") + fmt.Println(" OLLAMA_ENDPOINT (default http://localhost:11434)") + fmt.Println() + fmt.Println("Example:") + fmt.Println(" CHORUS_LICENSE_ID=dev-123 \\") + fmt.Println(" CHORUS_AGENT_ID=chorus-agent-1 \\") + fmt.Println(" CHORUS_P2P_PORT=9000 CHORUS_API_PORT=8080 ./chorus-agent") + fmt.Println() + fmt.Println("Agent Features:") + fmt.Println(" - Autonomous task execution") + fmt.Println(" - P2P mesh networking") + fmt.Println(" - HMMM collaborative reasoning") + fmt.Println(" - DHT encrypted storage") + fmt.Println(" - UCXL context addressing") + fmt.Println(" - Democratic leader election") + fmt.Println(" - Health monitoring") + return + case "--version", "-v": + fmt.Printf("%s-agent %s\n", runtime.AppName, runtime.AppVersion) + return + } + } + + // Initialize shared P2P runtime + sharedRuntime, err := runtime.Initialize("agent") + if err != nil { + fmt.Fprintf(os.Stderr, "❌ Failed to initialize CHORUS agent: %v\n", err) + os.Exit(1) + } + defer sharedRuntime.Cleanup() + + // Start agent mode with autonomous behaviors + if err := sharedRuntime.StartAgentMode(); err != nil { + fmt.Fprintf(os.Stderr, "❌ Agent mode failed: %v\n", err) + os.Exit(1) + } +} \ No newline at end of file diff --git a/cmd/chorus/main.go b/cmd/chorus/main.go index eb3c12e..7e45040 100644 --- a/cmd/chorus/main.go +++ b/cmd/chorus/main.go @@ -1,688 +1,63 @@ package main import ( - "context" "fmt" - "log" - "net/http" "os" - "path/filepath" - "time" - "chorus/api" - "chorus/coordinator" - "chorus/discovery" - "chorus/internal/backbeat" - "chorus/internal/licensing" - "chorus/internal/logging" - "chorus/p2p" - "chorus/pkg/config" - "chorus/pkg/dht" - "chorus/pkg/election" - "chorus/pkg/health" - "chorus/pkg/shutdown" - "chorus/pkg/ucxi" - "chorus/pkg/ucxl" - "chorus/pubsub" - "chorus/reasoning" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/multiformats/go-multiaddr" + "chorus/internal/runtime" ) -const ( - AppName = "CHORUS" - AppVersion = "0.1.0-dev" -) - -// SimpleLogger provides basic logging implementation -type SimpleLogger struct{} - -func (l *SimpleLogger) Info(msg string, args ...interface{}) { - log.Printf("[INFO] "+msg, args...) -} - -func (l *SimpleLogger) Warn(msg string, args ...interface{}) { - log.Printf("[WARN] "+msg, args...) -} - -func (l *SimpleLogger) Error(msg string, args ...interface{}) { - log.Printf("[ERROR] "+msg, args...) -} - -// SimpleTaskTracker tracks active tasks for availability reporting -type SimpleTaskTracker struct { - maxTasks int - activeTasks map[string]bool - decisionPublisher *ucxl.DecisionPublisher -} - -// GetActiveTasks returns list of active task IDs -func (t *SimpleTaskTracker) GetActiveTasks() []string { - tasks := make([]string, 0, len(t.activeTasks)) - for taskID := range t.activeTasks { - tasks = append(tasks, taskID) - } - return tasks -} - -// GetMaxTasks returns maximum number of concurrent tasks -func (t *SimpleTaskTracker) GetMaxTasks() int { - return t.maxTasks -} - -// AddTask marks a task as active -func (t *SimpleTaskTracker) AddTask(taskID string) { - t.activeTasks[taskID] = true -} - -// RemoveTask marks a task as completed and publishes decision if publisher available -func (t *SimpleTaskTracker) RemoveTask(taskID string) { - delete(t.activeTasks, taskID) - - // Publish task completion decision if publisher is available - if t.decisionPublisher != nil { - t.publishTaskCompletion(taskID, true, "Task completed successfully", nil) - } -} - -// publishTaskCompletion publishes a task completion decision to DHT -func (t *SimpleTaskTracker) publishTaskCompletion(taskID string, success bool, summary string, filesModified []string) { - if t.decisionPublisher == nil { - return - } - - if err := t.decisionPublisher.PublishTaskCompletion(taskID, success, summary, filesModified); err != nil { - fmt.Printf("⚠️ Failed to publish task completion for %s: %v\n", taskID, err) - } else { - fmt.Printf("πŸ“€ Published task completion decision for: %s\n", taskID) - } -} +// DEPRECATED: This binary is deprecated in favor of chorus-agent and chorus-hap +// This compatibility wrapper redirects users to the appropriate new binary func main() { - // Early CLI handling: print help/version without requiring env/config - for _, a := range os.Args[1:] { - switch a { - case "--help", "-h", "help": - fmt.Printf("%s %s\n\n", AppName, AppVersion) - fmt.Println("Usage:") - fmt.Printf(" %s [--help] [--version]\n\n", filepath.Base(os.Args[0])) - fmt.Println("Environment (common):") - fmt.Println(" CHORUS_LICENSE_ID (required)") - fmt.Println(" CHORUS_AGENT_ID (optional; auto-generated if empty)") - fmt.Println(" CHORUS_P2P_PORT (default 9000)") - fmt.Println(" CHORUS_API_PORT (default 8080)") - fmt.Println(" CHORUS_HEALTH_PORT (default 8081)") - fmt.Println(" CHORUS_DHT_ENABLED (default true)") - fmt.Println(" CHORUS_BOOTSTRAP_PEERS (comma-separated multiaddrs)") - fmt.Println(" OLLAMA_ENDPOINT (default http://localhost:11434)") - fmt.Println() - fmt.Println("Example:") - fmt.Println(" CHORUS_LICENSE_ID=dev-123 \\") - fmt.Println(" CHORUS_AGENT_ID=chorus-dev \\") - fmt.Println(" CHORUS_P2P_PORT=9000 CHORUS_API_PORT=8080 ./chorus") - return - case "--version", "-v": - fmt.Printf("%s %s\n", AppName, AppVersion) - return - } - } - - // Initialize container-optimized logger - logger := &SimpleLogger{} - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - logger.Info("🎭 Starting CHORUS v%s - Container-First P2P Task Coordination", AppVersion) - logger.Info("πŸ“¦ Container deployment of proven CHORUS functionality") - - // Load configuration from environment (no config files in containers) - logger.Info("πŸ“‹ Loading configuration from environment variables...") - cfg, err := config.LoadFromEnvironment() - if err != nil { - logger.Error("❌ Configuration error: %v", err) - os.Exit(1) - } - - logger.Info("βœ… Configuration loaded successfully") - logger.Info("πŸ€– Agent ID: %s", cfg.Agent.ID) - logger.Info("🎯 Specialization: %s", cfg.Agent.Specialization) - - // CRITICAL: Validate license before any P2P operations - logger.Info("πŸ” Validating CHORUS license with KACHING...") - licenseValidator := licensing.NewValidator(licensing.LicenseConfig{ - LicenseID: cfg.License.LicenseID, - ClusterID: cfg.License.ClusterID, - KachingURL: cfg.License.KachingURL, - }) - if err := licenseValidator.Validate(); err != nil { - logger.Error("❌ License validation failed: %v", err) - logger.Error("πŸ’° CHORUS requires a valid license to operate") - logger.Error("πŸ“ž Contact chorus.services for licensing information") - os.Exit(1) - } - logger.Info("βœ… License validation successful - CHORUS authorized to run") - - // Initialize AI provider configuration - logger.Info("🧠 Configuring AI provider: %s", cfg.AI.Provider) - if err := initializeAIProvider(cfg, logger); err != nil { - logger.Error("❌ AI provider initialization failed: %v", err) - os.Exit(1) - } - logger.Info("βœ… AI provider configured successfully") - - // Initialize BACKBEAT integration - var backbeatIntegration *backbeat.Integration - backbeatIntegration, err = backbeat.NewIntegration(cfg, cfg.Agent.ID, logger) - if err != nil { - logger.Warn("⚠️ BACKBEAT integration initialization failed: %v", err) - logger.Info("πŸ“ P2P operations will run without beat synchronization") - } else { - if err := backbeatIntegration.Start(ctx); err != nil { - logger.Warn("⚠️ Failed to start BACKBEAT integration: %v", err) - backbeatIntegration = nil - } else { - logger.Info("🎡 BACKBEAT integration started successfully") - } - } - defer func() { - if backbeatIntegration != nil { - backbeatIntegration.Stop() - } - }() - - // Initialize P2P node - node, err := p2p.NewNode(ctx) - if err != nil { - log.Fatalf("Failed to create P2P node: %v", err) - } - defer node.Close() - - logger.Info("🐝 CHORUS node started successfully") - logger.Info("πŸ“ Node ID: %s", node.ID().ShortString()) - logger.Info("πŸ”— Listening addresses:") - for _, addr := range node.Addresses() { - logger.Info(" %s/p2p/%s", addr, node.ID()) - } - - // Initialize Hypercore-style logger for P2P coordination - hlog := logging.NewHypercoreLog(node.ID()) - hlog.Append(logging.PeerJoined, map[string]interface{}{"status": "started"}) - logger.Info("πŸ“ Hypercore logger initialized") - - // Initialize mDNS discovery - mdnsDiscovery, err := discovery.NewMDNSDiscovery(ctx, node.Host(), "chorus-peer-discovery") - if err != nil { - log.Fatalf("Failed to create mDNS discovery: %v", err) - } - defer mdnsDiscovery.Close() - - // Initialize PubSub with hypercore logging - ps, err := pubsub.NewPubSubWithLogger(ctx, node.Host(), "chorus/coordination/v1", "hmmm/meta-discussion/v1", hlog) - if err != nil { - log.Fatalf("Failed to create PubSub: %v", err) - } - defer ps.Close() - - logger.Info("πŸ“‘ PubSub system initialized") - - // Join role-based topics if role is configured - if cfg.Agent.Role != "" { - reportsTo := []string{} - if cfg.Agent.ReportsTo != "" { - reportsTo = []string{cfg.Agent.ReportsTo} - } - if err := ps.JoinRoleBasedTopics(cfg.Agent.Role, cfg.Agent.Expertise, reportsTo); err != nil { - logger.Warn("⚠️ Failed to join role-based topics: %v", err) - } else { - logger.Info("🎯 Joined role-based collaboration topics") + // Early CLI handling: print help/version/deprecation notice + for _, a := range os.Args[1:] { + switch a { + case "--help", "-h", "help": + printDeprecationHelp() + return + case "--version", "-v": + fmt.Printf("%s %s (DEPRECATED)\n", runtime.AppName, runtime.AppVersion) + return } } - // === Admin Election System === - electionManager := election.NewElectionManager(ctx, cfg, node.Host(), ps, node.ID().ShortString()) - - // Set election callbacks with BACKBEAT integration - electionManager.SetCallbacks( - func(oldAdmin, newAdmin string) { - logger.Info("πŸ‘‘ Admin changed: %s -> %s", oldAdmin, newAdmin) - - // Track admin change with BACKBEAT if available - if backbeatIntegration != nil { - operationID := fmt.Sprintf("admin-change-%d", time.Now().Unix()) - if err := backbeatIntegration.StartP2POperation(operationID, "admin_change", 2, map[string]interface{}{ - "old_admin": oldAdmin, - "new_admin": newAdmin, - }); err == nil { - // Complete immediately as this is a state change, not a long operation - backbeatIntegration.CompleteP2POperation(operationID, 1) - } - } - - // If this node becomes admin, enable SLURP functionality - if newAdmin == node.ID().ShortString() { - logger.Info("🎯 This node is now admin - enabling SLURP functionality") - cfg.Slurp.Enabled = true - // Apply admin role configuration - if err := cfg.ApplyRoleDefinition("admin"); err != nil { - logger.Warn("⚠️ Failed to apply admin role: %v", err) - } - } - }, - func(winner string) { - logger.Info("πŸ† Election completed, winner: %s", winner) - - // Track election completion with BACKBEAT if available - if backbeatIntegration != nil { - operationID := fmt.Sprintf("election-completed-%d", time.Now().Unix()) - if err := backbeatIntegration.StartP2POperation(operationID, "election", 1, map[string]interface{}{ - "winner": winner, - "node_id": node.ID().ShortString(), - }); err == nil { - backbeatIntegration.CompleteP2POperation(operationID, 1) - } - } - }, - ) - - if err := electionManager.Start(); err != nil { - logger.Error("❌ Failed to start election manager: %v", err) - } else { - logger.Info("βœ… Election manager started with automated heartbeat management") - } - defer electionManager.Stop() - - // === DHT Storage and Decision Publishing === - var dhtNode *dht.LibP2PDHT - var encryptedStorage *dht.EncryptedDHTStorage - var decisionPublisher *ucxl.DecisionPublisher - - if cfg.V2.DHT.Enabled { - // Create DHT - dhtNode, err = dht.NewLibP2PDHT(ctx, node.Host()) - if err != nil { - logger.Warn("⚠️ Failed to create DHT: %v", err) - } else { - logger.Info("πŸ•ΈοΈ DHT initialized") - - // Bootstrap DHT with BACKBEAT tracking - if backbeatIntegration != nil { - operationID := fmt.Sprintf("dht-bootstrap-%d", time.Now().Unix()) - if err := backbeatIntegration.StartP2POperation(operationID, "dht_bootstrap", 4, nil); err == nil { - backbeatIntegration.UpdateP2POperationPhase(operationID, backbeat.PhaseConnecting, 0) - } - - if err := dhtNode.Bootstrap(); err != nil { - logger.Warn("⚠️ DHT bootstrap failed: %v", err) - backbeatIntegration.FailP2POperation(operationID, err.Error()) - } else { - backbeatIntegration.CompleteP2POperation(operationID, 1) - } - } else { - if err := dhtNode.Bootstrap(); err != nil { - logger.Warn("⚠️ DHT bootstrap failed: %v", err) - } - } - - // Connect to bootstrap peers if configured - for _, addrStr := range cfg.V2.DHT.BootstrapPeers { - addr, err := multiaddr.NewMultiaddr(addrStr) - if err != nil { - logger.Warn("⚠️ Invalid bootstrap address %s: %v", addrStr, err) - continue - } - - // Extract peer info from multiaddr - info, err := peer.AddrInfoFromP2pAddr(addr) - if err != nil { - logger.Warn("⚠️ Failed to parse peer info from %s: %v", addrStr, err) - continue - } - - // Track peer discovery with BACKBEAT if available - if backbeatIntegration != nil { - operationID := fmt.Sprintf("peer-discovery-%d", time.Now().Unix()) - if err := backbeatIntegration.StartP2POperation(operationID, "peer_discovery", 2, map[string]interface{}{ - "peer_addr": addrStr, - }); err == nil { - backbeatIntegration.UpdateP2POperationPhase(operationID, backbeat.PhaseConnecting, 0) - - if err := node.Host().Connect(ctx, *info); err != nil { - logger.Warn("⚠️ Failed to connect to bootstrap peer %s: %v", addrStr, err) - backbeatIntegration.FailP2POperation(operationID, err.Error()) - } else { - logger.Info("πŸ”— Connected to DHT bootstrap peer: %s", addrStr) - backbeatIntegration.CompleteP2POperation(operationID, 1) - } - } - } else { - if err := node.Host().Connect(ctx, *info); err != nil { - logger.Warn("⚠️ Failed to connect to bootstrap peer %s: %v", addrStr, err) - } else { - logger.Info("πŸ”— Connected to DHT bootstrap peer: %s", addrStr) - } - } - } - - // Initialize encrypted storage - encryptedStorage = dht.NewEncryptedDHTStorage( - ctx, - node.Host(), - dhtNode, - cfg, - node.ID().ShortString(), - ) - - // Start cache cleanup - encryptedStorage.StartCacheCleanup(5 * time.Minute) - logger.Info("πŸ” Encrypted DHT storage initialized") - - // Initialize decision publisher - decisionPublisher = ucxl.NewDecisionPublisher( - ctx, - cfg, - encryptedStorage, - node.ID().ShortString(), - cfg.Agent.ID, - ) - logger.Info("πŸ“€ Decision publisher initialized") - } - } else { - logger.Info("βšͺ DHT disabled in configuration") - } - - defer func() { - if dhtNode != nil { - dhtNode.Close() - } - }() - - // === Task Coordination Integration === - taskCoordinator := coordinator.NewTaskCoordinator( - ctx, - ps, - hlog, - cfg, - node.ID().ShortString(), - nil, // HMMM router placeholder - ) - - taskCoordinator.Start() - logger.Info("βœ… Task coordination system active") - - // Start HTTP API server - httpServer := api.NewHTTPServer(cfg.Network.APIPort, hlog, ps) - go func() { - logger.Info("🌐 HTTP API server starting on :%d", cfg.Network.APIPort) - if err := httpServer.Start(); err != nil && err != http.ErrServerClosed { - logger.Error("❌ HTTP server error: %v", err) - } - }() - defer httpServer.Stop() - - // === UCXI Server Integration === - var ucxiServer *ucxi.Server - if cfg.UCXL.Enabled && cfg.UCXL.Server.Enabled { - storageDir := cfg.UCXL.Storage.Directory - if storageDir == "" { - storageDir = filepath.Join(os.TempDir(), "chorus-ucxi-storage") - } - - storage, err := ucxi.NewBasicContentStorage(storageDir) - if err != nil { - logger.Warn("⚠️ Failed to create UCXI storage: %v", err) - } else { - resolver := ucxi.NewBasicAddressResolver(node.ID().ShortString()) - resolver.SetDefaultTTL(cfg.UCXL.Resolution.CacheTTL) - - ucxiConfig := ucxi.ServerConfig{ - Port: cfg.UCXL.Server.Port, - BasePath: cfg.UCXL.Server.BasePath, - Resolver: resolver, - Storage: storage, - Logger: ucxi.SimpleLogger{}, - } - - ucxiServer = ucxi.NewServer(ucxiConfig) - go func() { - logger.Info("πŸ”— UCXI server starting on :%d", cfg.UCXL.Server.Port) - if err := ucxiServer.Start(); err != nil && err != http.ErrServerClosed { - logger.Error("❌ UCXI server error: %v", err) - } - }() - defer func() { - if ucxiServer != nil { - ucxiServer.Stop() - } - }() - } - } else { - logger.Info("βšͺ UCXI server disabled") - } - - // Create simple task tracker - taskTracker := &SimpleTaskTracker{ - maxTasks: cfg.Agent.MaxTasks, - activeTasks: make(map[string]bool), - } - - // Connect decision publisher to task tracker if available - if decisionPublisher != nil { - taskTracker.decisionPublisher = decisionPublisher - logger.Info("πŸ“€ Task completion decisions will be published to DHT") - } - - // Announce capabilities and role - go announceAvailability(ps, node.ID().ShortString(), taskTracker, logger) - go announceCapabilitiesOnChange(ps, node.ID().ShortString(), cfg, logger) - go announceRoleOnStartup(ps, node.ID().ShortString(), cfg, logger) - - // Start status reporting - go statusReporter(node, logger) - - logger.Info("πŸ” Listening for peers on container network...") - logger.Info("πŸ“‘ Ready for task coordination and meta-discussion") - logger.Info("🎯 HMMM collaborative reasoning enabled") - - // === Comprehensive Health Monitoring & Graceful Shutdown === - shutdownManager := shutdown.NewManager(30*time.Second, &simpleLogger{logger: logger}) - - healthManager := health.NewManager(node.ID().ShortString(), AppVersion, &simpleLogger{logger: logger}) - healthManager.SetShutdownManager(shutdownManager) - - // Register health checks - setupHealthChecks(healthManager, ps, node, dhtNode, backbeatIntegration) - - // Register components for graceful shutdown - setupGracefulShutdown(shutdownManager, healthManager, node, ps, mdnsDiscovery, - electionManager, httpServer, ucxiServer, taskCoordinator, dhtNode) - - // Start health monitoring - if err := healthManager.Start(); err != nil { - logger.Error("❌ Failed to start health manager: %v", err) - } else { - logger.Info("❀️ Health monitoring started") - } - - // Start health HTTP server - if err := healthManager.StartHTTPServer(cfg.Network.HealthPort); err != nil { - logger.Error("❌ Failed to start health HTTP server: %v", err) - } else { - logger.Info("πŸ₯ Health endpoints available at http://localhost:%d/health", cfg.Network.HealthPort) - } - - // Start shutdown manager - shutdownManager.Start() - logger.Info("πŸ›‘οΈ Graceful shutdown manager started") - - logger.Info("βœ… CHORUS system fully operational with health monitoring") - - // Wait for graceful shutdown - shutdownManager.Wait() - logger.Info("βœ… CHORUS system shutdown completed") + // Print deprecation warning for direct execution + printDeprecationWarning() + os.Exit(1) } -// Rest of the functions (setupHealthChecks, etc.) would be adapted from CHORUS... -// For brevity, I'll include key functions but the full implementation would port all CHORUS functionality - -// simpleLogger implements basic logging for shutdown and health systems -type simpleLogger struct { - logger logging.Logger +func printDeprecationHelp() { + fmt.Printf("⚠️ %s %s - DEPRECATED BINARY\n\n", runtime.AppName, runtime.AppVersion) + fmt.Println("This binary has been replaced by specialized binaries:") + fmt.Println() + fmt.Println("πŸ€– chorus-agent - Autonomous AI agent for task coordination") + fmt.Println("πŸ‘€ chorus-hap - Human Agent Portal for human participation") + fmt.Println() + fmt.Println("Migration Guide:") + fmt.Println(" OLD: ./chorus") + fmt.Println(" NEW: ./chorus-agent (for autonomous agents)") + fmt.Println(" ./chorus-hap (for human agents)") + fmt.Println() + fmt.Println("Why this change?") + fmt.Println(" - Enables human participation in agent networks") + fmt.Println(" - Better separation of concerns") + fmt.Println(" - Specialized interfaces for different use cases") + fmt.Println(" - Shared P2P infrastructure with different UIs") + fmt.Println() + fmt.Println("For help with the new binaries:") + fmt.Println(" ./chorus-agent --help") + fmt.Println(" ./chorus-hap --help") } -func (l *simpleLogger) Info(msg string, args ...interface{}) { - l.logger.Info(msg, args...) -} - -func (l *simpleLogger) Warn(msg string, args ...interface{}) { - l.logger.Warn(msg, args...) -} - -func (l *simpleLogger) Error(msg string, args ...interface{}) { - l.logger.Error(msg, args...) -} - -// announceAvailability broadcasts current working status for task assignment -func announceAvailability(ps *pubsub.PubSub, nodeID string, taskTracker *SimpleTaskTracker, logger logging.Logger) { - ticker := time.NewTicker(30 * time.Second) - defer ticker.Stop() - - for ; ; <-ticker.C { - currentTasks := taskTracker.GetActiveTasks() - maxTasks := taskTracker.GetMaxTasks() - isAvailable := len(currentTasks) < maxTasks - - status := "ready" - if len(currentTasks) >= maxTasks { - status = "busy" - } else if len(currentTasks) > 0 { - status = "working" - } - - availability := map[string]interface{}{ - "node_id": nodeID, - "available_for_work": isAvailable, - "current_tasks": len(currentTasks), - "max_tasks": maxTasks, - "last_activity": time.Now().Unix(), - "status": status, - "timestamp": time.Now().Unix(), - } - if err := ps.PublishBzzzMessage(pubsub.AvailabilityBcast, availability); err != nil { - logger.Error("❌ Failed to announce availability: %v", err) - } - } -} - -// statusReporter provides periodic status updates -func statusReporter(node *p2p.Node, logger logging.Logger) { - ticker := time.NewTicker(60 * time.Second) - defer ticker.Stop() - - for ; ; <-ticker.C { - peers := node.ConnectedPeers() - logger.Info("πŸ“Š Status: %d connected peers", peers) - } -} - -// Placeholder functions for full CHORUS port - these would be fully implemented -func announceCapabilitiesOnChange(ps *pubsub.PubSub, nodeID string, cfg *config.Config, logger logging.Logger) { - // Implementation from CHORUS would go here -} - -func announceRoleOnStartup(ps *pubsub.PubSub, nodeID string, cfg *config.Config, logger logging.Logger) { - // Implementation from CHORUS would go here -} - -func setupHealthChecks(healthManager *health.Manager, ps *pubsub.PubSub, node *p2p.Node, dhtNode *dht.LibP2PDHT, backbeatIntegration *backbeat.Integration) { - // Add BACKBEAT health check - if backbeatIntegration != nil { - backbeatCheck := &health.HealthCheck{ - Name: "backbeat", - Description: "BACKBEAT timing integration health", - Interval: 30 * time.Second, - Timeout: 10 * time.Second, - Enabled: true, - Critical: false, - Checker: func(ctx context.Context) health.CheckResult { - healthInfo := backbeatIntegration.GetHealth() - connected, _ := healthInfo["connected"].(bool) - - result := health.CheckResult{ - Healthy: connected, - Details: healthInfo, - Timestamp: time.Now(), - } - - if connected { - result.Message = "BACKBEAT integration healthy and connected" - } else { - result.Message = "BACKBEAT integration not connected" - } - - return result - }, - } - healthManager.RegisterCheck(backbeatCheck) - } - - // Implementation from CHORUS would go here - other health checks -} - -func setupGracefulShutdown(shutdownManager *shutdown.Manager, healthManager *health.Manager, - node *p2p.Node, ps *pubsub.PubSub, mdnsDiscovery interface{}, electionManager interface{}, - httpServer *api.HTTPServer, ucxiServer *ucxi.Server, taskCoordinator interface{}, dhtNode *dht.LibP2PDHT) { - // Implementation from CHORUS would go here -} - -// initializeAIProvider configures the reasoning engine with the appropriate AI provider -func initializeAIProvider(cfg *config.Config, logger logging.Logger) error { - // Set the AI provider - reasoning.SetAIProvider(cfg.AI.Provider) - - // Configure the selected provider - switch cfg.AI.Provider { - case "resetdata": - if cfg.AI.ResetData.APIKey == "" { - return fmt.Errorf("RESETDATA_API_KEY environment variable is required for resetdata provider") - } - - resetdataConfig := reasoning.ResetDataConfig{ - BaseURL: cfg.AI.ResetData.BaseURL, - APIKey: cfg.AI.ResetData.APIKey, - Model: cfg.AI.ResetData.Model, - Timeout: cfg.AI.ResetData.Timeout, - } - reasoning.SetResetDataConfig(resetdataConfig) - logger.Info("🌐 ResetData AI provider configured - Endpoint: %s, Model: %s", - cfg.AI.ResetData.BaseURL, cfg.AI.ResetData.Model) - - case "ollama": - reasoning.SetOllamaEndpoint(cfg.AI.Ollama.Endpoint) - logger.Info("πŸ¦™ Ollama AI provider configured - Endpoint: %s", cfg.AI.Ollama.Endpoint) - - default: - logger.Warn("⚠️ Unknown AI provider '%s', defaulting to resetdata", cfg.AI.Provider) - if cfg.AI.ResetData.APIKey == "" { - return fmt.Errorf("RESETDATA_API_KEY environment variable is required for default resetdata provider") - } - - resetdataConfig := reasoning.ResetDataConfig{ - BaseURL: cfg.AI.ResetData.BaseURL, - APIKey: cfg.AI.ResetData.APIKey, - Model: cfg.AI.ResetData.Model, - Timeout: cfg.AI.ResetData.Timeout, - } - reasoning.SetResetDataConfig(resetdataConfig) - reasoning.SetAIProvider("resetdata") - } - - // Configure model selection - reasoning.SetModelConfig( - cfg.Agent.Models, - cfg.Agent.ModelSelectionWebhook, - cfg.Agent.DefaultReasoningModel, - ) - - return nil -} +func printDeprecationWarning() { + fmt.Fprintf(os.Stderr, "⚠️ DEPRECATION WARNING: The 'chorus' binary is deprecated!\n\n") + fmt.Fprintf(os.Stderr, "This binary has been replaced with specialized binaries:\n") + fmt.Fprintf(os.Stderr, " πŸ€– chorus-agent - For autonomous AI agents\n") + fmt.Fprintf(os.Stderr, " πŸ‘€ chorus-hap - For human agent participation\n\n") + fmt.Fprintf(os.Stderr, "Please use one of the new binaries instead:\n") + fmt.Fprintf(os.Stderr, " ./chorus-agent --help\n") + fmt.Fprintf(os.Stderr, " ./chorus-hap --help\n\n") + fmt.Fprintf(os.Stderr, "This wrapper will be removed in a future version.\n") +} \ No newline at end of file diff --git a/cmd/hap/main.go b/cmd/hap/main.go new file mode 100644 index 0000000..9d7279c --- /dev/null +++ b/cmd/hap/main.go @@ -0,0 +1,141 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "chorus/internal/runtime" +) + +func main() { + // Early CLI handling: print help/version without requiring env/config + for _, a := range os.Args[1:] { + switch a { + case "--help", "-h", "help": + fmt.Printf("%s-hap %s\n\n", runtime.AppName, runtime.AppVersion) + fmt.Println("Usage:") + fmt.Printf(" %s [--help] [--version]\n\n", filepath.Base(os.Args[0])) + fmt.Println("CHORUS Human Agent Portal - Human Interface to P2P Agent Networks") + fmt.Println() + fmt.Println("This binary provides a human-friendly interface to participate in P2P agent") + fmt.Println("coordination networks. Humans can collaborate with autonomous agents using") + fmt.Println("the same protocols and appear as peers in the distributed network.") + fmt.Println() + fmt.Println("Environment (common):") + fmt.Println(" CHORUS_LICENSE_ID (required)") + fmt.Println(" CHORUS_AGENT_ID (optional; auto-generated if empty)") + fmt.Println(" CHORUS_P2P_PORT (default 9000)") + fmt.Println(" CHORUS_API_PORT (default 8080)") + fmt.Println(" CHORUS_HEALTH_PORT (default 8081)") + fmt.Println(" CHORUS_DHT_ENABLED (default true)") + fmt.Println(" CHORUS_BOOTSTRAP_PEERS (comma-separated multiaddrs)") + fmt.Println(" OLLAMA_ENDPOINT (default http://localhost:11434)") + fmt.Println() + fmt.Println("HAP-Specific Environment:") + fmt.Println(" CHORUS_HAP_MODE (terminal|web, default terminal)") + fmt.Println(" CHORUS_HAP_WEB_PORT (default 8082)") + fmt.Println() + fmt.Println("Example:") + fmt.Println(" CHORUS_LICENSE_ID=dev-123 \\") + fmt.Println(" CHORUS_AGENT_ID=human-alice \\") + fmt.Println(" CHORUS_HAP_MODE=terminal \\") + fmt.Println(" CHORUS_P2P_PORT=9001 ./chorus-hap") + fmt.Println() + fmt.Println("HAP Features:") + fmt.Println(" - Human-friendly message composition") + fmt.Println(" - HMMM reasoning template helpers") + fmt.Println(" - UCXL context browsing") + fmt.Println(" - Collaborative decision participation") + fmt.Println(" - Terminal and web interface modes") + fmt.Println(" - Same P2P protocols as autonomous agents") + return + case "--version", "-v": + fmt.Printf("%s-hap %s\n", runtime.AppName, runtime.AppVersion) + return + } + } + + // Initialize shared P2P runtime (same as agent) + sharedRuntime, err := runtime.Initialize("hap") + if err != nil { + fmt.Fprintf(os.Stderr, "❌ Failed to initialize CHORUS HAP: %v\n", err) + os.Exit(1) + } + defer sharedRuntime.Cleanup() + + // Start HAP mode with human interface + if err := startHAPMode(sharedRuntime); err != nil { + fmt.Fprintf(os.Stderr, "❌ HAP mode failed: %v\n", err) + os.Exit(1) + } +} + +// startHAPMode runs the Human Agent Portal with interactive interface +func startHAPMode(runtime *runtime.SharedRuntime) error { + runtime.Logger.Info("πŸ‘€ Starting CHORUS Human Agent Portal (HAP)") + runtime.Logger.Info("πŸ”— Connected to P2P network as human agent") + runtime.Logger.Info("πŸ“ Ready for collaborative reasoning and decision making") + + // Get HAP mode from environment (terminal or web) + hapMode := os.Getenv("CHORUS_HAP_MODE") + if hapMode == "" { + hapMode = "terminal" + } + + switch hapMode { + case "terminal": + return startTerminalInterface(runtime) + case "web": + return startWebInterface(runtime) + default: + return fmt.Errorf("unknown HAP mode: %s (valid: terminal, web)", hapMode) + } +} + +// startTerminalInterface provides a terminal-based human interface +func startTerminalInterface(runtime *runtime.SharedRuntime) error { + runtime.Logger.Info("πŸ’» Starting terminal interface for human interaction") + runtime.Logger.Info("🎯 Human agent ready for collaboration") + + // TODO Phase 2: Implement terminal interface + // For now, just announce presence and wait + runtime.Logger.Info("πŸ“‘ Human agent announcing presence to network...") + + // Announce human agent capabilities + go func() { + // TODO: Implement human agent announcement + runtime.Logger.Info("πŸ‘‹ Human agent presence announced") + }() + + // TODO Phase 2: Implement interactive terminal loop + // - HMMM message composition + // - Context browsing + // - Decision participation + // - Command interface + + runtime.Logger.Info("⚠️ Terminal interface not yet implemented") + runtime.Logger.Info("πŸ”„ HAP running in stub mode - P2P connectivity established") + runtime.Logger.Info("πŸ“ Next: Implement Phase 2 terminal interface") + + // For now, just keep the P2P connection alive + select {} // Block forever (will be interrupted by shutdown signals) +} + +// startWebInterface provides a web-based human interface +func startWebInterface(runtime *runtime.SharedRuntime) error { + runtime.Logger.Info("🌐 Starting web interface for human interaction") + + // TODO Phase 3: Implement web interface + // - HTTP server with WebSocket for real-time updates + // - Web forms for HMMM message composition + // - Context browser UI + // - Decision voting interface + + runtime.Logger.Info("⚠️ Web interface not yet implemented") + runtime.Logger.Info("πŸ”„ HAP running in stub mode - P2P connectivity established") + runtime.Logger.Info("πŸ“ Next: Implement Phase 3 web interface") + + // For now, fall back to terminal mode + return startTerminalInterface(runtime) +} \ No newline at end of file diff --git a/internal/runtime/agent_support.go b/internal/runtime/agent_support.go new file mode 100644 index 0000000..9f62965 --- /dev/null +++ b/internal/runtime/agent_support.go @@ -0,0 +1,181 @@ +package runtime + +import ( + "context" + "time" + + "chorus/internal/logging" + "chorus/pkg/health" + "chorus/pkg/shutdown" + "chorus/pubsub" +) + +// simpleLogger implements basic logging for shutdown and health systems +type simpleLogger struct { + logger logging.Logger +} + +func (l *simpleLogger) Info(msg string, args ...interface{}) { + l.logger.Info(msg, args...) +} + +func (l *simpleLogger) Warn(msg string, args ...interface{}) { + l.logger.Warn(msg, args...) +} + +func (l *simpleLogger) Error(msg string, args ...interface{}) { + l.logger.Error(msg, args...) +} + +// StartAgentMode runs the autonomous agent with all standard behaviors +func (r *SharedRuntime) StartAgentMode() error { + // Announce capabilities and role + go r.announceAvailability() + go r.announceCapabilitiesOnChange() + go r.announceRoleOnStartup() + + // Start status reporting + go r.statusReporter() + + 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") + + // === Comprehensive Health Monitoring & Graceful Shutdown === + shutdownManager := shutdown.NewManager(30*time.Second, &simpleLogger{logger: r.Logger}) + + healthManager := health.NewManager(r.Node.ID().ShortString(), AppVersion, &simpleLogger{logger: r.Logger}) + healthManager.SetShutdownManager(shutdownManager) + + // Register health checks + r.setupHealthChecks(healthManager) + + // Register components for graceful shutdown + r.setupGracefulShutdown(shutdownManager, healthManager) + + // Start health monitoring + if err := healthManager.Start(); err != nil { + return err + } + r.HealthManager = healthManager + r.Logger.Info("❀️ Health monitoring started") + + // Start health HTTP server + if err := healthManager.StartHTTPServer(r.Config.Network.HealthPort); err != nil { + r.Logger.Error("❌ Failed to start health HTTP server: %v", err) + } else { + r.Logger.Info("πŸ₯ Health endpoints available at http://localhost:%d/health", r.Config.Network.HealthPort) + } + + // Start shutdown manager + shutdownManager.Start() + r.ShutdownManager = shutdownManager + r.Logger.Info("πŸ›‘οΈ Graceful shutdown manager started") + + r.Logger.Info("βœ… CHORUS agent system fully operational with health monitoring") + + // Wait for graceful shutdown + shutdownManager.Wait() + r.Logger.Info("βœ… CHORUS agent system shutdown completed") + + return nil +} + +// announceAvailability broadcasts current working status for task assignment +func (r *SharedRuntime) announceAvailability() { + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + + for ; ; <-ticker.C { + currentTasks := r.TaskTracker.GetActiveTasks() + maxTasks := r.TaskTracker.GetMaxTasks() + isAvailable := len(currentTasks) < maxTasks + + status := "ready" + if len(currentTasks) >= maxTasks { + status = "busy" + } else if len(currentTasks) > 0 { + status = "working" + } + + availability := map[string]interface{}{ + "node_id": r.Node.ID().ShortString(), + "available_for_work": isAvailable, + "current_tasks": len(currentTasks), + "max_tasks": maxTasks, + "last_activity": time.Now().Unix(), + "status": status, + "timestamp": time.Now().Unix(), + } + if err := r.PubSub.PublishBzzzMessage(pubsub.AvailabilityBcast, availability); err != nil { + r.Logger.Error("❌ Failed to announce availability: %v", err) + } + } +} + +// statusReporter provides periodic status updates +func (r *SharedRuntime) statusReporter() { + ticker := time.NewTicker(60 * time.Second) + defer ticker.Stop() + + for ; ; <-ticker.C { + peers := r.Node.ConnectedPeers() + r.Logger.Info("πŸ“Š Status: %d connected peers", peers) + } +} + +// announceCapabilitiesOnChange announces capabilities when they change +func (r *SharedRuntime) announceCapabilitiesOnChange() { + // Implementation from CHORUS would go here + // For now, just log that capabilities would be announced + r.Logger.Info("πŸ“’ Agent capabilities announcement enabled") +} + +// announceRoleOnStartup announces role when the agent starts +func (r *SharedRuntime) announceRoleOnStartup() { + // Implementation from CHORUS would go here + // For now, just log that role would be announced + r.Logger.Info("🎭 Agent role announcement enabled") +} + +func (r *SharedRuntime) setupHealthChecks(healthManager *health.Manager) { + // Add BACKBEAT health check + if r.BackbeatIntegration != nil { + backbeatCheck := &health.HealthCheck{ + Name: "backbeat", + Description: "BACKBEAT timing integration health", + Interval: 30 * time.Second, + Timeout: 10 * time.Second, + Enabled: true, + Critical: false, + Checker: func(ctx context.Context) health.CheckResult { + healthInfo := r.BackbeatIntegration.GetHealth() + connected, _ := healthInfo["connected"].(bool) + + result := health.CheckResult{ + Healthy: connected, + Details: healthInfo, + Timestamp: time.Now(), + } + + if connected { + result.Message = "BACKBEAT integration healthy and connected" + } else { + result.Message = "BACKBEAT integration not connected" + } + + return result + }, + } + healthManager.RegisterCheck(backbeatCheck) + } + + // Add other health checks (P2P, DHT, etc.) + // Implementation from CHORUS would go here +} + +func (r *SharedRuntime) setupGracefulShutdown(shutdownManager *shutdown.Manager, healthManager *health.Manager) { + // Register components for graceful shutdown + // Implementation would register all components that need graceful shutdown + r.Logger.Info("πŸ›‘οΈ Graceful shutdown components registered") +} \ No newline at end of file