🚀Transform your business with AI-powered process optimization
Architecture
💡 Design Patterns
Agent Design Patterns
Blackboard Pattern

Blackboard Pattern

The Blackboard Pattern enables multiple AI agents to collaborate by sharing information through a common knowledge space, allowing cooperative problem-solving.

Pattern Overview

This pattern creates a shared workspace (blackboard) where multiple specialist agents can read and write information, enabling collaborative problem-solving without direct agent-to-agent communication.

Structure

use std::collections::HashMap;
use std::sync::{Arc, Mutex};
 
// Knowledge item on the blackboard
#[derive(Debug, Clone)]
pub struct KnowledgeItem {
    pub id: String,
    pub content: String,
    pub agent_id: String,
    pub confidence: f64,
    pub timestamp: u64,
}
 
// Blackboard shared knowledge space
pub struct Blackboard {
    knowledge_base: Arc<Mutex<HashMap<String, KnowledgeItem>>>,
    subscribers: Arc<Mutex<Vec<String>>>,
}
 
impl Blackboard {
    pub fn new() -> Self {
        Self {
            knowledge_base: Arc::new(Mutex::new(HashMap::new())),
            subscribers: Arc::new(Mutex::new(Vec::new())),
        }
    }
 
    pub fn write_knowledge(&self, item: KnowledgeItem) {
        let mut kb = self.knowledge_base.lock().unwrap();
        kb.insert(item.id.clone(), item);
    }
 
    pub fn read_knowledge(&self, id: &str) -> Option<KnowledgeItem> {
        let kb = self.knowledge_base.lock().unwrap();
        kb.get(id).cloned()
    }
 
    pub fn query_knowledge(&self, pattern: &str) -> Vec<KnowledgeItem> {
        let kb = self.knowledge_base.lock().unwrap();
        kb.values()
            .filter(|item| item.content.contains(pattern))
            .cloned()
            .collect()
    }
 
    pub fn get_all_knowledge(&self) -> Vec<KnowledgeItem> {
        let kb = self.knowledge_base.lock().unwrap();
        kb.values().cloned().collect()
    }
 
    pub fn subscribe(&self, agent_id: String) {
        let mut subs = self.subscribers.lock().unwrap();
        subs.push(agent_id);
    }
}
 
// Specialist agent trait
pub trait SpecialistAgent {
    fn get_id(&self) -> &str;
    fn can_contribute(&self, knowledge: &[KnowledgeItem]) -> bool;
    fn analyze_and_contribute(&self, blackboard: &Blackboard) -> Option<KnowledgeItem>;
    fn get_expertise_domain(&self) -> &str;
}
 
// Data analysis specialist
pub struct DataAnalysisAgent {
    id: String,
}
 
impl DataAnalysisAgent {
    pub fn new(id: &str) -> Self {
        Self { id: id.to_string() }
    }
}
 
impl SpecialistAgent for DataAnalysisAgent {
    fn get_id(&self) -> &str {
        &self.id
    }
 
    fn can_contribute(&self, knowledge: &[KnowledgeItem]) -> bool {
        knowledge.iter().any(|item| 
            item.content.contains("data") || item.content.contains("statistics")
        )
    }
 
    fn analyze_and_contribute(&self, blackboard: &Blackboard) -> Option<KnowledgeItem> {
        let all_knowledge = blackboard.get_all_knowledge();
        
        if self.can_contribute(&all_knowledge) {
            Some(KnowledgeItem {
                id: format!("{}_analysis_{}", self.id, chrono::Utc::now().timestamp()),
                content: "Statistical analysis shows trend upward".to_string(),
                agent_id: self.id.clone(),
                confidence: 0.85,
                timestamp: chrono::Utc::now().timestamp() as u64,
            })
        } else {
            None
        }
    }
 
    fn get_expertise_domain(&self) -> &str {
        "data_analysis"
    }
}
 
// Pattern recognition specialist
pub struct PatternRecognitionAgent {
    id: String,
}
 
impl PatternRecognitionAgent {
    pub fn new(id: &str) -> Self {
        Self { id: id.to_string() }
    }
}
 
impl SpecialistAgent for PatternRecognitionAgent {
    fn get_id(&self) -> &str {
        &self.id
    }
 
    fn can_contribute(&self, knowledge: &[KnowledgeItem]) -> bool {
        knowledge.iter().any(|item| 
            item.content.contains("pattern") || item.content.contains("trend")
        )
    }
 
    fn analyze_and_contribute(&self, blackboard: &Blackboard) -> Option<KnowledgeItem> {
        let relevant_items = blackboard.query_knowledge("trend");
        
        if !relevant_items.is_empty() {
            Some(KnowledgeItem {
                id: format!("{}_pattern_{}", self.id, chrono::Utc::now().timestamp()),
                content: "Detected recurring pattern in customer behavior".to_string(),
                agent_id: self.id.clone(),
                confidence: 0.75,
                timestamp: chrono::Utc::now().timestamp() as u64,
            })
        } else {
            None
        }
    }
 
    fn get_expertise_domain(&self) -> &str {
        "pattern_recognition"
    }
}
 
// Blackboard system coordinator
pub struct BlackboardSystem {
    blackboard: Blackboard,
    agents: Vec<Box<dyn SpecialistAgent>>,
}
 
impl BlackboardSystem {
    pub fn new() -> Self {
        Self {
            blackboard: Blackboard::new(),
            agents: Vec::new(),
        }
    }
 
    pub fn add_agent(&mut self, agent: Box<dyn SpecialistAgent>) {
        self.blackboard.subscribe(agent.get_id().to_string());
        self.agents.push(agent);
    }
 
    pub fn solve_problem(&self, initial_data: KnowledgeItem) {
        // Post initial problem to blackboard
        self.blackboard.write_knowledge(initial_data);
 
        // Let agents contribute iteratively
        for _iteration in 0..5 {
            for agent in &self.agents {
                if let Some(contribution) = agent.analyze_and_contribute(&self.blackboard) {
                    println!("Agent {} contributed: {}", agent.get_id(), contribution.content);
                    self.blackboard.write_knowledge(contribution);
                }
            }
        }
    }
 
    pub fn get_solution(&self) -> Vec<KnowledgeItem> {
        self.blackboard.get_all_knowledge()
    }
}

Usage Example

let mut system = BlackboardSystem::new();
 
// Add specialist agents
system.add_agent(Box::new(DataAnalysisAgent::new("data_expert")));
system.add_agent(Box::new(PatternRecognitionAgent::new("pattern_expert")));
 
// Present problem to the system
let problem = KnowledgeItem {
    id: "initial_problem".to_string(),
    content: "Need to analyze customer data trends".to_string(),
    agent_id: "user".to_string(),
    confidence: 1.0,
    timestamp: 0,
};
 
system.solve_problem(problem);
let solution = system.get_solution();
 
for item in solution {
    println!("Knowledge: {} (confidence: {})", item.content, item.confidence);
}

Benefits

  • Collaboration: Multiple agents work together naturally
  • Modularity: Easy to add new specialist agents
  • Flexibility: Agents contribute when they have relevant expertise
  • Transparency: All knowledge is visible to all agents

Use Cases

  • Complex data analysis requiring multiple perspectives
  • Collaborative decision-making systems
  • Expert system combinations
  • Multi-disciplinary problem solving