Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/dallay/corvus/llms.txt

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

Tool Trait

The Tool trait defines the interface for all agent capabilities in Corvus. Every tool (shell, file operations, memory, etc.) implements this trait. Source: src/tools/traits.rs:34-57

Trait Definition

use async_trait::async_trait;
use serde_json::Value;

#[async_trait]
pub trait Tool: Send + Sync {
    /// Tool name (used in LLM function calling)
    fn name(&self) -> &str;
    
    /// Human-readable description
    fn description(&self) -> &str;
    
    /// JSON schema for parameters
    fn parameters_schema(&self) -> Value;
    
    /// Execute the tool with given arguments
    async fn execute(&self, args: Value) -> anyhow::Result<ToolResult>;
    
    /// Get the full spec for LLM registration
    fn spec(&self) -> ToolSpec {
        ToolSpec {
            name: self.name().to_string(),
            description: self.description().to_string(),
            parameters: self.parameters_schema(),
            source: None,
        }
    }
}

Core Types

ToolResult

From src/tools/traits.rs:5-10:
pub struct ToolResult {
    pub success: bool,
    pub output: String,
    pub error: Option<String>,
}

ToolSpec

From src/tools/traits.rs:13-20:
pub struct ToolSpec {
    pub name: String,
    pub description: String,
    pub parameters: Value,  // JSON Schema
    pub source: Option<ToolSourceMetadata>,
}

ToolSourceMetadata

For MCP/Composio tools:
pub struct ToolSourceMetadata {
    pub kind: String,           // "mcp", "composio", "builtin"
    pub provider: Option<String>,
    pub server: Option<String>,
    pub original_name: Option<String>,
}

Implementing a Tool

Minimal example from examples/custom_tool.rs:27-71:
use async_trait::async_trait;
use serde_json::{json, Value};

pub struct HttpGetTool;

#[async_trait]
impl Tool for HttpGetTool {
    fn name(&self) -> &str {
        "http_get"
    }
    
    fn description(&self) -> &str {
        "Fetch a URL and return the HTTP status code and content length"
    }
    
    fn parameters_schema(&self) -> Value {
        json!({
            "type": "object",
            "properties": {
                "url": { 
                    "type": "string", 
                    "description": "URL to fetch" 
                }
            },
            "required": ["url"]
        })
    }
    
    async fn execute(&self, args: Value) -> anyhow::Result<ToolResult> {
        let url = args["url"]
            .as_str()
            .ok_or_else(|| anyhow::anyhow!("Missing 'url' parameter"))?;
        
        match reqwest::get(url).await {
            Ok(resp) => {
                let status = resp.status().as_u16();
                let len = resp.content_length().unwrap_or(0);
                Ok(ToolResult {
                    success: status < 400,
                    output: format!("HTTP {} — {} bytes", status, len),
                    error: None,
                })
            }
            Err(e) => Ok(ToolResult {
                success: false,
                output: String::new(),
                error: Some(format!("Request failed: {}", e)),
            }),
        }
    }
}

Parameter Schema (JSON Schema)

All tools use JSON Schema for parameter validation:
fn parameters_schema(&self) -> Value {
    json!({
        "type": "object",
        "properties": {
            "path": {
                "type": "string",
                "description": "Relative path to the file"
            },
            "approved": {
                "type": "boolean",
                "description": "Explicit approval for risky operations",
                "default": false
            }
        },
        "required": ["path"]
    })
}

Built-in Tools

From src/tools/mod.rs:
ToolNameDescription
ShellToolshellExecute shell commands
FileReadToolfile_readRead file contents
FileWriteToolfile_writeWrite file contents
MemoryStoreToolmemory_storeSave memories
MemoryRecallToolmemory_recallSearch memories
MemoryForgetToolmemory_forgetDelete memories
BrowserOpenToolbrowser_openOpen URLs (opt-in)
ComposioToolcomposio_*1000+ OAuth apps (opt-in)

Security Integration

Tools integrate with SecurityPolicy:
pub struct FileReadTool {
    security: Arc<SecurityPolicy>,
}

#[async_trait]
impl Tool for FileReadTool {
    async fn execute(&self, args: Value) -> anyhow::Result<ToolResult> {
        let path = args["path"].as_str()?;
        
        // 1. Rate limit check
        if self.security.is_rate_limited() {
            return Ok(ToolResult {
                success: false,
                error: Some("Rate limit exceeded".into()),
                ..Default::default()
            });
        }
        
        // 2. Path validation
        if !self.security.is_path_allowed(path) {
            return Ok(ToolResult {
                success: false,
                error: Some(format!("Path not allowed: {}", path)),
                ..Default::default()
            });
        }
        
        // 3. Record action (consumes rate limit budget)
        if !self.security.record_action() {
            return Ok(ToolResult {
                success: false,
                error: Some("Action budget exhausted".into()),
                ..Default::default()
            });
        }
        
        // 4. Execute
        // ...
    }
}

Tool Registration

Register your tool in src/tools/mod.rs:
pub fn default_tools(
    security: Arc<SecurityPolicy>,
    memory: Arc<dyn Memory>,
    runtime: Arc<dyn RuntimeAdapter>,
    config: &Config,
) -> Vec<Arc<dyn Tool>> {
    let mut tools: Vec<Arc<dyn Tool>> = vec![
        Arc::new(shell::ShellTool::new(security.clone(), runtime.clone())),
        Arc::new(file_read::FileReadTool::new(security.clone())),
        Arc::new(file_write::FileWriteTool::new(security.clone())),
        Arc::new(memory_store::MemoryStoreTool::new(memory.clone())),
        Arc::new(memory_recall::MemoryRecallTool::new(memory.clone())),
        Arc::new(memory_forget::MemoryForgetTool::new(memory.clone())),
    ];
    
    // Optional tools
    if config.browser.enabled {
        tools.push(Arc::new(browser_open::BrowserOpenTool::new(config)));
    }
    
    tools
}

Error Handling

Return Errors as ToolResult

async fn execute(&self, args: Value) -> anyhow::Result<ToolResult> {
    // Validation errors: return Result::Err
    let param = args["param"]
        .as_str()
        .ok_or_else(|| anyhow::anyhow!("Missing 'param'"))?;
    
    // Execution errors: return ToolResult with error field
    match do_something(param).await {
        Ok(output) => Ok(ToolResult {
            success: true,
            output,
            error: None,
        }),
        Err(e) => Ok(ToolResult {
            success: false,
            output: String::new(),
            error: Some(e.to_string()),
        }),
    }
}

Never Panic in Tools

Never use .unwrap(), .expect(), or panic!() in tool execution paths. Return errors via ToolResult or Result::Err.

LLM Integration

Tools are exposed to the LLM via:

1. Native Function Calling (OpenAI, Anthropic, Gemini)

{
  "type": "function",
  "function": {
    "name": "file_read",
    "description": "Read the contents of a file in the workspace",
    "parameters": {
      "type": "object",
      "properties": {
        "path": {
          "type": "string",
          "description": "Relative path to the file"
        }
      },
      "required": ["path"]
    }
  }
}

2. Prompt-Guided (Ollama, local models)

## Available Tools

**file_read**: Read the contents of a file in the workspace
Parameters: `{"type":"object","properties":{"path":{"type":"string"}}}`

To use a tool, wrap JSON in <tool_call></tool_call> tags:

<tool_call>
{"name": "file_read", "arguments": {"path": "README.md"}}
</tool_call>

Testing Tools

From src/tools/shell.rs:206-216:
#[tokio::test]
async fn shell_executes_allowed_command() {
    let tool = ShellTool::new(
        test_security(AutonomyLevel::Supervised),
        test_runtime(),
    );
    
    let result = tool
        .execute(json!({"command": "echo hello"}))
        .await
        .unwrap();
    
    assert!(result.success);
    assert!(result.output.trim().contains("hello"));
    assert!(result.error.is_none());
}

Best Practices

Use descriptive names without prefixes: file_read, not tool_file_read
Provide detailed descriptions with examples in the JSON schema
Return structured output in ToolResult.output (e.g., JSON strings for parseable data)
Always validate inputs before executing. Don’t trust LLM-provided parameters.

Build docs developers (and LLMs) love