Skip to main content

Overview

Tools enable agents to interact with their environment, perform actions, and access external resources. Shadow Agent SDK comes with a comprehensive set of built-in tools.

Built-in Tools

File System Tools

Read

Read contents of files
agent.add_tool(ReadFile::new());

Write

Create or modify files
agent.add_tool(WriteFile::new());

Glob

Search files with patterns
agent.add_tool(Glob::new());

Grep

Search file contents
agent.add_tool(Grep::new());

System Tools

Bash

Execute shell commands
agent.add_tool(BashExecutor::new());

Environment

Access environment variables
agent.add_tool(EnvReader::new());

How Tools Work

When an agent receives a task, it:
  1. Analyzes the request to understand what needs to be done
  2. Selects the appropriate tool(s) from its available toolset
  3. Executes the tool with the necessary parameters
  4. Processes the tool output
  5. Responds to the user with results
Tool execution flow

Creating Custom Tools

Build your own tools by implementing the Tool trait:
use shadow_agent_sdk::{Tool, ToolResult};
use async_trait::async_trait;
use serde_json::Value;

pub struct CustomTool;

#[async_trait]
impl Tool for CustomTool {
    fn name(&self) -> &str {
        "custom_tool"
    }

    fn description(&self) -> &str {
        "Does something custom"
    }

    fn parameters(&self) -> Value {
        serde_json::json!({
            "type": "object",
            "properties": {
                "input": {
                    "type": "string",
                    "description": "Input parameter"
                }
            },
            "required": ["input"]
        })
    }

    async fn execute(&self, params: Value) -> ToolResult {
        let input = params["input"].as_str().unwrap();

        // Your custom logic here
        let result = format!("Processed: {}", input);

        Ok(result)
    }
}
Register your custom tool:
agent.add_tool(CustomTool);

Tool Security

Tools like BashExecutor can execute arbitrary commands. Only enable them when necessary and consider the security implications.

Best Practices

Only enable tools that are absolutely necessary for the agent’s tasks:
// Good: Only read access
agent.add_tool(ReadFile::new());

// Be careful: Full write access
agent.add_tool(WriteFile::new());
Add validation in custom tools:
async fn execute(&self, params: Value) -> ToolResult {
    let path = params["path"].as_str()
        .ok_or("Invalid path parameter")?;

    // Validate path is within allowed directory
    if !path.starts_with("/safe/directory/") {
        return Err("Path not allowed".into());
    }

    // Continue with tool execution
}
Log all tool executions for audit trails:
agent.on_tool_use(|tool_name, params| {
    tracing::info!(
        tool = tool_name,
        params = ?params,
        "Tool executed"
    );
});

Tool Examples

Example: File Analysis

use shadow_agent_sdk::{Agent, tools::*};

#[tokio::main]
async fn main() -> Result<()> {
    let mut agent = Agent::new("api-key").await?;

    // Add file tools
    agent.add_tool(ReadFile::new());
    agent.add_tool(Glob::new());

    // Ask agent to analyze project structure
    let response = agent.execute(
        "List all Rust files in the src directory and \
         summarize the main modules"
    ).await?;

    println!("{}", response);
    Ok(())
}

Example: Code Execution

let mut agent = Agent::new("api-key").await?;
agent.add_tool(BashExecutor::new());

let response = agent.execute(
    "Run the test suite and report any failures"
).await?;

Next Steps