Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/mofa-org/mofa/llms.txt

Use this file to discover all available pages before exploring further.

Understand how agents move through different states and how to manage their lifecycle effectively in the MoFA framework.

Agent States

Every MoFA agent goes through a defined lifecycle with the following states:
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AgentState {
    Created,      // Agent instance created but not initialized
    Initializing, // Resources being loaded
    Ready,        // Ready to accept tasks
    Executing,    // Currently processing a task
    Paused,       // Execution paused, can be resumed
    Interrupted,  // Execution interrupted by external signal
    ShuttingDown, // Cleanup in progress
    Shutdown,     // Agent stopped, resources released
    Failed,       // Error state
}

State Transitions

Core Lifecycle Methods

The MoFAAgent trait defines the core lifecycle interface.

Method Signatures

#[async_trait]
pub trait MoFAAgent: Send + Sync + 'static {
    // Identity
    fn id(&self) -> &str;
    fn name(&self) -> &str;
    fn capabilities(&self) -> &AgentCapabilities;
    fn state(&self) -> AgentState;

    // Core lifecycle
    async fn initialize(&mut self, ctx: &AgentContext) -> AgentResult<()>;
    async fn execute(&mut self, input: AgentInput, ctx: &AgentContext) -> AgentResult<AgentOutput>;
    async fn shutdown(&mut self) -> AgentResult<()>;
    async fn interrupt(&mut self) -> AgentResult<InterruptResult>;
}

initialize

Prepare the agent for execution. Called once before the agent can process tasks.
async fn initialize(&mut self, ctx: &AgentContext) -> AgentResult<()> {
    // Load configuration
    self.config = ctx.get_config("agent.toml")?;
    
    // Establish database connection
    self.db = Database::connect(&self.config.db_url).await?;
    
    // Load ML model
    self.model = Model::load(&self.config.model_path)?;
    
    // Update state
    self.state = AgentState::Ready;
    
    Ok(())
}
Use cases:
  • Load configuration files
  • Establish database/network connections
  • Initialize ML models
  • Allocate resources
  • Validate prerequisites

execute

The main task execution method. Processes input and returns output.
async fn execute(
    &mut self,
    input: AgentInput,
    ctx: &AgentContext
) -> AgentResult<AgentOutput> {
    self.state = AgentState::Executing;
    
    // Check for interrupts
    if ctx.is_interrupted() {
        return Err(AgentError::Interrupted);
    }
    
    // Process input
    let query = input.to_text();
    let result = self.model.predict(&query).await?;
    
    // Emit progress events
    ctx.emit_event(AgentEvent::progress(50));
    
    // Return result
    self.state = AgentState::Ready;
    Ok(AgentOutput::text(result))
}
Key points:
  • Always update state to Executing at start
  • Check for interrupts periodically for long-running tasks
  • Emit events to report progress
  • Return state to Ready on success
  • Handle errors gracefully

shutdown

Gracefully terminate the agent and release resources.
async fn shutdown(&mut self) -> AgentResult<()> {
    self.state = AgentState::ShuttingDown;
    
    // Save state to disk
    self.save_checkpoint().await?;
    
    // Close database connection
    self.db.close().await?;
    
    // Release ML model
    drop(self.model);
    
    // Final cleanup
    self.state = AgentState::Shutdown;
    
    Ok(())
}
Use cases:
  • Save state/checkpoints
  • Close database connections
  • Flush buffers
  • Release memory
  • Clean up temporary files

interrupt

Handle interrupt signals during execution.
async fn interrupt(&mut self) -> AgentResult<InterruptResult> {
    match self.state {
        AgentState::Executing => {
            // Save current progress
            self.save_checkpoint().await?;
            
            self.state = AgentState::Interrupted;
            Ok(InterruptResult::Acknowledged)
        }
        _ => Ok(InterruptResult::Ignored)
    }
}
Interrupt results:
  • Acknowledged: Interrupt accepted, execution stopped
  • Deferred: Will stop after current step
  • Ignored: Agent cannot be interrupted

Extended Lifecycle (Optional)

The AgentLifecycle trait provides additional control methods.

pause

Temporarily suspend execution while preserving state.
#[async_trait]
pub trait AgentLifecycle: MoFAAgent {
    async fn pause(&mut self) -> AgentResult<()>;
    async fn resume(&mut self) -> AgentResult<()>;
}
Implementation example:
#[async_trait]
impl AgentLifecycle for MyAgent {
    async fn pause(&mut self) -> AgentResult<()> {
        if self.state != AgentState::Executing {
            return Err(AgentError::InvalidState(
                "Can only pause during execution".into()
            ));
        }
        
        // Save execution context
        self.checkpoint = Some(self.current_context.clone());
        self.state = AgentState::Paused;
        
        Ok(())
    }
    
    async fn resume(&mut self) -> AgentResult<()> {
        if self.state != AgentState::Paused {
            return Err(AgentError::InvalidState(
                "Can only resume from paused state".into()
            ));
        }
        
        // Restore execution context
        if let Some(checkpoint) = self.checkpoint.take() {
            self.current_context = checkpoint;
        }
        
        self.state = AgentState::Ready;
        Ok(())
    }
}

Complete Lifecycle Example

Here’s a complete agent with full lifecycle management:
use mofa_sdk::kernel::agent::prelude::*;
use mofa_sdk::llm::{LLMClient, openai_from_env};
use std::sync::Arc;

struct ProductionAgent {
    id: String,
    name: String,
    capabilities: AgentCapabilities,
    state: AgentState,
    client: LLMClient,
    checkpoint: Option<String>,
    execution_count: u64,
}

impl ProductionAgent {
    fn new(client: LLMClient) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            name: "Production Agent".to_string(),
            capabilities: AgentCapabilities::builder()
                .tag("production")
                .supports_streaming(true)
                .supports_tools(true)
                .build(),
            state: AgentState::Created,
            client,
            checkpoint: None,
            execution_count: 0,
        }
    }
}

#[async_trait]
impl MoFAAgent for ProductionAgent {
    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<()> {
        tracing::info!("[{}] Initializing agent", self.id);
        self.state = AgentState::Initializing;
        
        // Simulate resource loading
        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
        
        // Verify LLM connection
        self.client.provider().health_check().await
            .map_err(|e| AgentError::InitializationFailed(e.to_string()))?;
        
        self.state = AgentState::Ready;
        tracing::info!("[{}] Agent ready", self.id);
        
        Ok(())
    }
    
    async fn execute(
        &mut self,
        input: AgentInput,
        ctx: &AgentContext
    ) -> AgentResult<AgentOutput> {
        tracing::info!("[{}] Executing task", self.id);
        self.state = AgentState::Executing;
        self.execution_count += 1;
        
        // Check for interrupt
        if ctx.is_interrupted() {
            self.state = AgentState::Interrupted;
            return Err(AgentError::Interrupted);
        }
        
        // Process task
        let result = self.client
            .ask(&input.to_text())
            .await
            .map_err(|e| AgentError::ExecutionFailed(e.to_string()))?;
        
        // Emit completion event
        ctx.emit_event(AgentEvent::execution_completed(
            &self.id,
            self.execution_count
        ));
        
        self.state = AgentState::Ready;
        Ok(AgentOutput::text(result))
    }
    
    async fn shutdown(&mut self) -> AgentResult<()> {
        tracing::info!("[{}] Shutting down", self.id);
        self.state = AgentState::ShuttingDown;
        
        // Save metrics
        tracing::info!(
            "[{}] Total executions: {}",
            self.id,
            self.execution_count
        );
        
        self.state = AgentState::Shutdown;
        Ok(())
    }
    
    async fn interrupt(&mut self) -> AgentResult<InterruptResult> {
        match self.state {
            AgentState::Executing => {
                tracing::warn!("[{}] Interrupt received", self.id);
                self.state = AgentState::Interrupted;
                Ok(InterruptResult::Acknowledged)
            }
            _ => Ok(InterruptResult::Ignored)
        }
    }
}

#[async_trait]
impl AgentLifecycle for ProductionAgent {
    async fn pause(&mut self) -> AgentResult<()> {
        if self.state != AgentState::Executing {
            return Err(AgentError::InvalidState(
                "Can only pause during execution".into()
            ));
        }
        
        tracing::info!("[{}] Pausing execution", self.id);
        self.checkpoint = Some(format!("checkpoint_{}", self.execution_count));
        self.state = AgentState::Paused;
        
        Ok(())
    }
    
    async fn resume(&mut self) -> AgentResult<()> {
        if self.state != AgentState::Paused {
            return Err(AgentError::InvalidState(
                "Can only resume from paused state".into()
            ));
        }
        
        tracing::info!("[{}] Resuming execution", self.id);
        self.checkpoint = None;
        self.state = AgentState::Ready;
        
        Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt::init();
    
    let provider = openai_from_env()?;
    let client = LLMClient::new(Arc::new(provider));
    
    let mut agent = ProductionAgent::new(client);
    let ctx = AgentContext::new("prod-001");
    
    // Initialize
    agent.initialize(&ctx).await?;
    assert_eq!(agent.state(), AgentState::Ready);
    
    // Execute
    let output = agent.execute(
        AgentInput::text("What is Rust?"),
        &ctx
    ).await?;
    
    println!("Result: {}", output.as_text().unwrap());
    
    // Shutdown
    agent.shutdown().await?;
    assert_eq!(agent.state(), AgentState::Shutdown);
    
    Ok(())
}

Best Practices

Update self.state at appropriate points to reflect the agent’s current status. This is crucial for monitoring and debugging.
async fn execute(&mut self, input: AgentInput, ctx: &AgentContext) -> AgentResult<AgentOutput> {
    self.state = AgentState::Executing;  // Start
    
    // ... do work ...
    
    self.state = AgentState::Ready;      // Success
    Ok(output)
}
If initialization fails, set state to Failed and return a descriptive error.
async fn initialize(&mut self, ctx: &AgentContext) -> AgentResult<()> {
    match Database::connect(&url).await {
        Ok(db) => self.db = db,
        Err(e) => {
            self.state = AgentState::Failed;
            return Err(AgentError::InitializationFailed(e.to_string()));
        }
    }
    Ok(())
}
Check for interrupts periodically and respond appropriately.
async fn execute(&mut self, input: AgentInput, ctx: &AgentContext) -> AgentResult<AgentOutput> {
    for i in 0..100 {
        if ctx.is_interrupted() {
            return Err(AgentError::Interrupted);
        }
        
        // Process chunk
        process_chunk(i).await?;
    }
    Ok(output)
}
Always clean up resources in the shutdown method, even if errors occur.
async fn shutdown(&mut self) -> AgentResult<()> {
    self.state = AgentState::ShuttingDown;
    
    // Try to save state, but don't fail shutdown
    let _ = self.save_checkpoint().await;
    
    // Close connections
    self.db.close().await?;
    
    self.state = AgentState::Shutdown;
    Ok(())
}
Use the event bus to communicate state changes for monitoring.
async fn initialize(&mut self, ctx: &AgentContext) -> AgentResult<()> {
    ctx.emit_event(AgentEvent::lifecycle_event(
        &self.id,
        "initializing"
    ));
    
    // ... initialization ...
    
    ctx.emit_event(AgentEvent::lifecycle_event(
        &self.id,
        "ready"
    ));
    Ok(())
}

Next Steps

Capabilities & State

Learn how to define and manage agent capabilities

API Reference

Explore the complete lifecycle API documentation