Command Pattern
The Command Pattern encapsulates agent requests as objects, enabling parameterization, queuing, logging, and undo operations for AI agent actions.
Pattern Overview
This pattern turns requests into stand-alone objects containing all information about the request, allowing you to queue operations, log them, and support undo.
Structure
// Command trait
pub trait Command {
fn execute(&self) -> Result<String, String>;
fn undo(&self) -> Result<String, String>;
fn get_description(&self) -> &str;
}
// Receiver - the object that performs the actual work
pub struct DataProcessor {
pub data: Vec<String>,
}
impl DataProcessor {
pub fn new() -> Self {
Self { data: Vec::new() }
}
pub fn add_data(&mut self, item: String) -> Result<String, String> {
self.data.push(item.clone());
Ok(format!("Added: {}", item))
}
pub fn remove_data(&mut self, item: &str) -> Result<String, String> {
if let Some(pos) = self.data.iter().position(|x| x == item) {
self.data.remove(pos);
Ok(format!("Removed: {}", item))
} else {
Err(format!("Item not found: {}", item))
}
}
}
// Concrete command implementations
pub struct AddDataCommand {
data: String,
description: String,
}
impl AddDataCommand {
pub fn new(data: String) -> Self {
let description = format!("Add data: {}", data);
Self { data, description }
}
}
impl Command for AddDataCommand {
fn execute(&self) -> Result<String, String> {
// In real implementation, would have reference to processor
Ok(format!("Executing: Add {}", self.data))
}
fn undo(&self) -> Result<String, String> {
Ok(format!("Undoing: Remove {}", self.data))
}
fn get_description(&self) -> &str {
&self.description
}
}
// Command invoker
pub struct AgentCommandInvoker {
history: Vec<Box<dyn Command>>,
current: usize,
}
impl AgentCommandInvoker {
pub fn new() -> Self {
Self {
history: Vec::new(),
current: 0,
}
}
pub fn execute_command(&mut self, command: Box<dyn Command>) -> Result<String, String> {
let result = command.execute();
self.history.push(command);
self.current += 1;
result
}
pub fn undo(&mut self) -> Result<String, String> {
if self.current > 0 {
self.current -= 1;
self.history[self.current].undo()
} else {
Err("Nothing to undo".to_string())
}
}
}Usage Example
let mut invoker = AgentCommandInvoker::new();
let command = AddDataCommand::new("customer_data.csv".to_string());
// Execute command
invoker.execute_command(Box::new(command));
// Undo if needed
invoker.undo();Benefits
- Decoupling: Separates request initiation from execution
- Undo/Redo: Built-in support for reversible operations
- Logging: Easy to log all agent commands
- Queuing: Commands can be queued and executed later
Use Cases
- Agent task scheduling and execution
- Implementing undo functionality in agent workflows
- Logging agent actions for audit trails
- Building macro commands from simple operations