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