Observer Pattern
The Observer Pattern enables AI agents to subscribe to and receive notifications about state changes in other components, facilitating reactive behavior.
Pattern Overview
This pattern defines a one-to-many dependency between objects, so when one object changes state, all dependents are notified automatically.
Structure
use std::collections::HashMap;
// Event types
#[derive(Debug, Clone)]
pub enum AgentEvent {
TaskCompleted(String),
ErrorOccurred(String),
StateChanged(String),
}
// Observer trait
pub trait Observer {
fn update(&mut self, event: &AgentEvent);
fn get_id(&self) -> &str;
}
// Subject trait
pub trait Subject {
fn attach(&mut self, observer: Box<dyn Observer>);
fn detach(&mut self, observer_id: &str);
fn notify(&self, event: &AgentEvent);
}
// Concrete observer implementation
pub struct MonitoringAgent {
id: String,
}
impl MonitoringAgent {
pub fn new(id: &str) -> Self {
Self { id: id.to_string() }
}
}
impl Observer for MonitoringAgent {
fn update(&mut self, event: &AgentEvent) {
println!("Monitor {}: Received event {:?}", self.id, event);
}
fn get_id(&self) -> &str {
&self.id
}
}
// Concrete subject implementation
pub struct WorkerAgent {
observers: HashMap<String, Box<dyn Observer>>,
state: String,
}
impl WorkerAgent {
pub fn new() -> Self {
Self {
observers: HashMap::new(),
state: "idle".to_string(),
}
}
pub fn do_work(&mut self, task: &str) {
self.state = format!("working on {}", task);
self.notify(&AgentEvent::StateChanged(self.state.clone()));
// Simulate work completion
self.notify(&AgentEvent::TaskCompleted(task.to_string()));
}
}
impl Subject for WorkerAgent {
fn attach(&mut self, observer: Box<dyn Observer>) {
let id = observer.get_id().to_string();
self.observers.insert(id, observer);
}
fn detach(&mut self, observer_id: &str) {
self.observers.remove(observer_id);
}
fn notify(&self, event: &AgentEvent) {
for observer in self.observers.values() {
// Note: This simplified example doesn't show mutable access
println!("Notifying observer about: {:?}", event);
}
}
}Usage Example
let mut worker = WorkerAgent::new();
let monitor = MonitoringAgent::new("monitor_1");
worker.attach(Box::new(monitor));
worker.do_work("data analysis");Benefits
- Loose Coupling: Subjects and observers are loosely coupled
- Dynamic Relationships: Add/remove observers at runtime
- Broadcast Communication: One-to-many communication pattern
- Separation of Concerns: Clear separation between core logic and notifications
Use Cases
- Agent performance monitoring and alerting
- Event-driven agent communication
- Logging and auditing agent activities
- Triggering dependent agent actions