ZAP Protocol
Advanced Topics

Agent Consensus

Voting-based response aggregation for multi-agent systems

Agent Consensus

Agent Consensus provides voting-based response aggregation for multi-agent AI systems.

Overview

When multiple AI agents process the same query, their responses may differ. Agent Consensus:

  • Collects responses from multiple agents
  • Counts votes for each unique response
  • Returns the response that meets the threshold
  • Optionally requires cryptographic signatures

Configuration

struct AgentConsensusConfig {
  threshold @0 :Float64;           # Required vote fraction (0.0-1.0)
  minResponses @1 :UInt32;         # Minimum responses before consensus check
  timeoutSecs @2 :UInt32;          # Query timeout in seconds
  requireSignatures @3 :Bool;      # Whether agent signatures are required
}

Example configurations:

Use CaseThresholdMin Responses
Simple majority0.53
Supermajority0.675
Unanimous1.03
First response0.01

Query Lifecycle

Submit Query

struct AgentSubmitQueryRequest {
  query @0 :Text;
  config @1 :AgentConsensusConfig;
}

struct AgentSubmitQueryResponse {
  queryId @0 :AgentQueryId;
}

Collect Responses

Agents submit their responses:

struct AgentSubmitResponseRequest {
  queryId @0 :AgentQueryId;
  agentId @1 :Text;
  response @2 :Text;
  signature @3 :Data;              # Optional signature
  confidence @4 :Float32;          # Agent's confidence (0.0-1.0)
}

Check Consensus

struct AgentTryConsensusResponse {
  union {
    result @0 :Text;               # Consensus reached
    pending @1 :AgentQueryState;   # Still collecting
    noConsensus @2 :AgentQueryState; # Min met, no consensus
    error @3 :Text;
  }
}

Query State

struct AgentQueryState {
  queryId @0 :AgentQueryId;
  query @1 :Text;                  # Original query
  responses @2 :List(AgentResponseEntry);
  votes @3 :List(AgentResponseVote);
  finalized @4 :Bool;
  result @5 :Text;                 # Final result (if finalized)
  createdAt @6 :Timestamp;
}

struct AgentResponseVote {
  responseHash @0 :Data;           # BLAKE3 hash of response
  voteCount @1 :UInt32;
  voters @2 :List(Text);           # Agent IDs that voted
}

Consensus Service

interface AgentConsensusService {
  # Submit a new query for consensus
  submitQuery @0 (request :AgentSubmitQueryRequest) -> (response :AgentSubmitQueryResponse);

  # Submit an agent's response to a query
  submitResponse @1 (request :AgentSubmitResponseRequest) -> (result :AgentSubmitResponseResult);

  # Try to reach consensus on a query
  tryConsensus @2 (request :AgentTryConsensusRequest) -> (response :AgentTryConsensusResponse);

  # Get current query state
  getQuery @3 (queryId :AgentQueryId) -> (state :AgentQueryState);

  # Clean up expired queries
  cleanup @4 () -> ();

  # Get number of active queries
  activeQueries @5 () -> (count :UInt32);
}

Usage Examples

Rust

use zap_protocol::consensus::{AgentConsensus, ConsensusConfig};

// Create consensus service
let consensus = AgentConsensus::new();

// Submit query
let query_id = consensus.submit_query(
    "What is the capital of France?",
    ConsensusConfig {
        threshold: 0.67,
        min_responses: 3,
        timeout_secs: 30,
        require_signatures: false,
    }
).await?;

// Agents submit responses (from different agents)
consensus.submit_response(&query_id, "agent-1", "Paris", None, 0.95).await?;
consensus.submit_response(&query_id, "agent-2", "Paris", None, 0.90).await?;
consensus.submit_response(&query_id, "agent-3", "Lyon", None, 0.60).await?;

// Check consensus
match consensus.try_consensus(&query_id).await? {
    ConsensusResult::Result(answer) => {
        println!("Consensus: {}", answer);  // "Paris"
    }
    ConsensusResult::Pending(state) => {
        println!("Still collecting: {}/{}",
            state.responses.len(),
            config.min_responses);
    }
    ConsensusResult::NoConsensus(state) => {
        println!("No consensus reached");
        for vote in &state.votes {
            println!("  {} votes: {}", vote.vote_count, vote.voters.len());
        }
    }
}

Go

import "github.com/zap-protocol/zap-go/consensus"

// Create service
svc := consensus.NewAgentConsensus()

// Submit query
queryID, _ := svc.SubmitQuery(ctx, "What is 2+2?", &consensus.Config{
    Threshold:    0.67,
    MinResponses: 3,
    TimeoutSecs:  30,
})

// Submit responses
svc.SubmitResponse(ctx, queryID, "agent-1", "4", nil, 0.99)
svc.SubmitResponse(ctx, queryID, "agent-2", "4", nil, 0.99)
svc.SubmitResponse(ctx, queryID, "agent-3", "4", nil, 0.99)

// Check consensus
result, _ := svc.TryConsensus(ctx, queryID)
fmt.Println("Answer:", result)  // "4"

Python

from zap_protocol.consensus import AgentConsensus, ConsensusConfig

# Create service
consensus = AgentConsensus()

# Submit query
query_id = await consensus.submit_query(
    "Translate 'hello' to French",
    ConsensusConfig(
        threshold=0.5,
        min_responses=3,
        timeout_secs=30
    )
)

# Submit responses
await consensus.submit_response(query_id, "agent-1", "bonjour")
await consensus.submit_response(query_id, "agent-2", "bonjour")
await consensus.submit_response(query_id, "agent-3", "salut")

# Check consensus
result = await consensus.try_consensus(query_id)
print(f"Translation: {result}")  # "bonjour"

Signature Verification

When requireSignatures is true:

// Agent signs response with ML-DSA
let signature = identity.sign(response.as_bytes())?;

consensus.submit_response(
    &query_id,
    "agent-1",
    response,
    Some(signature),
    0.95
).await?;

The consensus service verifies signatures against registered agent identities.

Response Hashing

Responses are compared by BLAKE3 hash:

let hash = blake3::hash(response.as_bytes());

This ensures:

  • Identical responses are grouped together
  • Whitespace/formatting differences are not tolerated
  • Large responses are efficiently compared

Confidence Weighting

Optionally, votes can be weighted by confidence:

let config = ConsensusConfig {
    threshold: 0.67,
    min_responses: 3,
    timeout_secs: 30,
    require_signatures: false,
    weight_by_confidence: true,  // Enable weighting
};

With weighting:

  • High-confidence responses count more
  • Threshold applies to weighted sum
  • Low-confidence responses may be filtered

Cleanup

Expired queries should be cleaned up periodically:

// Clean up queries older than timeout
consensus.cleanup().await?;

// Get active query count
let count = consensus.active_queries().await?;

Best Practices

  1. Set Appropriate Thresholds: Higher for critical decisions
  2. Minimum Responses: At least 3 for meaningful consensus
  3. Timeouts: Balance between wait time and response collection
  4. Signatures: Enable for production deployments
  5. Monitoring: Track consensus success rates

Last updated on

On this page