Skip to main content

Consensus Mechanism Pattern

The Consensus pattern enables multi-agent collaborative decision-making through negotiation, voting, and threshold-based agreement. Multiple agents contribute their perspectives to reach a collective decision.

Overview

This pattern implements democratic decision-making among agents:
  1. Proposal is submitted to all participating agents
  2. Each agent evaluates and provides input
  3. Votes/opinions are collected and aggregated
  4. Consensus threshold determines if agreement is reached
  5. Final decision is derived from collective input
The Consensus protocol supports optional LLM integration for intelligent analysis and voting.

When to Use

Decision Making

Important choices requiring multiple perspectives

Quality Review

Evaluate quality with multiple expert opinions

Proposal Selection

Choose from multiple competing options

Risk Assessment

Collective evaluation of risks and trade-offs

Basic Usage

Creating a Consensus Protocol

use mofa_sdk::collaboration::{
    ConsensusProtocol,
    CollaborationMessage,
    CollaborationMode,
    CollaborationContent
};
use std::sync::Arc;

// Create consensus protocol (threshold default: 0.7)
let protocol = Arc::new(ConsensusProtocol::new("decision_maker"));

// Create decision proposal
let proposal = CollaborationMessage::new(
    "proposer",
    CollaborationContent::Mixed {
        text: "Should we proceed with the new architecture?".to_string(),
        data: serde_json::json!({
            "proposal_id": "arch_001",
            "options": ["approve", "reject", "modify"],
            "context": "Microservices migration proposal"
        })
    },
    CollaborationMode::Consensus
);

// Submit for consensus
protocol.send_message(proposal).await?;
let result = protocol.process_message(
    protocol.receive_message().await?.unwrap()
).await?;

if result.success {
    println!("Consensus reached in {}ms", result.duration_ms);
}

LLM-Enhanced Consensus

use mofa_sdk::collaboration::ConsensusProtocol;
use mofa_sdk::llm::{LLMClient, openai_from_env};
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create LLM client
    let provider = openai_from_env()?;
    let llm_client = Arc::new(LLMClient::new(Arc::new(provider)));
    
    // Create LLM-enabled consensus protocol
    let protocol = Arc::new(ConsensusProtocol::with_llm(
        "intelligent_voter",
        llm_client.clone()
    ));
    
    // LLM will analyze proposals and provide reasoned votes
    let proposal = CollaborationMessage::new(
        "proposer",
        "Evaluate the security implications of using JWT tokens",
        CollaborationMode::Consensus
    );
    
    protocol.send_message(proposal).await?;
    
    if let Some(msg) = protocol.receive_message().await? {
        let result = protocol.process_message(msg).await?;
        
        if let Some(data) = result.data {
            if let CollaborationContent::LLMResponse { reasoning, conclusion, .. } = data {
                println!("LLM Analysis: {}", reasoning);
                println!("LLM Vote: {}", conclusion);
            }
        }
    }
    
    Ok(())
}

Multi-Agent Consensus

Simple Voting Example

use mofa_sdk::collaboration::{
    ConsensusProtocol,
    CollaborationMessage,
    CollaborationContent,
    CollaborationMode
};
use std::sync::Arc;
use std::collections::HashMap;

async fn simple_vote() -> Result<(), Box<dyn std::error::Error>> {
    // Create multiple voting agents
    let agents = vec![
        Arc::new(ConsensusProtocol::new("agent_1")),
        Arc::new(ConsensusProtocol::new("agent_2")),
        Arc::new(ConsensusProtocol::new("agent_3")),
        Arc::new(ConsensusProtocol::new("agent_4")),
        Arc::new(ConsensusProtocol::new("agent_5")),
    ];
    
    // Proposal to vote on
    let proposal = CollaborationMessage::new(
        "coordinator",
        CollaborationContent::Data(serde_json::json!({
            "question": "Approve deployment to production?",
            "options": ["approve", "reject", "delay"],
            "details": {
                "tests_passed": true,
                "code_review_approved": true,
                "performance_benchmarks_met": true
            }
        })),
        CollaborationMode::Consensus
    );
    
    // Collect votes from all agents
    let mut votes = HashMap::new();
    for agent in &agents {
        agent.send_message(proposal.clone()).await?;
        
        if let Some(msg) = agent.receive_message().await? {
            let result = agent.process_message(msg).await?;
            
            if let Some(data) = result.data {
                let vote = data.to_text();
                *votes.entry(vote).or_insert(0) += 1;
            }
        }
    }
    
    // Tally votes
    println!("Vote results: {:?}", votes);
    
    // Check if threshold is met (>70%)
    let total_votes = votes.values().sum::<i32>() as f32;
    for (option, count) in votes {
        let percentage = (count as f32 / total_votes) * 100.0;
        println!("  {}: {:.1}%", option, percentage);
        
        if percentage >= 70.0 {
            println!("✅ Consensus reached: {}", option);
        }
    }
    
    Ok(())
}

Real-World Examples

Code Merge Decision

use mofa_sdk::collaboration::{
    ConsensusProtocol,
    CollaborationMessage,
    CollaborationContent,
    CollaborationMode
};
use mofa_sdk::llm::{LLMClient, openai_from_env};
use std::sync::Arc;

struct CodeReviewPanel {
    security_expert: Arc<ConsensusProtocol>,
    performance_expert: Arc<ConsensusProtocol>,
    code_quality_expert: Arc<ConsensusProtocol>,
    domain_expert: Arc<ConsensusProtocol>,
}

impl CodeReviewPanel {
    async fn new(llm_client: Arc<LLMClient>) -> Self {
        Self {
            security_expert: Arc::new(ConsensusProtocol::with_llm(
                "security_expert",
                llm_client.clone()
            )),
            performance_expert: Arc::new(ConsensusProtocol::with_llm(
                "performance_expert",
                llm_client.clone()
            )),
            code_quality_expert: Arc::new(ConsensusProtocol::with_llm(
                "quality_expert",
                llm_client.clone()
            )),
            domain_expert: Arc::new(ConsensusProtocol::with_llm(
                "domain_expert",
                llm_client.clone()
            )),
        }
    }
}

Architecture Decision Record (ADR)

use mofa_sdk::collaboration::{
    ConsensusProtocol,
    LLMDrivenCollaborationManager,
    CollaborationContent
};
use std::sync::Arc;

async fn architecture_decision() -> Result<(), Box<dyn std::error::Error>> {
    let manager = LLMDrivenCollaborationManager::new("architecture_board");
    
    // Register consensus protocol
    manager.register_protocol(Arc::new(
        ConsensusProtocol::new("architecture_board")
    )).await?;
    
    // Propose architectural change
    let adr = CollaborationContent::Data(serde_json::json!({
        "title": "ADR-001: Migrate to Event-Driven Architecture",
        "context": "Current monolithic architecture is difficult to scale",
        "decision": "Adopt event-driven microservices architecture",
        "consequences": {
            "pros": [
                "Better scalability",
                "Improved fault isolation",
                "Technology diversity"
            ],
            "cons": [
                "Increased complexity",
                "Distributed system challenges",
                "Higher operational overhead"
            ]
        },
        "alternatives": [
            "Continue with monolith + optimization",
            "Modular monolith approach",
            "Hybrid architecture"
        ]
    }));
    
    let result = manager.execute_task_with_protocol(
        "consensus",
        adr
    ).await?;
    
    println!("ADR Decision:");
    println!("  Status: {}", if result.success { "Approved" } else { "Rejected" });
    println!("  Participants: {:?}", result.participants);
    println!("  Duration: {}ms", result.duration_ms);
    
    Ok(())
}

Multi-Round Consensus

Implement iterative consensus with multiple negotiation rounds:
use mofa_sdk::collaboration::{
    ConsensusProtocol,
    CollaborationMessage,
    CollaborationMode
};
use std::sync::Arc;

async fn multi_round_consensus() -> Result<(), Box<dyn std::error::Error>> {
    let protocol = Arc::new(ConsensusProtocol::new("negotiator"));
    
    let max_rounds = 3;
    let threshold = 0.75;
    
    for round in 1..=max_rounds {
        println!("\n=== Round {} ===", round);
        
        let proposal = CollaborationMessage::new(
            "coordinator",
            format!("Proposal iteration {}", round),
            CollaborationMode::Consensus
        );
        
        protocol.send_message(proposal).await?;
        
        if let Some(msg) = protocol.receive_message().await? {
            let result = protocol.process_message(msg).await?;
            
            // Check if consensus reached
            if result.success {
                println!("✅ Consensus reached in round {}", round);
                break;
            } else if round < max_rounds {
                println!("⚠️ No consensus yet, proceeding to round {}", round + 1);
                // Refine proposal based on feedback
            } else {
                println!("❌ Failed to reach consensus after {} rounds", max_rounds);
            }
        }
    }
    
    Ok(())
}

Weighted Voting

Implement voting with different agent weights:
use std::collections::HashMap;

struct WeightedVote {
    agent_id: String,
    vote: String,
    weight: f32,
    reasoning: String,
}

async fn weighted_consensus() -> Result<(), Box<dyn std::error::Error>> {
    // Define agent weights (expertise-based)
    let weights: HashMap<String, f32> = HashMap::from([
        ("senior_architect".to_string(), 2.0),
        ("tech_lead".to_string(), 1.5),
        ("developer_1".to_string(), 1.0),
        ("developer_2".to_string(), 1.0),
    ]);
    
    let votes = vec![
        WeightedVote {
            agent_id: "senior_architect".to_string(),
            vote: "approve".to_string(),
            weight: 2.0,
            reasoning: "Architecture is sound".to_string(),
        },
        WeightedVote {
            agent_id: "tech_lead".to_string(),
            vote: "approve".to_string(),
            weight: 1.5,
            reasoning: "Meets technical requirements".to_string(),
        },
        WeightedVote {
            agent_id: "developer_1".to_string(),
            vote: "reject".to_string(),
            weight: 1.0,
            reasoning: "Concerns about complexity".to_string(),
        },
        WeightedVote {
            agent_id: "developer_2".to_string(),
            vote: "approve".to_string(),
            weight: 1.0,
            reasoning: "Good solution".to_string(),
        },
    ];
    
    // Calculate weighted votes
    let mut vote_totals: HashMap<String, f32> = HashMap::new();
    let mut total_weight = 0.0;
    
    for vote in &votes {
        *vote_totals.entry(vote.vote.clone()).or_insert(0.0) += vote.weight;
        total_weight += vote.weight;
    }
    
    println!("Weighted Vote Results:");
    for (option, weight) in &vote_totals {
        let percentage = (weight / total_weight) * 100.0;
        println!("  {}: {:.1}% (weight: {:.1})", option, percentage, weight);
    }
    
    // Determine winner
    if let Some((winner, weight)) = vote_totals.iter().max_by(|a, b| a.1.partial_cmp(b.1).unwrap()) {
        let percentage = (weight / total_weight) * 100.0;
        if percentage >= 70.0 {
            println!("\n✅ Weighted consensus reached: {} ({:.1}%)", winner, percentage);
        } else {
            println!("\n❌ No consensus: highest vote {} at {:.1}%", winner, percentage);
        }
    }
    
    Ok(())
}

Best Practices

1

Set Appropriate Thresholds

Choose consensus thresholds based on decision importance:
  • Simple decisions: 50% (simple majority)
  • Important decisions: 70% (strong majority) - default
  • Critical decisions: 90%+ (near unanimous)
// Configure threshold in protocol implementation
// Default is 0.7 (70%)
2

Limit Consensus Rounds

Prevent infinite negotiation loops:
const MAX_ROUNDS: usize = 5;
const TIMEOUT_SECONDS: u64 = 300; // 5 minutes
3

Provide Context

Include comprehensive decision context:
let proposal = CollaborationContent::Data(serde_json::json!({
    "question": "Clear decision question",
    "context": "Relevant background information",
    "options": ["option1", "option2", "option3"],
    "criteria": "Decision criteria",
    "deadline": "2024-12-31"
}));
4

Track Decision Rationale

Record why each agent voted a certain way:
if let CollaborationContent::LLMResponse { reasoning, conclusion, .. } = data {
    println!("Agent: {}", agent_id);
    println!("Vote: {}", conclusion);
    println!("Reasoning: {}", reasoning);
}

See Also

Debate Pattern

For iterative refinement through discussion

Request-Response

For simple decision queries

Collaboration Overview

Return to patterns overview

LLM Integration

Learn about LLM-powered decision-making

Build docs developers (and LLMs) love