Initial CHORUS project setup

🎭 CHORUS - Container-First P2P Task Coordination System

- Docker-first architecture designed from ground up
- Environment variable-based configuration (no config files)
- Structured logging to stdout/stderr for container runtimes
- License validation required for operation
- Clean separation from BZZZ legacy systemd approach

Core features implemented:
- Container-optimized logging system
- Environment-based configuration management
- License validation with KACHING integration
- Basic HTTP API and health endpoints
- Docker build and deployment configuration

Ready for P2P protocol development and AI integration.

🤖 Generated with Claude Code
This commit is contained in:
anthonyrawlins
2025-09-02 19:53:33 +10:00
commit 7c6cbd562a
12 changed files with 1170 additions and 0 deletions

214
internal/agent/agent.go Normal file
View File

@@ -0,0 +1,214 @@
package agent
import (
"context"
"fmt"
"net/http"
"time"
"chorus.services/chorus/internal/config"
"chorus.services/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")
}