phi-kernel-tools 0.1.0

Kernel tools for phi-agent — file system, multi-agent, and shell tools
Documentation
use std::sync::Arc;

use agent_base::{AgentResult, ToolContext, ToolControlFlow, TypedTool};
use agent_works::multi_agent::MultiAgentRuntime;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};

#[derive(Debug, Deserialize)]
pub struct SpawnAgentArgs {
    pub task_name: String,
    pub message: String,
    #[serde(default)]
    pub agent_type: Option<String>,
    #[serde(default)]
    pub system_prompt: Option<String>,
    #[serde(default)]
    #[allow(dead_code)]
    pub model: Option<String>,
    #[serde(default)]
    #[allow(dead_code)]
    pub reasoning_effort: Option<String>,
    #[serde(default)]
    #[allow(dead_code)]
    pub fork_history: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct SpawnAgentOutput {
    pub agent_path: String,
    pub message: String,
}

pub struct SpawnAgentTool {
    runtime: Arc<MultiAgentRuntime>,
}

impl SpawnAgentTool {
    pub fn new(runtime: Arc<MultiAgentRuntime>) -> Self {
        Self { runtime }
    }
}

#[async_trait::async_trait]
impl TypedTool for SpawnAgentTool {
    type Args = SpawnAgentArgs;
    type Output = SpawnAgentOutput;

    fn name(&self) -> &'static str {
        "spawn_agent"
    }

    fn description(&self) -> &'static str {
        "Spawn a new sub-agent to execute a task independently.\n\
         The sub-agent runs concurrently and reports results via its mailbox.\n\
         Use agent_type for preset roles, or system_prompt for custom instructions."
    }

    fn parameters_schema(&self) -> Value {
        json!({
            "type": "object",
            "properties": {
                "task_name": {
                    "type": "string",
                    "description": "Unique name for this sub-agent (used in agent path)"
                },
                "message": {
                    "type": "string",
                    "description": "Initial task description for the sub-agent"
                },
                "agent_type": {
                    "type": "string",
                    "description": "Optional role type that maps to a preset configuration"
                },
                "system_prompt": {
                    "type": "string",
                    "description": "Optional custom system prompt (overrides agent_type)"
                },
                "model": {
                    "type": "string",
                    "description": "Optional model override"
                },
                "reasoning_effort": {
                    "type": "string",
                    "description": "Optional reasoning effort (low/medium/high)"
                },
                "fork_history": {
                    "type": "string",
                    "description": "Optional history: 'none' (default), 'all', or a number N"
                }
            },
            "required": ["task_name", "message"]
        })
    }

    fn control_flow() -> ToolControlFlow {
        ToolControlFlow::Continue
    }

    fn format_output(&self, output: Self::Output) -> String {
        serde_json::to_string(&output).unwrap_or_default()
    }

    async fn call_typed(&self, args: Self::Args, _ctx: &ToolContext) -> AgentResult<Self::Output> {
        let system_prompt = args
            .system_prompt
            .or(args
                .agent_type
                .map(|t| format!("You are a {} specialist.", t)))
            .unwrap_or_else(|| args.message.clone());

        let depth = 1;
        let tool_count = 0;

        match self
            .runtime
            .spawn_child(&args.task_name, system_prompt, depth, tool_count)
            .await
        {
            Ok(agent_path) => {
                let _ = self
                    .runtime
                    .send_task(&agent_path, args.message.clone(), true);
                Ok(SpawnAgentOutput {
                    agent_path,
                    message: "Agent spawned successfully".to_string(),
                })
            }
            Err(e) => Ok(SpawnAgentOutput {
                agent_path: String::new(),
                message: format!("Failed to spawn agent: {}", e),
            }),
        }
    }
}