Tool Trait

The Tool trait is the interface every tool implements. It defines a name, description, JSON Schema for parameters, risk classification, and an async execute method.

Definition

#![allow(unused)]
fn main() {
#[async_trait]
pub trait Tool: Send + Sync {
    /// The tool's registered name (used by the model to invoke it).
    fn name(&self) -> ToolName;

    /// Human-readable description shown to the model.
    fn description(&self) -> &str;

    /// JSON Schema object describing this tool's parameters.
    fn parameters_schema(&self) -> serde_json::Value;

    /// Risk classification, used by the approval policy.
    fn risk(&self) -> ToolRisk;

    /// Execute the tool. Check `cancel.is_cancelled()` at I/O boundaries.
    async fn execute(
        &self,
        arguments: serde_json::Value,
        cancel: CancellationToken,
    ) -> Result<ToolOutcome>;
}
}

Risk levels

Every tool has a ToolRisk that determines whether it needs human approval:

RiskMeaningApprovalExamples
ReadCannot modify stateAuto-approvedReadFile, ListDir, CargoCheck
WriteMay create or modify filesRequires approvalWriteFile, EditFile, CargoFix
DestructiveMay cause irreversible effectsRequires approvalRunCommand
NetworkMakes network requestsRequires approvalExtension tools that call fetch()

The approval policy can override these defaults per-tool via config.

Tool result

execute returns ToolOutcome::Immediate(ToolResult) containing:

  • output: String — the text result (sent to the model and session)
  • is_error: bool — whether the tool reported an error (e.g., non-zero exit code)
  • details: ToolResultDetails — structured data not sent to the model

The details field carries richer information for tools and extensions:

VariantPurpose
NoneNo structured detail (default)
FullOutputPreserves un-truncated output when the model-facing text is truncated
DiagnosticsStructured compiler diagnostics from CargoCheck / CargoClippy

Tool registry

ToolRegistry maps tool names to Box<dyn Tool> implementations:

#![allow(unused)]
fn main() {
let mut registry = ToolRegistry::new();
registry.register(Box::new(ReadFile { root: root.clone() }));
registry.register(Box::new(WriteFile { root: root.clone() }));

// Execute a model-issued tool call
let result = registry.execute(&call, cancel).await?;
}

Duplicate tool names panic at registration (fast-fail, programming error). Execution returns RhoError::ToolNotFound if the model requests an unregistered tool.

Built-in tools

rho ships with 14 built-in tools, all registered in rho-tools::register_all() (plus memory, registered only when [memory] enabled = true):

ToolRiskDescription
read_fileReadRead file contents, wrapped in <context> framing
batch_readReadRead multiple files at once (up to 20 paths)
write_fileWriteCreate or overwrite files within the sandbox
list_dirRead.gitignore-aware directory listing
edit_fileWriteHashline-anchor replacements with node-splitting validation
run_commandDestructiveExecute shell commands with denylist enforcement
cargo_checkReadStructured compiler diagnostics
cargo_clippyReadStructured lint diagnostics
cargo_testReadStructured test pass/fail results
cargo_fixWriteApply machine-applicable compiler/clippy suggestions
rustc_explainReadLook up detailed explanations for error codes
rustdoc_lookupReadLook up Rust standard library documentation (local rustdoc)
crates_io_lookupReadSearch and inspect crate metadata on crates.io
session_summaryReadCompressed numbered history of user requests and outcomes
memoryReadStore, search, and manage project-local knowledge (via rho-memory)

Extension tools

TypeScript extensions can register additional tools via rho-ext. Each extension tool is wrapped in a DenoTool that implements the same Tool trait. Extension tools appear alongside built-in tools in the ToolRegistry and are visible to the model via the tool schema.

Extension tools are discovered at startup from ~/.rho/extensions/ and .rho/extensions/. They can be hot-reloaded at runtime via the reloadExtensions RPC method.

See Extensions for details.