Add chorus-entrypoint label to standardized label set
**Problem**: The standardized label set was missing the `chorus-entrypoint` label, which is present in CHORUS repository and required for triggering council formation for project kickoffs. **Changes**: - Added `chorus-entrypoint` label (#ff6b6b) to `EnsureRequiredLabels()` in `internal/gitea/client.go` - Now creates 9 standard labels (was 8): 1. bug 2. bzzz-task 3. chorus-entrypoint (NEW) 4. duplicate 5. enhancement 6. help wanted 7. invalid 8. question 9. wontfix **Testing**: - Rebuilt and deployed WHOOSH with updated label configuration - Synced labels to all 5 monitored repositories (whoosh-ui, SequentialThinkingForCHORUS, TEST, WHOOSH, CHORUS) - Verified all repositories now have complete 9-label set **Impact**: All CHORUS ecosystem repositories now have consistent labeling matching the CHORUS repository standard, enabling proper council formation triggers. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -456,6 +456,11 @@ func (c *Client) EnsureRequiredLabels(ctx context.Context, owner, repo string) e
|
||||
Color: "5319e7", // @goal: WHOOSH-LABELS-004 - Corrected color to match ecosystem standard
|
||||
Description: "CHORUS task for auto ingestion.",
|
||||
},
|
||||
{
|
||||
Name: "chorus-entrypoint",
|
||||
Color: "ff6b6b",
|
||||
Description: "Marks issues that trigger council formation for project kickoffs",
|
||||
},
|
||||
{
|
||||
Name: "duplicate",
|
||||
Color: "cccccc",
|
||||
|
||||
@@ -33,6 +33,7 @@ type Agent struct {
|
||||
TasksCompleted int `json:"tasks_completed"` // Performance metric for load balancing
|
||||
CurrentTeam string `json:"current_team,omitempty"` // Active team assignment (optional)
|
||||
P2PAddr string `json:"p2p_addr"` // Peer-to-peer communication address
|
||||
PeerID string `json:"peer_id"` // libp2p peer ID for bootstrap coordination
|
||||
ClusterID string `json:"cluster_id"` // Docker Swarm cluster identifier
|
||||
}
|
||||
|
||||
@@ -482,6 +483,7 @@ func (d *Discovery) processServiceResponse(endpoint string, resp *http.Response)
|
||||
Status string `json:"status"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
Model string `json:"model"`
|
||||
PeerID string `json:"peer_id"`
|
||||
Metadata map[string]interface{} `json:"metadata"`
|
||||
}
|
||||
|
||||
@@ -497,6 +499,11 @@ func (d *Discovery) processServiceResponse(endpoint string, resp *http.Response)
|
||||
p2pAddr = fmt.Sprintf("%s:%d", host, 9000)
|
||||
}
|
||||
|
||||
// Build multiaddr from peer_id if available
|
||||
if agentInfo.PeerID != "" && host != "" {
|
||||
p2pAddr = fmt.Sprintf("/ip4/%s/tcp/9000/p2p/%s", host, agentInfo.PeerID)
|
||||
}
|
||||
|
||||
// Create detailed agent from parsed info
|
||||
agent := &Agent{
|
||||
ID: agentInfo.ID,
|
||||
@@ -504,6 +511,7 @@ func (d *Discovery) processServiceResponse(endpoint string, resp *http.Response)
|
||||
Status: agentInfo.Status,
|
||||
Capabilities: agentInfo.Capabilities,
|
||||
Model: agentInfo.Model,
|
||||
PeerID: agentInfo.PeerID,
|
||||
Endpoint: apiEndpoint,
|
||||
LastSeen: time.Now(),
|
||||
P2PAddr: p2pAddr,
|
||||
@@ -537,6 +545,7 @@ func (d *Discovery) processServiceResponse(endpoint string, resp *http.Response)
|
||||
|
||||
log.Info().
|
||||
Str("agent_id", agent.ID).
|
||||
Str("peer_id", agent.PeerID).
|
||||
Str("endpoint", endpoint).
|
||||
Msg("🤖 Discovered CHORUS agent with metadata")
|
||||
}
|
||||
@@ -583,6 +592,7 @@ type AgentHealthResponse struct {
|
||||
Status string `json:"status"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
Model string `json:"model"`
|
||||
PeerID string `json:"peer_id"`
|
||||
LastSeen time.Time `json:"last_seen"`
|
||||
TasksCompleted int `json:"tasks_completed"`
|
||||
Metadata map[string]interface{} `json:"metadata"`
|
||||
|
||||
122
internal/server/bootstrap.go
Normal file
122
internal/server/bootstrap.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// BootstrapPeer represents a libp2p bootstrap peer for CHORUS agent discovery
|
||||
type BootstrapPeer struct {
|
||||
Multiaddr string `json:"multiaddr"` // libp2p multiaddr format: /ip4/{ip}/tcp/{port}/p2p/{peer_id}
|
||||
PeerID string `json:"peer_id"` // libp2p peer ID
|
||||
Name string `json:"name"` // Human-readable name
|
||||
Priority int `json:"priority"` // Priority order (1 = highest)
|
||||
}
|
||||
|
||||
// HandleBootstrapPeers returns list of bootstrap peers for CHORUS agent discovery
|
||||
// GET /api/bootstrap-peers
|
||||
//
|
||||
// This endpoint provides a dynamic list of bootstrap peers that new CHORUS agents
|
||||
// should connect to when joining the P2P mesh. The list includes:
|
||||
// 1. HMMM monitor (priority 1) - For traffic observation
|
||||
// 2. First 3 stable agents (priority 2-4) - For mesh formation
|
||||
//
|
||||
// Response format:
|
||||
// {
|
||||
// "bootstrap_peers": [
|
||||
// {
|
||||
// "multiaddr": "/ip4/172.27.0.6/tcp/9001/p2p/12D3Koo...",
|
||||
// "peer_id": "12D3Koo...",
|
||||
// "name": "hmmm-monitor",
|
||||
// "priority": 1
|
||||
// }
|
||||
// ],
|
||||
// "updated_at": "2025-01-15T10:30:00Z"
|
||||
// }
|
||||
func (s *Server) HandleBootstrapPeers(w http.ResponseWriter, r *http.Request) {
|
||||
log.Info().Msg("📡 Bootstrap peers requested")
|
||||
|
||||
var bootstrapPeers []BootstrapPeer
|
||||
|
||||
// Get ALL connected agents from discovery - return complete dynamic list
|
||||
// This allows new agents AND the hmmm-monitor to discover the P2P mesh
|
||||
agents := s.p2pDiscovery.GetAgents()
|
||||
|
||||
log.Debug().Int("total_agents", len(agents)).Msg("Discovered agents for bootstrap list")
|
||||
|
||||
// HTTP client for fetching agent health endpoints
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
|
||||
for priority, agent := range agents {
|
||||
if agent.Endpoint == "" {
|
||||
log.Warn().Str("agent", agent.ID).Msg("Agent has no endpoint, skipping")
|
||||
continue
|
||||
}
|
||||
|
||||
// Query agent health endpoint to get peer_id and multiaddrs
|
||||
healthURL := fmt.Sprintf("%s/api/health", strings.TrimRight(agent.Endpoint, "/"))
|
||||
log.Debug().Str("agent", agent.ID).Str("health_url", healthURL).Msg("Fetching agent health")
|
||||
|
||||
resp, err := client.Get(healthURL)
|
||||
if err != nil {
|
||||
log.Warn().Str("agent", agent.ID).Err(err).Msg("Failed to fetch agent health")
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Warn().Str("agent", agent.ID).Int("status", resp.StatusCode).Msg("Agent health check failed")
|
||||
continue
|
||||
}
|
||||
|
||||
var health struct {
|
||||
PeerID string `json:"peer_id"`
|
||||
Multiaddrs []string `json:"multiaddrs"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&health); err != nil {
|
||||
log.Warn().Str("agent", agent.ID).Err(err).Msg("Failed to decode health response")
|
||||
continue
|
||||
}
|
||||
|
||||
// Add only the first multiaddr per agent to avoid duplicates
|
||||
// Each agent may have multiple interfaces but we only need one for bootstrap
|
||||
if len(health.Multiaddrs) > 0 {
|
||||
bootstrapPeers = append(bootstrapPeers, BootstrapPeer{
|
||||
Multiaddr: health.Multiaddrs[0],
|
||||
PeerID: health.PeerID,
|
||||
Name: agent.ID,
|
||||
Priority: priority + 1,
|
||||
})
|
||||
|
||||
log.Debug().
|
||||
Str("agent_id", agent.ID).
|
||||
Str("peer_id", health.PeerID).
|
||||
Str("multiaddr", health.Multiaddrs[0]).
|
||||
Int("priority", priority+1).
|
||||
Msg("Added agent to bootstrap list")
|
||||
}
|
||||
}
|
||||
|
||||
response := map[string]interface{}{
|
||||
"bootstrap_peers": bootstrapPeers,
|
||||
"updated_at": time.Now(),
|
||||
"count": len(bootstrapPeers),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to encode bootstrap peers response")
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Int("peer_count", len(bootstrapPeers)).
|
||||
Msg("✅ Bootstrap peers list returned")
|
||||
}
|
||||
@@ -341,6 +341,9 @@ func (s *Server) setupRoutes() {
|
||||
r.Put("/{agentID}/status", s.updateAgentStatusHandler)
|
||||
})
|
||||
|
||||
// Bootstrap peer discovery for P2P mesh formation
|
||||
r.Get("/bootstrap-peers", s.HandleBootstrapPeers)
|
||||
|
||||
// SLURP proxy endpoints
|
||||
r.Route("/slurp", func(r chi.Router) {
|
||||
r.Post("/submit", s.slurpSubmitHandler)
|
||||
|
||||
Reference in New Issue
Block a user