Files
zeekay 79eb047505 feat: unify Python SDK with MCP mesh consensus
Major changes:
- Merge pkg/wire Python types into pkg/python/lux_consensus
- Add MCP mesh consensus module (from hanzo-consensus)
- Add types.py with Candidate, Vote, Certificate for wire protocol
- Bridge functions for AI consensus -> blockchain interop
- Keep pkg/wire as Go-only wire protocol types

Python SDK now provides:
- lux_consensus: Core chain/block/vote types
- lux_consensus.types: Wire protocol (Candidate, WireVote, WireCertificate)
- lux_consensus.mcp: MCP mesh for agent-to-agent consensus

Also includes engine improvements:
- Chain syncer with persistence
- PQ crypto enhancements
- External sequencer finality example
- Protocol field/horizon/prism updates

All tests passing.
2026-01-13 09:49:03 -08:00

207 lines
6.1 KiB
Python

"""Metastable consensus protocol.
Two-phase finality for distributed agreement.
Phase I (Sampling):
- Each participant proposes initial response
- k-peer sampling per round
- Confidence accumulation toward β₁
Phase II (Finality):
- Threshold aggregation
- β₂ finality threshold
- Winner synthesis
Reference: https://github.com/luxfi/consensus
"""
import random
import asyncio
from typing import Any, Dict, List, TypeVar, Callable, Optional, Coroutine
from dataclasses import field, dataclass
T = TypeVar("T")
@dataclass
class Result:
"""Participant result."""
id: str
output: str
ok: bool
error: Optional[str] = None
ms: int = 0
round: int = 0
@dataclass
class State:
"""Consensus state."""
prompt: str
participants: List[str]
rounds: int
k: int # Sample size per round
alpha: float # Agreement threshold (0-1)
beta_1: float # Preference threshold (Phase I)
beta_2: float # Decision threshold (Phase II)
# State
responses: Dict[str, List[str]] = field(default_factory=dict)
confidence: Dict[str, float] = field(default_factory=dict)
luminance: Dict[str, float] = field(default_factory=dict)
finalized: bool = False
winner: Optional[str] = None
synthesis: Optional[str] = None
discussion_history: List[Dict[str, str]] = field(default_factory=list)
@dataclass
class Consensus:
"""Metastable consensus protocol.
Args:
participants: List of participant IDs
execute: Async function (id, prompt) -> Result
rounds: Number of sampling rounds (default: 3)
k: Sample size per round (default: 3)
alpha: Agreement threshold (default: 0.6)
beta_1: Preference threshold (default: 0.5)
beta_2: Decision threshold (default: 0.8)
"""
participants: List[str]
execute: Callable[[str, str], Coroutine[Any, Any, Result]]
rounds: int = 3
k: int = 3
alpha: float = 0.6
beta_1: float = 0.5
beta_2: float = 0.8
async def run(self, prompt: str) -> State:
"""Run consensus protocol."""
state = State(
prompt=prompt,
participants=self.participants,
rounds=self.rounds,
k=min(self.k, len(self.participants)),
alpha=self.alpha,
beta_1=self.beta_1,
beta_2=self.beta_2,
)
# Initialize
for p in self.participants:
state.luminance[p] = 1.0
state.confidence[p] = 0.0
state.responses[p] = []
# Phase I: Sampling - initial proposals
initial = await asyncio.gather(*[
self.execute(p, prompt) for p in self.participants
])
for r in initial:
state.responses[r.id].append(r.output)
if r.ok and r.ms > 0:
# Faster = higher luminance
state.luminance[r.id] = 1.0 / (1.0 + r.ms / 1000.0)
# Sampling rounds
for _ in range(self.rounds):
# Luminance-weighted peer selection
weights = [state.luminance[p] for p in self.participants]
total = sum(weights)
sampled = random.choices(
self.participants,
[w / total for w in weights],
k=state.k
)
# Build context from sampled peers
context = [f"Query: {prompt}", "", "Peers:"]
for p in sampled:
if state.responses[p]:
context.append(f"[{p}] {state.responses[p][-1][:1000]}")
context.append("\nRefine your response:")
# Each participant refines
round_results = await asyncio.gather(*[
self.execute(p, "\n".join(context)) for p in self.participants
])
for r in round_results:
state.responses[r.id].append(r.output)
if r.ok:
# Agreement metric
agreement = self._agreement(r.output, sampled, state)
state.confidence[r.id] = state.confidence[r.id] * 0.5 + agreement * 0.5
# Check β₁ threshold
if max(state.confidence.values()) >= self.beta_1:
break
# Phase II: Finality
scores = {p: state.confidence[p] * state.luminance[p] for p in self.participants}
state.winner = max(scores, key=lambda p: scores[p])
if scores[state.winner] >= self.beta_2:
state.finalized = True
# Synthesis
if state.responses[state.winner]:
state.synthesis = state.responses[state.winner][-1]
return state
def _agreement(self, output: str, sampled: List[str], state: State) -> float:
"""Calculate agreement with sampled peers."""
if not sampled:
return 0.0
total = 0.0
for p in sampled:
if state.responses[p]:
r_words = set(output.lower().split())
p_words = set(state.responses[p][-1].lower().split())
if r_words and p_words:
overlap = len(r_words & p_words) / len(r_words | p_words)
total += overlap
return total / len(sampled)
async def run(
prompt: str,
participants: List[str],
execute: Callable[[str, str], Coroutine[Any, Any, Result]],
rounds: int = 3,
k: int = 3,
alpha: float = 0.6,
beta_1: float = 0.5,
beta_2: float = 0.8,
) -> State:
"""Run metastable consensus.
Args:
prompt: Query to reach consensus on
participants: List of participant IDs
execute: Async function (id, prompt) -> Result
rounds: Sampling rounds
k: Sample size per round
alpha: Agreement threshold
beta_1: Preference threshold
beta_2: Decision threshold
Returns:
Final consensus state
"""
consensus = Consensus(
participants=participants,
execute=execute,
rounds=rounds,
k=k,
alpha=alpha,
beta_1=beta_1,
beta_2=beta_2,
)
return await consensus.run(prompt)