AgentLifecycle Trait
The AgentLifecycle trait provides optional lifecycle control methods for agents that need fine-grained execution management. It extends the core MoFAAgent trait with pause and resume capabilities.
Location: mofa-kernel/src/agent/core.rs:329
Trait Definition
#[async_trait]
pub trait AgentLifecycle: MoFAAgent {
async fn pause(&mut self) -> AgentResult<()>;
async fn resume(&mut self) -> AgentResult<()>;
}
Design Philosophy
The AgentLifecycle trait is optional and follows the microkernel principle of separating core from extensions:
- Core functionality is in
MoFAAgent (initialize, execute, shutdown)
- Optional functionality is in extension traits like
AgentLifecycle
- Only implement this trait if your agent needs pause/resume control
Not all agents need pause/resume functionality. Only implement AgentLifecycle when your use case requires it (e.g., long-running tasks, user-controlled execution, resource management).
Required Methods
pause
async fn pause(&mut self) -> AgentResult<()>
Pauses the current execution, preserving state for later resumption.
State Transition: Executing → Paused
Use Cases:
- Long-running tasks that need user-controlled pausing
- Resource management (temporarily release resources)
- Debugging and inspection
- Graceful handling of system events
Example:
use mofa_sdk::kernel::{AgentLifecycle, AgentResult, AgentState};
use async_trait::async_trait;
struct MyAgent {
state: AgentState,
// Agent state fields...
}
#[async_trait]
impl AgentLifecycle for MyAgent {
async fn pause(&mut self) -> AgentResult<()> {
// Save current progress
self.save_checkpoint().await?;
// Update state
self.state = AgentState::Paused;
// Release resources if needed
self.release_temporary_resources().await?;
Ok(())
}
async fn resume(&mut self) -> AgentResult<()> {
// Restore resources
self.acquire_resources().await?;
// Load checkpoint
self.load_checkpoint().await?;
// Update state
self.state = AgentState::Ready;
Ok(())
}
}
resume
async fn resume(&mut self) -> AgentResult<()>
Resumes execution from a paused state.
State Transition: Paused → Ready
Use Cases:
- Continue paused tasks
- Restore from saved checkpoints
- Re-acquire resources after pause
State Transitions
The AgentLifecycle trait interacts with the agent state machine:
┌──────────┐
│ Created │
└────┬─────┘
│ initialize()
┌────▼──────────┐
│ Initializing │
└────┬──────────┘
│
┌────▼─────┐
┌────────│ Ready │◄──────────┐
│ └────┬─────┘ │
│ execute() │ │ resume()
│ ┌────▼──────┐ │
│ │ Executing │ │
│ └─────┬─────┘ │
│ │ │
│ │ pause() │
│ ┌─────▼─────┐ │
└───────►│ Paused │──────────┘
└───────────┘
Complete Implementation Example
use mofa_sdk::kernel::{
MoFAAgent, AgentLifecycle, AgentInput, AgentOutput, AgentContext,
AgentResult, AgentState, AgentCapabilities,
};
use async_trait::async_trait;
use std::collections::HashMap;
struct LongRunningAgent {
id: String,
name: String,
capabilities: AgentCapabilities,
state: AgentState,
progress: usize,
checkpoint: Option<HashMap<String, serde_json::Value>>,
}
#[async_trait]
impl MoFAAgent for LongRunningAgent {
fn id(&self) -> &str {
&self.id
}
fn name(&self) -> &str {
&self.name
}
fn capabilities(&self) -> &AgentCapabilities {
&self.capabilities
}
fn state(&self) -> AgentState {
self.state.clone()
}
async fn initialize(&mut self, _ctx: &AgentContext) -> AgentResult<()> {
self.state = AgentState::Ready;
self.progress = 0;
Ok(())
}
async fn execute(
&mut self,
input: AgentInput,
ctx: &AgentContext,
) -> AgentResult<AgentOutput> {
self.state = AgentState::Executing;
// Long-running task with pause check
let total_steps = 100;
for step in self.progress..total_steps {
// Check for pause/interrupt
if ctx.is_interrupted() {
break;
}
if self.state == AgentState::Paused {
self.progress = step;
break;
}
// Process step
self.process_step(step).await?;
self.progress = step + 1;
}
self.state = AgentState::Ready;
Ok(AgentOutput::text(format!("Completed {} steps", self.progress)))
}
async fn shutdown(&mut self) -> AgentResult<()> {
self.state = AgentState::Shutdown;
self.checkpoint = None;
Ok(())
}
}
#[async_trait]
impl AgentLifecycle for LongRunningAgent {
async fn pause(&mut self) -> AgentResult<()> {
// Save checkpoint
let mut checkpoint = HashMap::new();
checkpoint.insert(
"progress".to_string(),
serde_json::json!(self.progress),
);
self.checkpoint = Some(checkpoint);
// Update state
self.state = AgentState::Paused;
println!("Agent paused at step {}", self.progress);
Ok(())
}
async fn resume(&mut self) -> AgentResult<()> {
// Restore from checkpoint if available
if let Some(checkpoint) = &self.checkpoint {
if let Some(progress) = checkpoint.get("progress") {
self.progress = progress.as_u64().unwrap_or(0) as usize;
}
}
// Update state
self.state = AgentState::Ready;
println!("Agent resumed from step {}", self.progress);
Ok(())
}
}
impl LongRunningAgent {
async fn process_step(&self, step: usize) -> AgentResult<()> {
// Simulate work
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
println!("Processing step {}", step);
Ok(())
}
}
Usage Example
use mofa_sdk::kernel::{AgentContext, AgentInput, AgentLifecycle};
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut agent = LongRunningAgent {
id: "long-runner".to_string(),
name: "Long Running Agent".to_string(),
capabilities: AgentCapabilities::default(),
state: AgentState::Created,
progress: 0,
checkpoint: None,
};
let ctx = AgentContext::new("exec-001");
// Initialize
agent.initialize(&ctx).await?;
// Start execution in background
let agent_clone = agent.clone();
let ctx_clone = ctx.clone();
let handle = tokio::spawn(async move {
agent_clone.execute(AgentInput::Empty, &ctx_clone).await
});
// Pause after 2 seconds
tokio::time::sleep(Duration::from_secs(2)).await;
agent.pause().await?;
println!("Agent paused!");
// Resume after 1 second
tokio::time::sleep(Duration::from_secs(1)).await;
agent.resume().await?;
println!("Agent resumed!");
// Wait for completion
let output = handle.await??;
println!("Result: {}", output.to_text());
// Shutdown
agent.shutdown().await?;
Ok(())
}
Best Practices
1. Save State on Pause
Always save enough state to resume correctly:
async fn pause(&mut self) -> AgentResult<()> {
// Save all necessary state
self.checkpoint = Some(serde_json::json!({
"progress": self.progress,
"batch_id": self.current_batch_id,
"partial_results": self.partial_results,
}));
self.state = AgentState::Paused;
Ok(())
}
2. Check Pause State in Loops
For long-running tasks, regularly check if pause was requested:
async fn execute(&mut self, input: AgentInput, ctx: &AgentContext) -> AgentResult<AgentOutput> {
for item in large_dataset {
// Check for pause
if self.state == AgentState::Paused {
return Ok(AgentOutput::text("Paused"));
}
// Check for interrupt
if ctx.is_interrupted() {
break;
}
process_item(item).await?;
}
Ok(AgentOutput::text("Complete"))
}
3. Release Resources on Pause
Free up resources when paused:
async fn pause(&mut self) -> AgentResult<()> {
// Release database connections
self.db_pool.close().await?;
// Close file handles
self.file_handles.clear();
// Save state
self.save_checkpoint().await?;
self.state = AgentState::Paused;
Ok(())
}
async fn resume(&mut self) -> AgentResult<()> {
// Re-acquire resources
self.db_pool = create_pool().await?;
// Restore state
self.load_checkpoint().await?;
self.state = AgentState::Ready;
Ok(())
}
4. Handle Resume Failures
Handle cases where resume might fail:
async fn resume(&mut self) -> AgentResult<()> {
// Attempt to restore checkpoint
match self.load_checkpoint().await {
Ok(_) => {
self.state = AgentState::Ready;
Ok(())
}
Err(e) => {
// If checkpoint is corrupted, reset to initial state
tracing::warn!("Failed to load checkpoint: {}, resetting", e);
self.progress = 0;
self.state = AgentState::Ready;
Ok(())
}
}
}
The MoFAAgent trait has other optional extensions:
- AgentMessaging - Message and event handling
- AgentPluginSupport - Plugin registration and management
All extension traits follow the same optional pattern - only implement them when needed.
See Also