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.
Custom Tool
Build a custom tool to extend the agent’s capabilities.
Overview
Tools implement the Tool trait from src/tools/traits.rs. Every tool defines a name, description, parameter schema, and execution logic.
Create src/tools/my_tool.rs:
use crate::tools::traits::{Tool, ToolResult};
use anyhow::Result;
use async_trait::async_trait;
use serde_json::{json, Value};
pub struct MyTool {
// Dependencies (security, memory, runtime, etc.)
}
impl MyTool {
pub fn new() -> Self {
Self {}
}
}
#[async_trait]
impl Tool for MyTool {
fn name(&self) -> &str {
"my_tool"
}
fn description(&self) -> &str {
"Does something useful with the given input"
}
fn parameters_schema(&self) -> Value {
json!({
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "The input to process"
},
"mode": {
"type": "string",
"enum": ["fast", "thorough"],
"description": "Processing mode",
"default": "fast"
}
},
"required": ["input"]
})
}
async fn execute(&self, args: Value) -> Result<ToolResult> {
// 1. Validate inputs
let input = args["input"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("Missing 'input' parameter"))?;
let mode = args["mode"]
.as_str()
.unwrap_or("fast");
// 2. Execute logic
match do_something(input, mode).await {
Ok(result) => Ok(ToolResult {
success: true,
output: result,
error: None,
}),
Err(e) => Ok(ToolResult {
success: false,
output: String::new(),
error: Some(e.to_string()),
}),
}
}
}
async fn do_something(input: &str, mode: &str) -> Result<String> {
// Your tool logic here
Ok(format!("Processed '{}' in {} mode", input, mode))
}
Add to src/tools/mod.rs:
pub mod my_tool;
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::new(shell::ShellTool::new(security.clone(), runtime.clone())),
Arc::new(file_read::FileReadTool::new(security.clone())),
// ... existing tools
Arc::new(my_tool::MyTool::new()), // Add here
];
tools
}
Step 3: Test
Add tests in src/tools/my_tool.rs:
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_my_tool_success() {
let tool = MyTool::new();
let result = tool.execute(json!({
"input": "test data",
"mode": "fast"
})).await.unwrap();
assert!(result.success);
assert!(result.output.contains("Processed"));
}
#[tokio::test]
async fn test_my_tool_missing_param() {
let tool = MyTool::new();
let result = tool.execute(json!({})).await;
assert!(result.is_err());
}
}
Run tests:
cargo test tools::my_tool
Step 4: Use
The agent automatically discovers the tool:
corvus agent -m "Use my_tool to process this text"
Security Integration
For tools that need security checks:
use crate::security::SecurityPolicy;
use std::sync::Arc;
pub struct MyTool {
security: Arc<SecurityPolicy>,
}
impl MyTool {
pub fn new(security: Arc<SecurityPolicy>) -> Self {
Self { security }
}
}
#[async_trait]
impl Tool for MyTool {
async fn execute(&self, args: Value) -> Result<ToolResult> {
// Rate limit check
if self.security.is_rate_limited() {
return Ok(ToolResult {
success: false,
error: Some("Rate limit exceeded".into()),
..Default::default()
});
}
// Record action
if !self.security.record_action() {
return Ok(ToolResult {
success: false,
error: Some("Action budget exhausted".into()),
..Default::default()
});
}
// Execute...
}
}
Best Practices
1. Descriptive Schemas
Provide detailed descriptions in the JSON schema:
json!({
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "Full URL to fetch (must be https://)",
"pattern": "^https://"
}
},
"required": ["url"]
})
2. Error Handling
Return errors via ToolResult, not Result::Err:
match operation().await {
Ok(output) => Ok(ToolResult {
success: true,
output,
error: None,
}),
Err(e) => Ok(ToolResult {
success: false,
output: String::new(),
error: Some(e.to_string()), // Structured error
}),
}
3. Structured Output
For parseable data, return JSON strings:
let data = serde_json::json!({
"status": "success",
"count": 42,
"items": ["a", "b", "c"]
});
Ok(ToolResult {
success: true,
output: data.to_string(),
error: None,
})
Full Example
See examples/custom_tool.rs:27-71 for a complete HTTP GET tool.