collet 0.1.1

Relentless agentic coding orchestrator with zero-drop agent loops
Documentation
//! Blackboard implementation for Flock decentralized coordination.

use crate::agent::swarm::knowledge::SharedKnowledge;
use crate::agent::swarm::knowledge::types::{BlackboardEntry, BlackboardKind, ConsensusResult};

impl SharedKnowledge {
    /// Post an entry to the blackboard (Flock decentralized coordination).
    pub async fn post_to_blackboard(
        &self,
        key: &str,
        value: &str,
        author: &str,
        kind: BlackboardKind,
    ) {
        let mut inner = self.inner.write().await;
        inner.blackboard.insert(
            key.to_string(),
            BlackboardEntry {
                key: key.to_string(),
                value: value.to_string(),
                author: author.to_string(),
                kind,
                votes_for: vec![author.to_string()], // author auto-votes for
                votes_against: Vec::new(),
                timestamp: std::time::Instant::now(),
            },
        );
    }

    /// Read a blackboard entry by key.
    pub async fn read_blackboard(&self, key: &str) -> Option<BlackboardEntry> {
        let inner = self.inner.read().await;
        inner.blackboard.get(key).cloned()
    }

    /// Read all blackboard entries whose key starts with `prefix`.
    #[allow(dead_code)]
    pub async fn query_blackboard_prefix(&self, prefix: &str) -> Vec<BlackboardEntry> {
        let inner = self.inner.read().await;
        inner
            .blackboard
            .range(prefix.to_string()..)
            .take_while(|(k, _)| k.starts_with(prefix))
            .map(|(_, v)| v.clone())
            .collect()
    }

    /// Read all blackboard entries of a given kind.
    pub async fn blackboard_by_kind(&self, kind: BlackboardKind) -> Vec<BlackboardEntry> {
        let inner = self.inner.read().await;
        inner
            .blackboard
            .values()
            .filter(|e| e.kind == kind)
            .cloned()
            .collect()
    }

    /// Vote on a blackboard proposal (for/against).
    pub async fn vote_on_proposal(
        &self,
        key: &str,
        voter_id: &str,
        approve: bool,
    ) -> Option<ConsensusResult> {
        let mut inner = self.inner.write().await;

        let entry = inner.blackboard.get_mut(key)?;
        if entry.kind != BlackboardKind::Proposal {
            return None;
        }

        // Remove previous vote if any
        entry.votes_for.retain(|v| v != voter_id);
        entry.votes_against.retain(|v| v != voter_id);

        if approve {
            entry.votes_for.push(voter_id.to_string());
        } else {
            entry.votes_against.push(voter_id.to_string());
        }

        Some(ConsensusResult {
            key: key.to_string(),
            approved: approve,
            votes_for: entry.votes_for.len(),
            votes_against: entry.votes_against.len(),
            total_agents: 0, // caller should fill in
        })
    }

    /// Check consensus on a proposal.
    /// Returns `Some(true)` if majority approves, `Some(false)` if majority rejects,
    /// `None` if not enough votes yet.
    pub async fn check_consensus(&self, key: &str, total_agents: usize) -> Option<ConsensusResult> {
        let inner = self.inner.read().await;
        let entry = inner.blackboard.get(key)?;

        let for_count = entry.votes_for.len();
        let against_count = entry.votes_against.len();
        let total_votes = for_count + against_count;
        let majority = total_agents.div_ceil(2); // ceil(n/2)

        let approved = if for_count >= majority {
            true
        } else if against_count >= majority {
            false
        } else if total_votes >= total_agents {
            // All voted, go with majority
            for_count >= against_count
        } else {
            // Not enough votes yet
            return None;
        };

        Some(ConsensusResult {
            key: key.to_string(),
            approved,
            votes_for: for_count,
            votes_against: against_count,
            total_agents,
        })
    }

    /// Promote a proposal to a decision after consensus.
    pub async fn finalize_proposal(&self, key: &str) -> bool {
        let mut inner = self.inner.write().await;
        if let Some(entry) = inner.blackboard.get_mut(key)
            && entry.kind == BlackboardKind::Proposal
        {
            entry.kind = BlackboardKind::Decision;
            return true;
        }
        false
    }
}