echo_agent 0.1.4

Production-grade AI Agent framework for Rust — ReAct engine, multi-agent, memory, streaming, MCP, IM channels, workflows
Documentation
use futures::future::BoxFuture;

use crate::agent::subagent::executor::SubagentExecutor;
use crate::error::ToolError;
use crate::tools::{Tool, ToolParameters, ToolResult};
use echo_core::agent::CancellationToken;
use serde_json::{Value, json};
use std::sync::Arc;
use tracing::{debug, info, warn};

pub struct AgentDispatchTool {
    executor: Arc<SubagentExecutor>,
    parent_agent: String,
    cancel: CancellationToken,
}

impl AgentDispatchTool {
    pub fn new(
        executor: Arc<SubagentExecutor>,
        parent_agent: impl Into<String>,
        cancel: CancellationToken,
    ) -> Self {
        Self {
            executor,
            parent_agent: parent_agent.into(),
            cancel,
        }
    }
}

impl Tool for AgentDispatchTool {
    fn name(&self) -> &str {
        "agent_tool"
    }

    fn description(&self) -> &str {
        "Dispatch a task to a specialized SubAgent for execution. As the orchestrator, prefer using this tool to delegate computation, data fetching, etc. to professional SubAgents rather than answering directly."
    }

    fn parameters(&self) -> Value {
        // NOTE: agent_names would require async, so we provide a generic description
        json!({
            "type": "object",
            "properties": {
                "agent_name": {
                    "type": "string",
                    "description": "SubAgent name"
                },
                "task": {
                    "type": "string",
                    "description": "Specific task description to assign to the SubAgent, should include necessary context"
                },
                "mode": {
                    "type": "string",
                    "enum": ["sync", "fork", "teammate"],
                    "description": "Execution mode: sync - synchronous wait (default), fork - independent with inherited context, teammate - parallel collaboration"
                }
            },
            "required": ["agent_name", "task"]
        })
    }

    fn execute(
        &self,
        parameters: ToolParameters,
    ) -> BoxFuture<'_, crate::error::Result<ToolResult>> {
        let executor = self.executor.clone();
        let parent_agent = self.parent_agent.clone();
        let cancel = self.cancel.clone();

        Box::pin(async move {
            let agent_name = parameters
                .get("agent_name")
                .and_then(|v| v.as_str())
                .ok_or_else(|| ToolError::MissingParameter("agent_name".to_string()))?;

            let task = parameters
                .get("task")
                .and_then(|v| v.as_str())
                .ok_or_else(|| ToolError::MissingParameter("task".to_string()))?;

            let mode_override =
                parameters
                    .get("mode")
                    .and_then(|v| v.as_str())
                    .and_then(|m| match m {
                        "sync" => Some(crate::agent::subagent::ExecutionMode::Sync),
                        "fork" => Some(crate::agent::subagent::ExecutionMode::Fork),
                        "teammate" => Some(crate::agent::subagent::ExecutionMode::Teammate),
                        _ => None,
                    });

            info!(
                target_agent = %agent_name,
                task = %task,
                mode = ?mode_override,
                "Dispatching task to subagent via SubagentExecutor"
            );

            let req = crate::agent::subagent::DispatchRequest {
                agent_name: agent_name.to_string(),
                task: task.to_string(),
                mode_override,
                cancel,
                parent_agent: parent_agent.clone(),
                parent_context: None, // Context inheritance handled by executor based on definition
                delegate_depth: 0,
            };

            match executor.dispatch(req).await {
                Ok(result) => {
                    info!(target_agent = %agent_name, "Subagent completed successfully");
                    debug!(target_agent = %agent_name, output = %result.output, "Subagent result");
                    Ok(ToolResult::success(result.output))
                }
                Err(e) => {
                    warn!(target_agent = %agent_name, error = %e, "Subagent execution failed");
                    Ok(ToolResult::error(format!(
                        "SubAgent '{}' execution failed: {}",
                        agent_name, e
                    )))
                }
            }
        })
    }
}