mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
926 lines
25 KiB
Go
926 lines
25 KiB
Go
// Agentic Consensus over ZAP Protocol
|
|
//
|
|
// Demonstrates high-performance AI agent consensus using the ZAP protocol,
|
|
// featuring dynamic capability discovery, tool registration, and MCP bridging.
|
|
//
|
|
// Architecture:
|
|
//
|
|
// ┌────────────────────────────────────────────────────────────────┐
|
|
// │ AGENTIC CONSENSUS SWARM │
|
|
// ├────────────────────────────────────────────────────────────────┤
|
|
// │ │
|
|
// │ ┌─────────┐ ZAP ┌─────────┐ ZAP ┌─────────┐ │
|
|
// │ │ Claude │◄──────────►│ GPT │◄──────────►│ Qwen │ │
|
|
// │ │ Agent │ │ Agent │ │ Agent │ │
|
|
// │ └────┬────┘ └────┬────┘ └────┬────┘ │
|
|
// │ │ │ │ │
|
|
// │ │ ZAP │ ZAP │ │
|
|
// │ └──────────┬───────────┴──────────┬───────────┘ │
|
|
// │ │ │ │
|
|
// │ ┌─────┴────┐ ┌──────┴─────┐ │
|
|
// │ │ Copilot │◄────────►│ Gemini │ │
|
|
// │ │ Agent │ ZAP │ (Arbiter) │ │
|
|
// │ └──────────┘ └────────────┘ │
|
|
// │ │
|
|
// └──────────────────────────────────────────────────────────────┘
|
|
//
|
|
// Features:
|
|
// - Dynamic agent discovery via mDNS + ZAP handshake
|
|
// - Capability registration and tool exposure
|
|
// - Consensus on multi-agent task execution
|
|
// - MCP<>ZAP protocol bridging
|
|
// - Zero-copy message passing for high performance
|
|
//
|
|
// Usage:
|
|
//
|
|
// go run main.go [query]
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
// ZAP Wire Format (compatible with github.com/luxfi/zap)
|
|
const (
|
|
ZAPMagic = 0x5041_5A00 // "ZAP\0" little-endian
|
|
ZAPVersion = 1
|
|
)
|
|
|
|
// Message types for agentic consensus
|
|
const (
|
|
MsgTypeHandshake uint16 = 0 // Initial connection
|
|
MsgTypeCapabilities uint16 = 1 // Capability announcement
|
|
MsgTypeQuery uint16 = 10 // Task query
|
|
MsgTypeResponse uint16 = 11 // Agent response
|
|
MsgTypeVote uint16 = 12 // Vote for response
|
|
MsgTypeSynthesis uint16 = 13 // Final synthesis
|
|
MsgTypeToolCall uint16 = 20 // Tool invocation
|
|
MsgTypeToolResult uint16 = 21 // Tool result
|
|
)
|
|
|
|
// Capability represents an agent's exposed tool/capability
|
|
type Capability struct {
|
|
Name string
|
|
Description string
|
|
Parameters []string
|
|
}
|
|
|
|
// AgentConfig defines agent properties
|
|
type AgentConfig struct {
|
|
ID int
|
|
Name string
|
|
Model string
|
|
Capabilities []Capability
|
|
IsArbiter bool // Gemini is the arbiter
|
|
}
|
|
|
|
// Default agent configurations
|
|
var DefaultAgents = []AgentConfig{
|
|
{0, "Claude", "claude-sonnet-4-20250514", []Capability{
|
|
{"analyze", "Deep code analysis and reasoning", []string{"code", "context"}},
|
|
{"review", "Security and quality review", []string{"code", "rules"}},
|
|
}, false},
|
|
{1, "GPT", "gpt-4o", []Capability{
|
|
{"generate", "Code generation and completion", []string{"prompt", "language"}},
|
|
{"explain", "Technical explanations", []string{"topic", "level"}},
|
|
}, false},
|
|
{2, "Copilot", "gpt-4o", []Capability{
|
|
{"complete", "Inline code completion", []string{"prefix", "suffix"}},
|
|
{"suggest", "Improvement suggestions", []string{"code", "goal"}},
|
|
}, false},
|
|
{3, "Qwen", "qwen-max", []Capability{
|
|
{"translate", "Multi-language translation", []string{"text", "target"}},
|
|
{"reason", "Chain-of-thought reasoning", []string{"problem"}},
|
|
}, false},
|
|
{4, "Gemini", "gemini-1.5-pro", []Capability{
|
|
{"synthesize", "Multi-source synthesis", []string{"inputs"}},
|
|
{"arbitrate", "Consensus arbitration", []string{"votes", "responses"}},
|
|
}, true}, // Arbiter
|
|
}
|
|
|
|
// Agent represents a ZAP-connected AI agent
|
|
type Agent struct {
|
|
config AgentConfig
|
|
nodeID string
|
|
port int
|
|
|
|
// Networking
|
|
listener net.Listener
|
|
conns map[string]*AgentConn
|
|
connsMu sync.RWMutex
|
|
|
|
// Capability registry
|
|
peerCaps map[string][]Capability
|
|
capsMu sync.RWMutex
|
|
|
|
// Consensus state
|
|
responses map[uint64]map[int]string
|
|
votes map[uint64]map[int][]int
|
|
synthesis map[uint64]string
|
|
stateMu sync.Mutex
|
|
|
|
// Stats
|
|
queryCount atomic.Int64
|
|
responseCount atomic.Int64
|
|
voteCount atomic.Int64
|
|
toolCallCount atomic.Int64
|
|
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
wg sync.WaitGroup
|
|
logger *slog.Logger
|
|
}
|
|
|
|
type AgentConn struct {
|
|
nodeID string
|
|
conn net.Conn
|
|
caps []Capability
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// NewAgent creates a new ZAP-enabled agent
|
|
func NewAgent(config AgentConfig, port int, logger *slog.Logger) *Agent {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
return &Agent{
|
|
config: config,
|
|
nodeID: fmt.Sprintf("agent-%s-%d", strings.ToLower(config.Name), config.ID),
|
|
port: port,
|
|
conns: make(map[string]*AgentConn),
|
|
peerCaps: make(map[string][]Capability),
|
|
responses: make(map[uint64]map[int]string),
|
|
votes: make(map[uint64]map[int][]int),
|
|
synthesis: make(map[uint64]string),
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// Start begins listening for ZAP connections
|
|
func (a *Agent) Start() error {
|
|
listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", a.port))
|
|
if err != nil {
|
|
return fmt.Errorf("listen failed: %w", err)
|
|
}
|
|
a.listener = listener
|
|
|
|
a.wg.Add(1)
|
|
go a.acceptLoop()
|
|
|
|
a.logger.Info("Agent started",
|
|
"name", a.config.Name,
|
|
"model", a.config.Model,
|
|
"port", a.port,
|
|
"capabilities", len(a.config.Capabilities),
|
|
"arbiter", a.config.IsArbiter,
|
|
)
|
|
return nil
|
|
}
|
|
|
|
// Stop shuts down the agent
|
|
func (a *Agent) Stop() {
|
|
a.cancel()
|
|
if a.listener != nil {
|
|
a.listener.Close()
|
|
}
|
|
a.connsMu.Lock()
|
|
for _, c := range a.conns {
|
|
c.conn.Close()
|
|
}
|
|
a.connsMu.Unlock()
|
|
a.wg.Wait()
|
|
}
|
|
|
|
func (a *Agent) acceptLoop() {
|
|
defer a.wg.Done()
|
|
for {
|
|
conn, err := a.listener.Accept()
|
|
if err != nil {
|
|
select {
|
|
case <-a.ctx.Done():
|
|
return
|
|
default:
|
|
continue
|
|
}
|
|
}
|
|
a.wg.Add(1)
|
|
go a.handleConn(conn, false)
|
|
}
|
|
}
|
|
|
|
func (a *Agent) handleConn(netConn net.Conn, initiator bool) {
|
|
defer a.wg.Done()
|
|
defer netConn.Close()
|
|
|
|
// Handshake
|
|
peerID, err := a.doHandshake(netConn, initiator)
|
|
if err != nil {
|
|
a.logger.Debug("Handshake failed", "error", err)
|
|
return
|
|
}
|
|
|
|
ac := &AgentConn{nodeID: peerID, conn: netConn}
|
|
a.connsMu.Lock()
|
|
a.conns[peerID] = ac
|
|
a.connsMu.Unlock()
|
|
|
|
// Exchange capabilities
|
|
a.sendCapabilities(ac)
|
|
|
|
defer func() {
|
|
a.connsMu.Lock()
|
|
delete(a.conns, peerID)
|
|
a.connsMu.Unlock()
|
|
a.capsMu.Lock()
|
|
delete(a.peerCaps, peerID)
|
|
a.capsMu.Unlock()
|
|
a.logger.Info("Peer disconnected", "peer", peerID)
|
|
}()
|
|
|
|
a.logger.Info("Peer connected", "peer", peerID)
|
|
|
|
// Message loop
|
|
for {
|
|
select {
|
|
case <-a.ctx.Done():
|
|
return
|
|
default:
|
|
}
|
|
|
|
msg, err := a.readMessage(netConn)
|
|
if err != nil {
|
|
return
|
|
}
|
|
a.handleMessage(peerID, msg)
|
|
}
|
|
}
|
|
|
|
// ConnectTo establishes connection to another agent
|
|
func (a *Agent) ConnectTo(addr string) error {
|
|
netConn, err := net.DialTimeout("tcp", addr, 5*time.Second)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
a.wg.Add(1)
|
|
go a.handleConn(netConn, true)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *Agent) doHandshake(conn net.Conn, initiator bool) (string, error) {
|
|
if initiator {
|
|
if err := a.sendHandshake(conn); err != nil {
|
|
return "", err
|
|
}
|
|
return a.recvHandshake(conn)
|
|
}
|
|
peerID, err := a.recvHandshake(conn)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return peerID, a.sendHandshake(conn)
|
|
}
|
|
|
|
func (a *Agent) sendHandshake(conn net.Conn) error {
|
|
// ZAP header + node ID
|
|
buf := make([]byte, 128)
|
|
|
|
// Magic
|
|
binary.LittleEndian.PutUint32(buf[0:4], ZAPMagic)
|
|
// Version
|
|
buf[4] = ZAPVersion
|
|
// Flags (MsgTypeHandshake << 8)
|
|
binary.LittleEndian.PutUint16(buf[6:8], MsgTypeHandshake<<8)
|
|
|
|
// Node ID as payload
|
|
idBytes := []byte(a.nodeID)
|
|
copy(buf[16:], idBytes)
|
|
buf[16+60] = byte(len(idBytes))
|
|
|
|
return a.writeFrame(conn, buf[:80])
|
|
}
|
|
|
|
func (a *Agent) recvHandshake(conn net.Conn) (string, error) {
|
|
buf, err := a.readFrame(conn)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Verify magic
|
|
magic := binary.LittleEndian.Uint32(buf[0:4])
|
|
if magic != ZAPMagic {
|
|
return "", fmt.Errorf("invalid ZAP magic: %x", magic)
|
|
}
|
|
|
|
// Extract node ID
|
|
idLen := int(buf[16+60])
|
|
if idLen > 60 || idLen == 0 {
|
|
return "", fmt.Errorf("invalid node ID length: %d", idLen)
|
|
}
|
|
|
|
return string(buf[16 : 16+idLen]), nil
|
|
}
|
|
|
|
func (a *Agent) sendCapabilities(ac *AgentConn) {
|
|
// Build capabilities message
|
|
buf := make([]byte, 1024)
|
|
|
|
// Header
|
|
binary.LittleEndian.PutUint32(buf[0:4], ZAPMagic)
|
|
buf[4] = ZAPVersion
|
|
binary.LittleEndian.PutUint16(buf[6:8], MsgTypeCapabilities<<8)
|
|
|
|
// Encode capabilities as simple format
|
|
offset := 16
|
|
buf[offset] = byte(len(a.config.Capabilities))
|
|
offset++
|
|
|
|
for _, cap := range a.config.Capabilities {
|
|
nameBytes := []byte(cap.Name)
|
|
buf[offset] = byte(len(nameBytes))
|
|
offset++
|
|
copy(buf[offset:], nameBytes)
|
|
offset += len(nameBytes)
|
|
|
|
descBytes := []byte(cap.Description)
|
|
buf[offset] = byte(len(descBytes))
|
|
offset++
|
|
copy(buf[offset:], descBytes)
|
|
offset += len(descBytes)
|
|
}
|
|
|
|
ac.mu.Lock()
|
|
a.writeFrame(ac.conn, buf[:offset])
|
|
ac.mu.Unlock()
|
|
}
|
|
|
|
func (a *Agent) handleMessage(from string, msg []byte) {
|
|
if len(msg) < 8 {
|
|
return
|
|
}
|
|
|
|
msgType := binary.LittleEndian.Uint16(msg[6:8]) >> 8
|
|
|
|
switch msgType {
|
|
case MsgTypeCapabilities:
|
|
a.handleCapabilities(from, msg)
|
|
case MsgTypeQuery:
|
|
a.handleQuery(from, msg)
|
|
case MsgTypeResponse:
|
|
a.handleResponse(from, msg)
|
|
case MsgTypeVote:
|
|
a.handleVote(from, msg)
|
|
case MsgTypeSynthesis:
|
|
a.handleSynthesis(from, msg)
|
|
case MsgTypeToolCall:
|
|
a.handleToolCall(from, msg)
|
|
}
|
|
}
|
|
|
|
func (a *Agent) handleCapabilities(from string, msg []byte) {
|
|
if len(msg) < 17 {
|
|
return
|
|
}
|
|
|
|
offset := 16
|
|
capCount := int(msg[offset])
|
|
offset++
|
|
|
|
caps := make([]Capability, 0, capCount)
|
|
for i := 0; i < capCount && offset < len(msg); i++ {
|
|
nameLen := int(msg[offset])
|
|
offset++
|
|
if offset+nameLen > len(msg) {
|
|
break
|
|
}
|
|
name := string(msg[offset : offset+nameLen])
|
|
offset += nameLen
|
|
|
|
descLen := int(msg[offset])
|
|
offset++
|
|
if offset+descLen > len(msg) {
|
|
break
|
|
}
|
|
desc := string(msg[offset : offset+descLen])
|
|
offset += descLen
|
|
|
|
caps = append(caps, Capability{Name: name, Description: desc})
|
|
}
|
|
|
|
a.capsMu.Lock()
|
|
a.peerCaps[from] = caps
|
|
a.capsMu.Unlock()
|
|
|
|
a.logger.Info("Received capabilities", "from", from, "caps", len(caps))
|
|
}
|
|
|
|
func (a *Agent) handleQuery(from string, msg []byte) {
|
|
a.queryCount.Add(1)
|
|
|
|
if len(msg) < 24 {
|
|
return
|
|
}
|
|
|
|
queryID := binary.LittleEndian.Uint64(msg[16:24])
|
|
queryLen := int(binary.LittleEndian.Uint32(msg[24:28]))
|
|
if 28+queryLen > len(msg) {
|
|
queryLen = len(msg) - 28
|
|
}
|
|
query := string(msg[28 : 28+queryLen])
|
|
|
|
a.logger.Info("Received query", "from", from, "queryID", queryID, "query", truncate(query, 50))
|
|
|
|
// Generate response
|
|
go func() {
|
|
response := a.generateResponse(query)
|
|
a.broadcastResponse(queryID, response)
|
|
}()
|
|
}
|
|
|
|
func (a *Agent) handleResponse(from string, msg []byte) {
|
|
a.responseCount.Add(1)
|
|
|
|
if len(msg) < 28 {
|
|
return
|
|
}
|
|
|
|
queryID := binary.LittleEndian.Uint64(msg[16:24])
|
|
agentID := int(binary.LittleEndian.Uint32(msg[24:28]))
|
|
respLen := int(binary.LittleEndian.Uint32(msg[28:32]))
|
|
if 32+respLen > len(msg) {
|
|
respLen = len(msg) - 32
|
|
}
|
|
response := string(msg[32 : 32+respLen])
|
|
|
|
a.stateMu.Lock()
|
|
if a.responses[queryID] == nil {
|
|
a.responses[queryID] = make(map[int]string)
|
|
}
|
|
a.responses[queryID][agentID] = response
|
|
count := len(a.responses[queryID])
|
|
a.stateMu.Unlock()
|
|
|
|
a.logger.Info("Received response", "from", from, "agentID", agentID, "responses", count)
|
|
|
|
// Vote when we have enough responses
|
|
if count >= 4 {
|
|
go a.castVote(queryID)
|
|
}
|
|
}
|
|
|
|
func (a *Agent) handleVote(from string, msg []byte) {
|
|
a.voteCount.Add(1)
|
|
|
|
if len(msg) < 32 {
|
|
return
|
|
}
|
|
|
|
queryID := binary.LittleEndian.Uint64(msg[16:24])
|
|
voterID := int(binary.LittleEndian.Uint32(msg[24:28]))
|
|
voteFor := int(binary.LittleEndian.Uint32(msg[28:32]))
|
|
|
|
a.stateMu.Lock()
|
|
if a.votes[queryID] == nil {
|
|
a.votes[queryID] = make(map[int][]int)
|
|
}
|
|
a.votes[queryID][voteFor] = append(a.votes[queryID][voteFor], voterID)
|
|
totalVotes := 0
|
|
for _, v := range a.votes[queryID] {
|
|
totalVotes += len(v)
|
|
}
|
|
a.stateMu.Unlock()
|
|
|
|
a.logger.Info("Received vote", "from", from, "voter", voterID, "for", voteFor, "total", totalVotes)
|
|
|
|
// Arbiter synthesizes when votes are in
|
|
if a.config.IsArbiter && totalVotes >= 4 {
|
|
go a.synthesizeConsensus(queryID)
|
|
}
|
|
}
|
|
|
|
func (a *Agent) handleSynthesis(from string, msg []byte) {
|
|
if len(msg) < 28 {
|
|
return
|
|
}
|
|
|
|
queryID := binary.LittleEndian.Uint64(msg[16:24])
|
|
synthLen := int(binary.LittleEndian.Uint32(msg[24:28]))
|
|
if 28+synthLen > len(msg) {
|
|
synthLen = len(msg) - 28
|
|
}
|
|
synthesis := string(msg[28 : 28+synthLen])
|
|
|
|
a.stateMu.Lock()
|
|
a.synthesis[queryID] = synthesis
|
|
a.stateMu.Unlock()
|
|
|
|
fmt.Printf("\n%s\n", synthesis)
|
|
}
|
|
|
|
func (a *Agent) handleToolCall(from string, msg []byte) {
|
|
a.toolCallCount.Add(1)
|
|
// Tool execution would go here
|
|
a.logger.Info("Tool call received", "from", from)
|
|
}
|
|
|
|
func (a *Agent) generateResponse(query string) string {
|
|
// Simulate different agent personalities
|
|
personalities := map[string]string{
|
|
"Claude": "Analyzing with careful reasoning: ",
|
|
"GPT": "Based on comprehensive analysis: ",
|
|
"Copilot": "From a practical coding perspective: ",
|
|
"Qwen": "Through methodical examination: ",
|
|
"Gemini": "Synthesizing multiple viewpoints: ",
|
|
}
|
|
|
|
time.Sleep(time.Duration(50+a.config.ID*30) * time.Millisecond)
|
|
|
|
prefix := personalities[a.config.Name]
|
|
caps := make([]string, 0, len(a.config.Capabilities))
|
|
for _, c := range a.config.Capabilities {
|
|
caps = append(caps, c.Name)
|
|
}
|
|
|
|
return fmt.Sprintf("%sFor '%s', I would approach this using my capabilities (%s). "+
|
|
"Key considerations include accuracy, performance, and maintainability. "+
|
|
"Agent %s recommends a balanced approach considering all factors.",
|
|
prefix, truncate(query, 30), strings.Join(caps, ", "), a.config.Name)
|
|
}
|
|
|
|
func (a *Agent) broadcastResponse(queryID uint64, response string) {
|
|
buf := make([]byte, 4096)
|
|
|
|
binary.LittleEndian.PutUint32(buf[0:4], ZAPMagic)
|
|
buf[4] = ZAPVersion
|
|
binary.LittleEndian.PutUint16(buf[6:8], MsgTypeResponse<<8)
|
|
|
|
binary.LittleEndian.PutUint64(buf[16:24], queryID)
|
|
binary.LittleEndian.PutUint32(buf[24:28], uint32(a.config.ID))
|
|
|
|
respBytes := []byte(response)
|
|
binary.LittleEndian.PutUint32(buf[28:32], uint32(len(respBytes)))
|
|
copy(buf[32:], respBytes)
|
|
|
|
a.broadcast(buf[:32+len(respBytes)])
|
|
|
|
// Store own response
|
|
a.stateMu.Lock()
|
|
if a.responses[queryID] == nil {
|
|
a.responses[queryID] = make(map[int]string)
|
|
}
|
|
a.responses[queryID][a.config.ID] = response
|
|
a.stateMu.Unlock()
|
|
}
|
|
|
|
func (a *Agent) castVote(queryID uint64) {
|
|
a.stateMu.Lock()
|
|
responses := make(map[int]string)
|
|
for k, v := range a.responses[queryID] {
|
|
responses[k] = v
|
|
}
|
|
a.stateMu.Unlock()
|
|
|
|
// Vote for longest response (heuristic)
|
|
bestAgent := -1
|
|
bestLen := 0
|
|
for agentID, resp := range responses {
|
|
if len(resp) > bestLen {
|
|
bestLen = len(resp)
|
|
bestAgent = agentID
|
|
}
|
|
}
|
|
if bestAgent == -1 {
|
|
return
|
|
}
|
|
|
|
buf := make([]byte, 64)
|
|
binary.LittleEndian.PutUint32(buf[0:4], ZAPMagic)
|
|
buf[4] = ZAPVersion
|
|
binary.LittleEndian.PutUint16(buf[6:8], MsgTypeVote<<8)
|
|
|
|
binary.LittleEndian.PutUint64(buf[16:24], queryID)
|
|
binary.LittleEndian.PutUint32(buf[24:28], uint32(a.config.ID))
|
|
binary.LittleEndian.PutUint32(buf[28:32], uint32(bestAgent))
|
|
|
|
a.broadcast(buf[:32])
|
|
|
|
// Record own vote
|
|
a.stateMu.Lock()
|
|
if a.votes[queryID] == nil {
|
|
a.votes[queryID] = make(map[int][]int)
|
|
}
|
|
a.votes[queryID][bestAgent] = append(a.votes[queryID][bestAgent], a.config.ID)
|
|
a.stateMu.Unlock()
|
|
}
|
|
|
|
func (a *Agent) synthesizeConsensus(queryID uint64) {
|
|
a.stateMu.Lock()
|
|
if _, exists := a.synthesis[queryID]; exists {
|
|
a.stateMu.Unlock()
|
|
return
|
|
}
|
|
a.synthesis[queryID] = "pending"
|
|
|
|
responses := make(map[int]string)
|
|
for k, v := range a.responses[queryID] {
|
|
responses[k] = v
|
|
}
|
|
votes := make(map[int][]int)
|
|
for k, v := range a.votes[queryID] {
|
|
votes[k] = v
|
|
}
|
|
a.stateMu.Unlock()
|
|
|
|
// Find winner
|
|
winner := -1
|
|
maxVotes := 0
|
|
for agentID, voters := range votes {
|
|
if len(voters) > maxVotes {
|
|
maxVotes = len(voters)
|
|
winner = agentID
|
|
}
|
|
}
|
|
|
|
// Build synthesis
|
|
var sb strings.Builder
|
|
sb.WriteString("═══════════════════════════════════════════════════════════════\n")
|
|
sb.WriteString(" AGENTIC CONSENSUS REACHED \n")
|
|
sb.WriteString("═══════════════════════════════════════════════════════════════\n\n")
|
|
|
|
sb.WriteString(fmt.Sprintf("Winner: %s (Agent %d) with %d votes\n\n",
|
|
DefaultAgents[winner].Name, winner, maxVotes))
|
|
|
|
sb.WriteString("Vote Distribution:\n")
|
|
for agentID, voters := range votes {
|
|
voterNames := make([]string, len(voters))
|
|
for i, v := range voters {
|
|
voterNames[i] = DefaultAgents[v].Name
|
|
}
|
|
sb.WriteString(fmt.Sprintf(" • %s: %d votes (%s)\n",
|
|
DefaultAgents[agentID].Name, len(voters), strings.Join(voterNames, ", ")))
|
|
}
|
|
|
|
sb.WriteString(fmt.Sprintf("\nWinning Response:\n%s\n", responses[winner]))
|
|
|
|
sb.WriteString("\n───────────────────────────────────────────────────────────────\n")
|
|
sb.WriteString("Synthesized by Gemini (Arbiter) via ZAP Protocol\n")
|
|
|
|
synthesis := sb.String()
|
|
a.stateMu.Lock()
|
|
a.synthesis[queryID] = synthesis
|
|
a.stateMu.Unlock()
|
|
|
|
// Broadcast synthesis
|
|
buf := make([]byte, 8192)
|
|
binary.LittleEndian.PutUint32(buf[0:4], ZAPMagic)
|
|
buf[4] = ZAPVersion
|
|
binary.LittleEndian.PutUint16(buf[6:8], MsgTypeSynthesis<<8)
|
|
|
|
binary.LittleEndian.PutUint64(buf[16:24], queryID)
|
|
synthBytes := []byte(synthesis)
|
|
binary.LittleEndian.PutUint32(buf[24:28], uint32(len(synthBytes)))
|
|
copy(buf[28:], synthBytes)
|
|
|
|
a.broadcast(buf[:28+len(synthBytes)])
|
|
}
|
|
|
|
// BroadcastQuery sends a query to all connected agents
|
|
func (a *Agent) BroadcastQuery(queryID uint64, query string) {
|
|
buf := make([]byte, 2048)
|
|
|
|
binary.LittleEndian.PutUint32(buf[0:4], ZAPMagic)
|
|
buf[4] = ZAPVersion
|
|
binary.LittleEndian.PutUint16(buf[6:8], MsgTypeQuery<<8)
|
|
|
|
binary.LittleEndian.PutUint64(buf[16:24], queryID)
|
|
queryBytes := []byte(query)
|
|
binary.LittleEndian.PutUint32(buf[24:28], uint32(len(queryBytes)))
|
|
copy(buf[28:], queryBytes)
|
|
|
|
a.broadcast(buf[:28+len(queryBytes)])
|
|
}
|
|
|
|
func (a *Agent) broadcast(data []byte) {
|
|
a.connsMu.RLock()
|
|
conns := make([]*AgentConn, 0, len(a.conns))
|
|
for _, c := range a.conns {
|
|
conns = append(conns, c)
|
|
}
|
|
a.connsMu.RUnlock()
|
|
|
|
for _, c := range conns {
|
|
c.mu.Lock()
|
|
a.writeFrame(c.conn, data)
|
|
c.mu.Unlock()
|
|
}
|
|
}
|
|
|
|
// GetPeerCapabilities returns discovered capabilities from all peers
|
|
func (a *Agent) GetPeerCapabilities() map[string][]Capability {
|
|
a.capsMu.RLock()
|
|
defer a.capsMu.RUnlock()
|
|
|
|
result := make(map[string][]Capability)
|
|
for k, v := range a.peerCaps {
|
|
result[k] = v
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetConnectedPeers returns connected peer node IDs
|
|
func (a *Agent) GetConnectedPeers() []string {
|
|
a.connsMu.RLock()
|
|
defer a.connsMu.RUnlock()
|
|
|
|
peers := make([]string, 0, len(a.conns))
|
|
for id := range a.conns {
|
|
peers = append(peers, id)
|
|
}
|
|
return peers
|
|
}
|
|
|
|
// GetSynthesis retrieves synthesis for a query
|
|
func (a *Agent) GetSynthesis(queryID uint64) (string, bool) {
|
|
a.stateMu.Lock()
|
|
defer a.stateMu.Unlock()
|
|
s, ok := a.synthesis[queryID]
|
|
return s, ok && s != "pending"
|
|
}
|
|
|
|
// Wire format helpers
|
|
func (a *Agent) writeFrame(w io.Writer, data []byte) error {
|
|
lenBuf := make([]byte, 4)
|
|
binary.LittleEndian.PutUint32(lenBuf, uint32(len(data)))
|
|
if _, err := w.Write(lenBuf); err != nil {
|
|
return err
|
|
}
|
|
_, err := w.Write(data)
|
|
return err
|
|
}
|
|
|
|
func (a *Agent) readFrame(r io.Reader) ([]byte, error) {
|
|
lenBuf := make([]byte, 4)
|
|
if _, err := io.ReadFull(r, lenBuf); err != nil {
|
|
return nil, err
|
|
}
|
|
length := binary.LittleEndian.Uint32(lenBuf)
|
|
if length > 1024*1024 {
|
|
return nil, fmt.Errorf("frame too large: %d", length)
|
|
}
|
|
data := make([]byte, length)
|
|
if _, err := io.ReadFull(r, data); err != nil {
|
|
return nil, err
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
func (a *Agent) readMessage(r io.Reader) ([]byte, error) {
|
|
return a.readFrame(r)
|
|
}
|
|
|
|
func truncate(s string, n int) string {
|
|
if len(s) <= n {
|
|
return s
|
|
}
|
|
return s[:n] + "..."
|
|
}
|
|
|
|
func main() {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
|
Level: slog.LevelInfo,
|
|
}))
|
|
|
|
fmt.Println("╔═══════════════════════════════════════════════════════════════╗")
|
|
fmt.Println("║ AGENTIC CONSENSUS OVER ZAP PROTOCOL ║")
|
|
fmt.Println("║ High-Performance Multi-Agent AI Consensus ║")
|
|
fmt.Println("╚═══════════════════════════════════════════════════════════════╝")
|
|
fmt.Println()
|
|
|
|
// Display agent info
|
|
fmt.Println("Initializing Agent Swarm:")
|
|
for _, cfg := range DefaultAgents {
|
|
caps := make([]string, len(cfg.Capabilities))
|
|
for i, c := range cfg.Capabilities {
|
|
caps[i] = c.Name
|
|
}
|
|
role := ""
|
|
if cfg.IsArbiter {
|
|
role = " [ARBITER]"
|
|
}
|
|
fmt.Printf(" [%d] %s (%s)%s\n Capabilities: %s\n",
|
|
cfg.ID, cfg.Name, cfg.Model, role, strings.Join(caps, ", "))
|
|
}
|
|
fmt.Println()
|
|
|
|
// Create agents
|
|
agents := make([]*Agent, len(DefaultAgents))
|
|
basePort := 21000
|
|
|
|
for i, cfg := range DefaultAgents {
|
|
agents[i] = NewAgent(cfg, basePort+i, logger)
|
|
}
|
|
|
|
// Start all agents
|
|
for i, agent := range agents {
|
|
if err := agent.Start(); err != nil {
|
|
fmt.Printf("Failed to start agent %d: %v\n", i, err)
|
|
return
|
|
}
|
|
defer agent.Stop()
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Connect agents in full mesh
|
|
fmt.Println("Establishing ZAP mesh network...")
|
|
for i := 0; i < len(agents); i++ {
|
|
for j := i + 1; j < len(agents); j++ {
|
|
addr := fmt.Sprintf("127.0.0.1:%d", basePort+j)
|
|
if err := agents[i].ConnectTo(addr); err != nil {
|
|
fmt.Printf("Warning: %s failed to connect to %s: %v\n",
|
|
agents[i].config.Name, agents[j].config.Name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
time.Sleep(300 * time.Millisecond)
|
|
|
|
// Show network topology
|
|
fmt.Println("\nNetwork Topology:")
|
|
for _, agent := range agents {
|
|
peers := agent.GetConnectedPeers()
|
|
caps := agent.GetPeerCapabilities()
|
|
totalCaps := 0
|
|
for _, c := range caps {
|
|
totalCaps += len(c)
|
|
}
|
|
fmt.Printf(" %s: %d peers, %d discovered capabilities\n",
|
|
agent.config.Name, len(peers), totalCaps)
|
|
}
|
|
fmt.Println()
|
|
|
|
// Get query from args or use default
|
|
query := "What is the most important principle in software engineering?"
|
|
if len(os.Args) > 1 {
|
|
query = strings.Join(os.Args[1:], " ")
|
|
}
|
|
|
|
fmt.Printf("Query: %s\n", query)
|
|
fmt.Println("\nBroadcasting to swarm via ZAP protocol...")
|
|
fmt.Println()
|
|
|
|
// Claude initiates the query
|
|
start := time.Now()
|
|
agents[0].BroadcastQuery(1, query)
|
|
|
|
// Also process locally
|
|
go func() {
|
|
response := agents[0].generateResponse(query)
|
|
agents[0].broadcastResponse(1, response)
|
|
}()
|
|
|
|
// Wait for consensus
|
|
timeout := time.After(15 * time.Second)
|
|
ticker := time.NewTicker(100 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-timeout:
|
|
fmt.Println("\nTimeout waiting for consensus")
|
|
goto done
|
|
case <-ticker.C:
|
|
if synthesis, ok := agents[4].GetSynthesis(1); ok {
|
|
elapsed := time.Since(start)
|
|
fmt.Printf("Consensus achieved in %v\n", elapsed)
|
|
_ = synthesis // Already printed by handleSynthesis
|
|
goto done
|
|
}
|
|
}
|
|
}
|
|
|
|
done:
|
|
// Print stats
|
|
fmt.Println("\n=== Agent Statistics ===")
|
|
for _, agent := range agents {
|
|
fmt.Printf(" %s: queries=%d responses=%d votes=%d tools=%d\n",
|
|
agent.config.Name,
|
|
agent.queryCount.Load(),
|
|
agent.responseCount.Load(),
|
|
agent.voteCount.Load(),
|
|
agent.toolCallCount.Load(),
|
|
)
|
|
}
|
|
}
|