Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
214 lines
5.6 KiB
Go
214 lines
5.6 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"chorus/internal/config"
|
|
"chorus/internal/logging"
|
|
)
|
|
|
|
// Agent represents a CHORUS agent instance
|
|
type Agent struct {
|
|
id string
|
|
config *config.Config
|
|
logger logging.Logger
|
|
|
|
// Services
|
|
apiServer *http.Server
|
|
healthServer *http.Server
|
|
|
|
// P2P components (to be implemented)
|
|
// p2pHost host.Host
|
|
// dht *dht.DHT
|
|
// pubsub *pubsub.PubSub
|
|
}
|
|
|
|
// New creates a new CHORUS agent
|
|
func New(ctx context.Context, cfg *config.Config, logger logging.Logger) (*Agent, error) {
|
|
agent := &Agent{
|
|
id: cfg.Agent.ID,
|
|
config: cfg,
|
|
logger: logger,
|
|
}
|
|
|
|
// Initialize HTTP servers
|
|
if err := agent.initHTTPServers(); err != nil {
|
|
return nil, fmt.Errorf("failed to initialize HTTP servers: %w", err)
|
|
}
|
|
|
|
// TODO: Initialize P2P components
|
|
// TODO: Initialize task coordination
|
|
// TODO: Initialize AI integration
|
|
|
|
return agent, nil
|
|
}
|
|
|
|
// Start starts all agent services
|
|
func (a *Agent) Start() error {
|
|
a.logger.Info("🚀 Starting CHORUS agent services...")
|
|
|
|
// Start API server
|
|
go func() {
|
|
a.logger.Info("🌐 Starting API server on :%d", a.config.Network.APIPort)
|
|
if err := a.apiServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
a.logger.Error("❌ API server error: %v", err)
|
|
}
|
|
}()
|
|
|
|
// Start health server
|
|
go func() {
|
|
a.logger.Info("🏥 Starting health server on :%d", a.config.Network.HealthPort)
|
|
if err := a.healthServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
a.logger.Error("❌ Health server error: %v", err)
|
|
}
|
|
}()
|
|
|
|
// TODO: Start P2P services
|
|
// TODO: Start task coordination
|
|
// TODO: Connect to DHT network
|
|
|
|
a.logger.Info("✅ All CHORUS agent services started")
|
|
return nil
|
|
}
|
|
|
|
// Stop gracefully stops all agent services
|
|
func (a *Agent) Stop(ctx context.Context) error {
|
|
a.logger.Info("🛑 Stopping CHORUS agent services...")
|
|
|
|
// Stop HTTP servers
|
|
if err := a.apiServer.Shutdown(ctx); err != nil {
|
|
a.logger.Error("⚠️ Error stopping API server: %v", err)
|
|
}
|
|
|
|
if err := a.healthServer.Shutdown(ctx); err != nil {
|
|
a.logger.Error("⚠️ Error stopping health server: %v", err)
|
|
}
|
|
|
|
// TODO: Stop P2P services
|
|
// TODO: Stop task coordination
|
|
// TODO: Disconnect from DHT network
|
|
|
|
a.logger.Info("✅ CHORUS agent services stopped")
|
|
return nil
|
|
}
|
|
|
|
// ID returns the agent ID
|
|
func (a *Agent) ID() string {
|
|
return a.id
|
|
}
|
|
|
|
// P2PAddress returns the P2P address (placeholder)
|
|
func (a *Agent) P2PAddress() string {
|
|
// TODO: Return actual P2P address when P2P is implemented
|
|
return fmt.Sprintf("/ip4/%s/tcp/%d/p2p/%s", a.config.Network.BindAddr, a.config.Network.P2PPort, a.id)
|
|
}
|
|
|
|
// initHTTPServers initializes the HTTP servers for API and health endpoints
|
|
func (a *Agent) initHTTPServers() error {
|
|
// API server
|
|
apiMux := http.NewServeMux()
|
|
apiMux.HandleFunc("/", a.handleRoot)
|
|
apiMux.HandleFunc("/agent/info", a.handleAgentInfo)
|
|
apiMux.HandleFunc("/agent/tasks", a.handleTasks)
|
|
|
|
a.apiServer = &http.Server{
|
|
Addr: fmt.Sprintf("%s:%d", a.config.Network.BindAddr, a.config.Network.APIPort),
|
|
Handler: apiMux,
|
|
ReadTimeout: 15 * time.Second,
|
|
WriteTimeout: 15 * time.Second,
|
|
IdleTimeout: 60 * time.Second,
|
|
}
|
|
|
|
// Health server
|
|
healthMux := http.NewServeMux()
|
|
healthMux.HandleFunc("/health", a.handleHealth)
|
|
healthMux.HandleFunc("/health/ready", a.handleReady)
|
|
healthMux.HandleFunc("/health/live", a.handleLive)
|
|
|
|
a.healthServer = &http.Server{
|
|
Addr: fmt.Sprintf("%s:%d", a.config.Network.BindAddr, a.config.Network.HealthPort),
|
|
Handler: healthMux,
|
|
ReadTimeout: 5 * time.Second,
|
|
WriteTimeout: 5 * time.Second,
|
|
IdleTimeout: 30 * time.Second,
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// HTTP handler implementations
|
|
|
|
func (a *Agent) handleRoot(w http.ResponseWriter, r *http.Request) {
|
|
response := map[string]interface{}{
|
|
"service": "CHORUS",
|
|
"version": "0.1.0-dev",
|
|
"agent_id": a.id,
|
|
"status": "running",
|
|
"endpoints": map[string]string{
|
|
"agent_info": "/agent/info",
|
|
"tasks": "/agent/tasks",
|
|
"health": fmt.Sprintf("http://localhost:%d/health", a.config.Network.HealthPort),
|
|
},
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
// Simple JSON response without external dependency
|
|
fmt.Fprintf(w, `{
|
|
"service": "%s",
|
|
"version": "0.1.0-dev",
|
|
"agent_id": "%s",
|
|
"status": "running"
|
|
}`, "CHORUS", a.id)
|
|
}
|
|
|
|
func (a *Agent) handleAgentInfo(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
fmt.Fprintf(w, `{
|
|
"agent_id": "%s",
|
|
"specialization": "%s",
|
|
"max_tasks": %d,
|
|
"capabilities": %q
|
|
}`, a.id, a.config.Agent.Specialization, a.config.Agent.MaxTasks, a.config.Agent.Capabilities)
|
|
}
|
|
|
|
func (a *Agent) handleTasks(w http.ResponseWriter, r *http.Request) {
|
|
// TODO: Implement task management
|
|
w.Header().Set("Content-Type", "application/json")
|
|
fmt.Fprintf(w, `{
|
|
"active_tasks": [],
|
|
"completed_tasks": [],
|
|
"available_for_tasks": true
|
|
}`)
|
|
}
|
|
|
|
func (a *Agent) handleHealth(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprintf(w, `{
|
|
"status": "healthy",
|
|
"agent_id": "%s",
|
|
"timestamp": "%s",
|
|
"checks": {
|
|
"license": "valid",
|
|
"api_server": "running",
|
|
"p2p": "not_implemented"
|
|
}
|
|
}`, a.id, time.Now().UTC().Format(time.RFC3339))
|
|
}
|
|
|
|
func (a *Agent) handleReady(w http.ResponseWriter, r *http.Request) {
|
|
// Kubernetes readiness probe
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprint(w, "ready")
|
|
}
|
|
|
|
func (a *Agent) handleLive(w http.ResponseWriter, r *http.Request) {
|
|
// Kubernetes liveness probe
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprint(w, "live")
|
|
} |