🚀Transform your business with AI-powered process optimization
Architecture
💡 Design Patterns
Agent Design Patterns
Multi-Agent Consensus Pattern

Multi-Agent Consensus Pattern

The Multi-Agent Consensus Pattern enables multiple AI agents to reach agreement on decisions or shared state through distributed consensus algorithms.

Pattern Overview

This pattern allows multiple agents to coordinate and agree on decisions even in the presence of failures or network partitions, ensuring system-wide consistency and reliability.

Structure

use std::collections::HashMap;
 
// Consensus proposal
#[derive(Debug, Clone)]
pub struct Proposal {
    pub id: String,
    pub value: String,
    pub proposer_id: String,
    pub round: u64,
}
 
// Agent vote
#[derive(Debug, Clone)]
pub struct Vote {
    pub agent_id: String,
    pub proposal_id: String,
    pub decision: bool, // true = accept, false = reject
    pub reasoning: String,
}
 
// Consensus result
#[derive(Debug, Clone)]
pub struct ConsensusResult {
    pub proposal: Proposal,
    pub votes: Vec<Vote>,
    pub consensus_reached: bool,
    pub final_decision: bool,
}
 
// Consensus agent trait
pub trait ConsensusAgent {
    fn get_id(&self) -> &str;
    fn propose(&self, value: String, round: u64) -> Proposal;
    fn evaluate_proposal(&self, proposal: &Proposal) -> Vote;
    fn can_vote(&self, proposal: &Proposal) -> bool;
    fn get_weight(&self) -> f64; // Voting weight
}
 
// Consensus coordinator
pub struct ConsensusCoordinator {
    agents: Vec<Box<dyn ConsensusAgent>>,
    active_proposals: HashMap<String, Proposal>,
    votes: HashMap<String, Vec<Vote>>,
    consensus_threshold: f64,
}
 
impl ConsensusCoordinator {
    pub fn new(consensus_threshold: f64) -> Self {
        Self {
            agents: Vec::new(),
            active_proposals: HashMap::new(),
            votes: HashMap::new(),
            consensus_threshold,
        }
    }
 
    pub fn add_agent(&mut self, agent: Box<dyn ConsensusAgent>) {
        self.agents.push(agent);
    }
 
    pub fn submit_proposal(&mut self, proposal: Proposal) -> String {
        let proposal_id = proposal.id.clone();
        self.active_proposals.insert(proposal_id.clone(), proposal);
        self.votes.insert(proposal_id.clone(), Vec::new());
        proposal_id
    }
 
    pub fn collect_votes(&mut self, proposal_id: &str) -> Option<ConsensusResult> {
        if let Some(proposal) = self.active_proposals.get(proposal_id) {
            let mut votes = Vec::new();
            
            for agent in &self.agents {
                if agent.can_vote(proposal) {
                    let vote = agent.evaluate_proposal(proposal);
                    votes.push(vote);
                }
            }
 
            self.votes.insert(proposal_id.to_string(), votes.clone());
            
            // Calculate consensus
            let (consensus_reached, final_decision) = self.calculate_consensus(&votes);
            
            Some(ConsensusResult {
                proposal: proposal.clone(),
                votes,
                consensus_reached,
                final_decision,
            })
        } else {
            None
        }
    }
 
    fn calculate_consensus(&self, votes: &[Vote]) -> (bool, bool) {
        let total_weight: f64 = self.agents.iter().map(|a| a.get_weight()).sum();
        let mut accept_weight = 0.0;
        let mut reject_weight = 0.0;
 
        for vote in votes {
            if let Some(agent) = self.agents.iter().find(|a| a.get_id() == vote.agent_id) {
                let weight = agent.get_weight();
                if vote.decision {
                    accept_weight += weight;
                } else {
                    reject_weight += weight;
                }
            }
        }
 
        let accept_ratio = accept_weight / total_weight;
        let reject_ratio = reject_weight / total_weight;
 
        if accept_ratio >= self.consensus_threshold {
            (true, true)
        } else if reject_ratio >= self.consensus_threshold {
            (true, false)
        } else {
            (false, false)
        }
    }
}
 
// Example consensus agents
pub struct DataValidatorAgent {
    id: String,
    expertise_domain: String,
}
 
impl DataValidatorAgent {
    pub fn new(id: &str, domain: &str) -> Self {
        Self {
            id: id.to_string(),
            expertise_domain: domain.to_string(),
        }
    }
}
 
impl ConsensusAgent for DataValidatorAgent {
    fn get_id(&self) -> &str {
        &self.id
    }
 
    fn propose(&self, value: String, round: u64) -> Proposal {
        Proposal {
            id: format!("{}_{}", self.id, round),
            value,
            proposer_id: self.id.clone(),
            round,
        }
    }
 
    fn evaluate_proposal(&self, proposal: &Proposal) -> Vote {
        // Simple validation logic
        let is_valid = proposal.value.len() > 5 && proposal.value.contains(&self.expertise_domain);
        
        Vote {
            agent_id: self.id.clone(),
            proposal_id: proposal.id.clone(),
            decision: is_valid,
            reasoning: if is_valid {
                "Proposal meets validation criteria".to_string()
            } else {
                "Proposal lacks required domain expertise".to_string()
            },
        }
    }
 
    fn can_vote(&self, proposal: &Proposal) -> bool {
        proposal.value.contains(&self.expertise_domain) || self.expertise_domain == "general"
    }
 
    fn get_weight(&self) -> f64 {
        1.0 // Equal weight for all validators
    }
}
 
pub struct QualityAssuranceAgent {
    id: String,
    quality_threshold: f64,
}
 
impl QualityAssuranceAgent {
    pub fn new(id: &str, threshold: f64) -> Self {
        Self {
            id: id.to_string(),
            quality_threshold: threshold,
        }
    }
}
 
impl ConsensusAgent for QualityAssuranceAgent {
    fn get_id(&self) -> &str {
        &self.id
    }
 
    fn propose(&self, value: String, round: u64) -> Proposal {
        Proposal {
            id: format!("qa_{}_{}", self.id, round),
            value,
            proposer_id: self.id.clone(),
            round,
        }
    }
 
    fn evaluate_proposal(&self, proposal: &Proposal) -> Vote {
        // Quality assessment based on length and content
        let quality_score = proposal.value.len() as f64 * 0.1;
        let meets_quality = quality_score >= self.quality_threshold;
        
        Vote {
            agent_id: self.id.clone(),
            proposal_id: proposal.id.clone(),
            decision: meets_quality,
            reasoning: format!("Quality score: {:.2}, threshold: {:.2}", 
                             quality_score, self.quality_threshold),
        }
    }
 
    fn can_vote(&self, _proposal: &Proposal) -> bool {
        true // QA can vote on all proposals
    }
 
    fn get_weight(&self) -> f64 {
        1.5 // Higher weight for quality assurance
    }
}

Usage Example

let mut coordinator = ConsensusCoordinator::new(0.6); // 60% consensus threshold
 
// Add consensus agents
coordinator.add_agent(Box::new(DataValidatorAgent::new("validator_1", "finance")));
coordinator.add_agent(Box::new(DataValidatorAgent::new("validator_2", "general")));
coordinator.add_agent(Box::new(QualityAssuranceAgent::new("qa_1", 5.0)));
 
// Submit proposal for consensus
let proposal = Proposal {
    id: "proposal_001".to_string(),
    value: "Implement new finance risk assessment algorithm".to_string(),
    proposer_id: "system".to_string(),
    round: 1,
};
 
let proposal_id = coordinator.submit_proposal(proposal);
 
// Collect votes and determine consensus
if let Some(result) = coordinator.collect_votes(&proposal_id) {
    println!("Consensus reached: {}", result.consensus_reached);
    println!("Final decision: {}", result.final_decision);
    
    for vote in result.votes {
        println!("Agent {}: {} - {}", vote.agent_id, vote.decision, vote.reasoning);
    }
}

Benefits

  • Distributed Decision Making: No single point of failure
  • Democratic Process: All agents participate in decisions
  • Fault Tolerance: Continues working despite agent failures
  • Transparency: All votes and reasoning are recorded

Use Cases

  • Distributed system configuration changes
  • Multi-agent task allocation decisions
  • Collaborative data validation
  • Consensus-based model updates