Files
wifi-densepose/vendor/ruvector/crates/ruvector-exotic-wasm/src/nao.rs

746 lines
22 KiB
Rust

//! # Neural Autonomous Organization (NAO)
//!
//! A decentralized governance mechanism for AI agent collectives using
//! oscillatory synchronization for consensus and stake-weighted voting.
//!
//! ## Key Concepts
//!
//! - **Stake**: Each agent's influence weight in the organization
//! - **Proposals**: Actions that require collective approval
//! - **Oscillatory Sync**: Neural-inspired synchronization for coherence
//! - **Quadratic Voting**: Diminishing returns on vote weight
//!
//! ## Example
//!
//! ```rust
//! use ruvector_exotic_wasm::nao::{NeuralAutonomousOrg, ProposalStatus};
//!
//! let mut nao = NeuralAutonomousOrg::new(0.7); // 70% quorum
//!
//! // Add agents with stake
//! nao.add_member("agent_1", 100);
//! nao.add_member("agent_2", 50);
//!
//! // Create and vote on proposal
//! let prop_id = nao.propose("Migrate to new memory backend");
//! nao.vote(&prop_id, "agent_1", 0.9); // Strong support
//! nao.vote(&prop_id, "agent_2", 0.6); // Moderate support
//!
//! // Execute if consensus reached
//! if nao.execute(&prop_id) {
//! println!("Proposal executed!");
//! }
//! ```
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use wasm_bindgen::prelude::*;
/// Status of a proposal in the NAO
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ProposalStatus {
/// Proposal is active and accepting votes
Pending,
/// Proposal passed quorum and was executed
Executed,
/// Proposal failed to reach quorum or was rejected
Rejected,
/// Proposal expired without decision
Expired,
}
/// A proposal for collective action
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Proposal {
/// Unique identifier
pub id: String,
/// Description of the proposed action
pub action: String,
/// Current status
pub status: ProposalStatus,
/// Votes: agent_id -> vote weight (-1.0 to 1.0)
pub votes: HashMap<String, f32>,
/// Creation timestamp (in simulation ticks)
pub created_at: u64,
/// Expiration timestamp
pub expires_at: u64,
}
impl Proposal {
/// Create a new proposal
pub fn new(id: String, action: String, created_at: u64, ttl: u64) -> Self {
Self {
id,
action,
status: ProposalStatus::Pending,
votes: HashMap::new(),
created_at,
expires_at: created_at + ttl,
}
}
/// Calculate weighted vote tally
pub fn tally(&self, members: &HashMap<String, u64>) -> (f32, f32) {
let mut for_votes = 0.0f32;
let mut against_votes = 0.0f32;
for (agent_id, vote_weight) in &self.votes {
if let Some(&stake) = members.get(agent_id) {
// Quadratic voting: sqrt(stake) * vote_weight
let voting_power = (stake as f32).sqrt();
let weighted_vote = voting_power * vote_weight;
if weighted_vote > 0.0 {
for_votes += weighted_vote;
} else {
against_votes += weighted_vote.abs();
}
}
}
(for_votes, against_votes)
}
/// Check if proposal has reached quorum
pub fn has_quorum(&self, members: &HashMap<String, u64>, quorum_threshold: f32) -> bool {
let total_voting_power: f32 = members.values().map(|&s| (s as f32).sqrt()).sum();
if total_voting_power == 0.0 {
return false;
}
let participating_power: f32 = self
.votes
.keys()
.filter_map(|id| members.get(id))
.map(|&s| (s as f32).sqrt())
.sum();
(participating_power / total_voting_power) >= quorum_threshold
}
}
/// Kuramoto-style oscillatory synchronizer for agent coherence
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OscillatorySynchronizer {
/// Phase of each oscillator (agent)
phases: HashMap<String, f32>,
/// Natural frequency of each oscillator
frequencies: HashMap<String, f32>,
/// Coupling strength between oscillators
coupling: f32,
/// Base frequency (Hz)
base_frequency: f32,
}
impl OscillatorySynchronizer {
/// Create a new synchronizer
pub fn new(coupling: f32, base_frequency: f32) -> Self {
Self {
phases: HashMap::new(),
frequencies: HashMap::new(),
coupling,
base_frequency,
}
}
/// Add an oscillator for an agent
pub fn add_oscillator(&mut self, agent_id: &str) {
use rand::Rng;
let mut rng = rand::thread_rng();
// Random initial phase
let phase = rng.gen::<f32>() * 2.0 * std::f32::consts::PI;
// Slight frequency variation around base
let freq = self.base_frequency * (0.95 + rng.gen::<f32>() * 0.1);
self.phases.insert(agent_id.to_string(), phase);
self.frequencies.insert(agent_id.to_string(), freq);
}
/// Remove an oscillator
pub fn remove_oscillator(&mut self, agent_id: &str) {
self.phases.remove(agent_id);
self.frequencies.remove(agent_id);
}
/// Step the Kuramoto dynamics forward
pub fn step(&mut self, dt: f32) {
let n = self.phases.len();
if n < 2 {
return;
}
// Collect current phases
let current_phases: Vec<(String, f32)> =
self.phases.iter().map(|(k, v)| (k.clone(), *v)).collect();
// Kuramoto update: dθ_i/dt = ω_i + (K/N) * Σ_j sin(θ_j - θ_i)
for (agent_id, phase) in &current_phases {
let omega = self
.frequencies
.get(agent_id)
.copied()
.unwrap_or(self.base_frequency);
// Sum of phase differences
let phase_coupling: f32 = current_phases
.iter()
.filter(|(id, _)| id != agent_id)
.map(|(_, other_phase)| (other_phase - phase).sin())
.sum();
let coupling_term = (self.coupling / n as f32) * phase_coupling;
let new_phase = phase + (omega + coupling_term) * dt;
// Wrap to [0, 2π]
let wrapped = new_phase.rem_euclid(2.0 * std::f32::consts::PI);
self.phases.insert(agent_id.clone(), wrapped);
}
}
/// Calculate order parameter (synchronization level, 0-1)
pub fn order_parameter(&self) -> f32 {
let n = self.phases.len();
if n == 0 {
return 0.0;
}
// r = |1/N * Σ_j e^(iθ_j)|
let sum_cos: f32 = self.phases.values().map(|&p| p.cos()).sum();
let sum_sin: f32 = self.phases.values().map(|&p| p.sin()).sum();
let r = ((sum_cos / n as f32).powi(2) + (sum_sin / n as f32).powi(2)).sqrt();
r
}
/// Get coherence between two agents (0-1)
pub fn coherence(&self, agent_a: &str, agent_b: &str) -> f32 {
match (self.phases.get(agent_a), self.phases.get(agent_b)) {
(Some(&pa), Some(&pb)) => {
// Coherence = cos(phase_difference)
let diff = pa - pb;
(1.0 + diff.cos()) / 2.0 // Map [-1, 1] to [0, 1]
}
_ => 0.0,
}
}
/// Get all current phases
pub fn phases(&self) -> &HashMap<String, f32> {
&self.phases
}
}
/// Neural Autonomous Organization - decentralized AI governance
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NeuralAutonomousOrg {
/// Member agents: agent_id -> stake
members: HashMap<String, u64>,
/// Active proposals
proposals: Vec<Proposal>,
/// Oscillatory synchronizer for coherence
sync: OscillatorySynchronizer,
/// Quorum threshold (0.0 - 1.0)
quorum_threshold: f32,
/// Current simulation tick
tick: u64,
/// Proposal time-to-live in ticks
proposal_ttl: u64,
/// Counter for generating proposal IDs
proposal_counter: u64,
}
impl Default for NeuralAutonomousOrg {
fn default() -> Self {
Self::new(0.5)
}
}
impl NeuralAutonomousOrg {
/// Create a new NAO with the given quorum threshold
pub fn new(quorum_threshold: f32) -> Self {
Self {
members: HashMap::new(),
proposals: Vec::new(),
sync: OscillatorySynchronizer::new(5.0, 40.0), // 40Hz gamma oscillations
quorum_threshold: quorum_threshold.clamp(0.0, 1.0),
tick: 0,
proposal_ttl: 1000, // 1000 ticks default TTL
proposal_counter: 0,
}
}
/// Add a member agent with initial stake
pub fn add_member(&mut self, agent_id: &str, stake: u64) {
self.members.insert(agent_id.to_string(), stake);
self.sync.add_oscillator(agent_id);
}
/// Remove a member agent
pub fn remove_member(&mut self, agent_id: &str) {
self.members.remove(agent_id);
self.sync.remove_oscillator(agent_id);
}
/// Get member count
pub fn member_count(&self) -> usize {
self.members.len()
}
/// Get a member's stake
pub fn get_stake(&self, agent_id: &str) -> Option<u64> {
self.members.get(agent_id).copied()
}
/// Update a member's stake
pub fn update_stake(&mut self, agent_id: &str, delta: i64) -> Option<u64> {
if let Some(stake) = self.members.get_mut(agent_id) {
let new_stake = (*stake as i64 + delta).max(0) as u64;
*stake = new_stake;
Some(new_stake)
} else {
None
}
}
/// Create a new proposal
pub fn propose(&mut self, action: &str) -> String {
self.proposal_counter += 1;
let id = format!("prop_{}", self.proposal_counter);
let proposal = Proposal::new(id.clone(), action.to_string(), self.tick, self.proposal_ttl);
self.proposals.push(proposal);
id
}
/// Vote on a proposal
///
/// # Arguments
/// * `proposal_id` - The proposal to vote on
/// * `agent_id` - The voting agent
/// * `weight` - Vote weight from -1.0 (strongly against) to 1.0 (strongly for)
///
/// # Returns
/// `true` if vote was recorded, `false` if proposal not found or agent not a member
pub fn vote(&mut self, proposal_id: &str, agent_id: &str, weight: f32) -> bool {
// Verify agent is a member
if !self.members.contains_key(agent_id) {
return false;
}
// Find and update proposal
for proposal in &mut self.proposals {
if proposal.id == proposal_id && proposal.status == ProposalStatus::Pending {
let clamped_weight = weight.clamp(-1.0, 1.0);
proposal.votes.insert(agent_id.to_string(), clamped_weight);
return true;
}
}
false
}
/// Execute a proposal if it has reached consensus
///
/// # Returns
/// `true` if proposal was executed, `false` otherwise
pub fn execute(&mut self, proposal_id: &str) -> bool {
let members = self.members.clone();
let quorum = self.quorum_threshold;
for proposal in &mut self.proposals {
if proposal.id == proposal_id && proposal.status == ProposalStatus::Pending {
// Check quorum
if !proposal.has_quorum(&members, quorum) {
return false;
}
// Tally votes
let (for_votes, against_votes) = proposal.tally(&members);
// Simple majority with coherence boost
let sync_level = self.sync.order_parameter();
let coherence_boost = 1.0 + sync_level * 0.2; // Up to 20% boost for synchronized org
if for_votes * coherence_boost > against_votes {
proposal.status = ProposalStatus::Executed;
return true;
} else {
proposal.status = ProposalStatus::Rejected;
return false;
}
}
}
false
}
/// Advance simulation by one tick
pub fn tick(&mut self, dt: f32) {
self.tick += 1;
self.sync.step(dt);
// Expire old proposals
for proposal in &mut self.proposals {
if proposal.status == ProposalStatus::Pending && self.tick > proposal.expires_at {
proposal.status = ProposalStatus::Expired;
}
}
}
/// Get current synchronization level (0-1)
pub fn synchronization(&self) -> f32 {
self.sync.order_parameter()
}
/// Get coherence between two agents
pub fn agent_coherence(&self, agent_a: &str, agent_b: &str) -> f32 {
self.sync.coherence(agent_a, agent_b)
}
/// Get all active proposals
pub fn active_proposals(&self) -> Vec<&Proposal> {
self.proposals
.iter()
.filter(|p| p.status == ProposalStatus::Pending)
.collect()
}
/// Get proposal by ID
pub fn get_proposal(&self, proposal_id: &str) -> Option<&Proposal> {
self.proposals.iter().find(|p| p.id == proposal_id)
}
/// Clean up expired/rejected proposals older than given tick threshold
pub fn cleanup(&mut self, tick_threshold: u64) {
self.proposals.retain(|p| {
p.status == ProposalStatus::Pending
|| p.status == ProposalStatus::Executed
|| p.created_at + tick_threshold > self.tick
});
}
/// Get total voting power in the organization
pub fn total_voting_power(&self) -> f32 {
self.members.values().map(|&s| (s as f32).sqrt()).sum()
}
/// Get current tick
pub fn current_tick(&self) -> u64 {
self.tick
}
}
// WASM Bindings
/// WASM-bindgen wrapper for NeuralAutonomousOrg
#[wasm_bindgen]
pub struct WasmNAO {
inner: NeuralAutonomousOrg,
}
#[wasm_bindgen]
impl WasmNAO {
/// Create a new NAO with the given quorum threshold (0.0 - 1.0)
#[wasm_bindgen(constructor)]
pub fn new(quorum_threshold: f32) -> Self {
Self {
inner: NeuralAutonomousOrg::new(quorum_threshold),
}
}
/// Add a member agent with initial stake
#[wasm_bindgen(js_name = addMember)]
pub fn add_member(&mut self, agent_id: &str, stake: u32) {
self.inner.add_member(agent_id, stake as u64);
}
/// Remove a member agent
#[wasm_bindgen(js_name = removeMember)]
pub fn remove_member(&mut self, agent_id: &str) {
self.inner.remove_member(agent_id);
}
/// Get member count
#[wasm_bindgen(js_name = memberCount)]
pub fn member_count(&self) -> usize {
self.inner.member_count()
}
/// Create a new proposal, returns proposal ID
pub fn propose(&mut self, action: &str) -> String {
self.inner.propose(action)
}
/// Vote on a proposal
pub fn vote(&mut self, proposal_id: &str, agent_id: &str, weight: f32) -> bool {
self.inner.vote(proposal_id, agent_id, weight)
}
/// Execute a proposal if consensus reached
pub fn execute(&mut self, proposal_id: &str) -> bool {
self.inner.execute(proposal_id)
}
/// Advance simulation by one tick
pub fn tick(&mut self, dt: f32) {
self.inner.tick(dt);
}
/// Get current synchronization level (0-1)
pub fn synchronization(&self) -> f32 {
self.inner.synchronization()
}
/// Get coherence between two agents (0-1)
#[wasm_bindgen(js_name = agentCoherence)]
pub fn agent_coherence(&self, agent_a: &str, agent_b: &str) -> f32 {
self.inner.agent_coherence(agent_a, agent_b)
}
/// Get active proposal count
#[wasm_bindgen(js_name = activeProposalCount)]
pub fn active_proposal_count(&self) -> usize {
self.inner.active_proposals().len()
}
/// Get total voting power
#[wasm_bindgen(js_name = totalVotingPower)]
pub fn total_voting_power(&self) -> f32 {
self.inner.total_voting_power()
}
/// Get current tick
#[wasm_bindgen(js_name = currentTick)]
pub fn current_tick(&self) -> u32 {
self.inner.current_tick() as u32
}
/// Get all data as JSON
#[wasm_bindgen(js_name = toJson)]
pub fn to_json(&self) -> Result<JsValue, JsValue> {
serde_wasm_bindgen::to_value(&self.inner).map_err(|e| JsValue::from_str(&e.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nao_creation() {
let nao = NeuralAutonomousOrg::new(0.5);
assert_eq!(nao.member_count(), 0);
assert_eq!(nao.synchronization(), 0.0);
}
#[test]
fn test_member_management() {
let mut nao = NeuralAutonomousOrg::new(0.5);
nao.add_member("agent_1", 100);
nao.add_member("agent_2", 50);
assert_eq!(nao.member_count(), 2);
assert_eq!(nao.get_stake("agent_1"), Some(100));
assert_eq!(nao.get_stake("agent_2"), Some(50));
nao.remove_member("agent_1");
assert_eq!(nao.member_count(), 1);
assert_eq!(nao.get_stake("agent_1"), None);
}
#[test]
fn test_stake_update() {
let mut nao = NeuralAutonomousOrg::new(0.5);
nao.add_member("agent_1", 100);
let new_stake = nao.update_stake("agent_1", 50);
assert_eq!(new_stake, Some(150));
let new_stake = nao.update_stake("agent_1", -200);
assert_eq!(new_stake, Some(0)); // Can't go negative
assert_eq!(nao.update_stake("nonexistent", 10), None);
}
#[test]
fn test_proposal_lifecycle() {
let mut nao = NeuralAutonomousOrg::new(0.5);
nao.add_member("agent_1", 100);
nao.add_member("agent_2", 100);
let prop_id = nao.propose("Test action");
assert_eq!(nao.active_proposals().len(), 1);
// Vote
assert!(nao.vote(&prop_id, "agent_1", 1.0));
assert!(nao.vote(&prop_id, "agent_2", 0.8));
// Execute
assert!(nao.execute(&prop_id));
// Should be executed now
let proposal = nao.get_proposal(&prop_id).unwrap();
assert_eq!(proposal.status, ProposalStatus::Executed);
}
#[test]
fn test_quorum_requirement() {
let mut nao = NeuralAutonomousOrg::new(0.7); // 70% quorum
nao.add_member("agent_1", 100);
nao.add_member("agent_2", 100);
nao.add_member("agent_3", 100);
let prop_id = nao.propose("Test action");
// Only one vote - should not reach quorum
nao.vote(&prop_id, "agent_1", 1.0);
assert!(!nao.execute(&prop_id));
// Add second vote - still below 70%
nao.vote(&prop_id, "agent_2", 1.0);
// 2/3 = 66.7% < 70%
assert!(!nao.execute(&prop_id));
// Add third vote - now above quorum
nao.vote(&prop_id, "agent_3", 1.0);
assert!(nao.execute(&prop_id));
}
#[test]
fn test_voting_rejection() {
let mut nao = NeuralAutonomousOrg::new(0.5);
nao.add_member("agent_1", 100);
nao.add_member("agent_2", 100);
nao.add_member("agent_3", 100);
let prop_id = nao.propose("Controversial action");
// Two against, one weak for - should be rejected even with coherence boost
nao.vote(&prop_id, "agent_1", 0.3); // weak support
nao.vote(&prop_id, "agent_2", -1.0); // strong against
nao.vote(&prop_id, "agent_3", -1.0); // strong against
// Should be rejected (more against than for)
assert!(!nao.execute(&prop_id));
let proposal = nao.get_proposal(&prop_id).unwrap();
assert_eq!(proposal.status, ProposalStatus::Rejected);
}
#[test]
fn test_oscillatory_synchronization() {
let mut nao = NeuralAutonomousOrg::new(0.5);
nao.add_member("agent_1", 100);
nao.add_member("agent_2", 100);
nao.add_member("agent_3", 100);
// Initial sync should be low (random phases)
let initial_sync = nao.synchronization();
// Run dynamics to synchronize
for _ in 0..1000 {
nao.tick(0.001); // 1ms steps
}
let final_sync = nao.synchronization();
// Synchronization should increase due to Kuramoto coupling
assert!(
final_sync > initial_sync * 0.5,
"Sync should improve: initial={}, final={}",
initial_sync,
final_sync
);
}
#[test]
fn test_coherence_between_agents() {
let mut nao = NeuralAutonomousOrg::new(0.5);
nao.add_member("agent_1", 100);
nao.add_member("agent_2", 100);
// Run to synchronize
for _ in 0..2000 {
nao.tick(0.001);
}
let coherence = nao.agent_coherence("agent_1", "agent_2");
assert!(
coherence >= 0.0 && coherence <= 1.0,
"Coherence should be in [0,1]: {}",
coherence
);
}
#[test]
fn test_proposal_expiration() {
let mut nao = NeuralAutonomousOrg::new(0.5);
nao.proposal_ttl = 10; // Short TTL for testing
nao.add_member("agent_1", 100);
let prop_id = nao.propose("Expiring action");
// Advance past TTL
for _ in 0..15 {
nao.tick(1.0);
}
let proposal = nao.get_proposal(&prop_id).unwrap();
assert_eq!(proposal.status, ProposalStatus::Expired);
}
#[test]
fn test_non_member_cannot_vote() {
let mut nao = NeuralAutonomousOrg::new(0.5);
nao.add_member("agent_1", 100);
let prop_id = nao.propose("Test");
// Non-member vote should fail
assert!(!nao.vote(&prop_id, "stranger", 1.0));
}
#[test]
fn test_quadratic_voting_power() {
let mut nao = NeuralAutonomousOrg::new(0.1); // Low quorum for testing
// Agent with 100 stake has sqrt(100) = 10 voting power
// Agent with 25 stake has sqrt(25) = 5 voting power
nao.add_member("rich", 100);
nao.add_member("poor", 25);
let prop_id = nao.propose("Favor rich");
// Rich votes against, poor votes for
nao.vote(&prop_id, "rich", -1.0); // -10 effective vote
nao.vote(&prop_id, "poor", 1.0); // +5 effective vote
// Rich should win despite being one agent
assert!(!nao.execute(&prop_id)); // Rejected
let proposal = nao.get_proposal(&prop_id).unwrap();
assert_eq!(proposal.status, ProposalStatus::Rejected);
}
#[test]
fn test_total_voting_power() {
let mut nao = NeuralAutonomousOrg::new(0.5);
nao.add_member("agent_1", 100); // sqrt(100) = 10
nao.add_member("agent_2", 25); // sqrt(25) = 5
let total = nao.total_voting_power();
assert!((total - 15.0).abs() < 0.01, "Expected ~15, got {}", total);
}
}