Tools implement the Tool trait. Each tool defines its input schema, permission behavior, and execution logic.

The Tool trait

#![allow(unused)]
fn main() {
#[async_trait]
pub trait Tool: Send + Sync {
    /// Unique name used in API tool_use blocks.
    fn name(&self) -> &'static str;

    /// Description sent to the LLM.
    fn description(&self) -> &'static str;

    /// JSON Schema for input parameters.
    fn input_schema(&self) -> serde_json::Value;

    /// Execute the tool.
    async fn call(
        &self,
        input: serde_json::Value,
        ctx: &ToolContext,
    ) -> Result<ToolResult, ToolError>;

    /// Whether this tool only reads (no mutations).
    fn is_read_only(&self) -> bool { false }

    /// Whether it's safe to run in parallel with other tools.
    fn is_concurrency_safe(&self) -> bool { self.is_read_only() }
}
}

Example: a simple tool

#![allow(unused)]
fn main() {
pub struct TimeTool;

#[async_trait]
impl Tool for TimeTool {
    fn name(&self) -> &'static str { "Time" }

    fn description(&self) -> &'static str {
        "Returns the current date and time."
    }

    fn input_schema(&self) -> serde_json::Value {
        json!({
            "type": "object",
            "properties": {}
        })
    }

    fn is_read_only(&self) -> bool { true }

    async fn call(
        &self,
        _input: serde_json::Value,
        _ctx: &ToolContext,
    ) -> Result<ToolResult, ToolError> {
        let now = chrono::Utc::now().to_rfc3339();
        Ok(ToolResult::success(now))
    }
}
}

Registering the tool

In src/tools/registry.rs:

#![allow(unused)]
fn main() {
pub fn default_tools() -> Self {
    let mut registry = Self::new();
    // ... existing tools ...
    registry.register(Arc::new(TimeTool));
    registry
}
}

ToolContext

Every tool receives a ToolContext with:

FieldTypeDescription
cwdPathBufCurrent working directory
cancelCancellationTokenCheck for Ctrl+C
permission_checkerArc<PermissionChecker>Check permissions
verboseboolVerbose output mode
plan_modeboolRead-only mode active
file_cacheOption<Arc<Mutex<FileCache>>>Shared file cache
denial_trackerOption<Arc<Mutex<DenialTracker>>>Permission denial log

ToolResult

#![allow(unused)]
fn main() {
// Success
Ok(ToolResult::success("output text"))

// Error (sent back to LLM as an error result)
Ok(ToolResult::error("what went wrong"))

// Fatal error (stops the tool, not sent to LLM)
Err(ToolError::ExecutionFailed("crash details".into()))
}