Invoking Tools

Tools are invoked via agent.invoke_tool(name, input). The input and output are JSON Value objects.

Basic Invocation

#![allow(unused)]
fn main() {
let output = agent.invoke_tool("echo", json!({"message": "hello"})).await?;
println!("{}", output["message"]);  // "hello"
}

Error Handling

#![allow(unused)]
fn main() {
use libagent::agent::AgentError;

match agent.invoke_tool("web.fetch", json!({"url": "https://example.com"})).await {
    Ok(output) => {
        let status = output["status"].as_i64().unwrap_or(0);
        let body = output["body"].as_str().unwrap_or("");
        println!("HTTP {}: {} bytes", status, body.len());
    }
    Err(AgentError::ToolFailed(msg)) if msg.contains("access denied") => {
        eprintln!("Missing capability for web.fetch");
    }
    Err(AgentError::ToolFailed(msg)) if msg.contains("requires approval") => {
        eprintln!("Waiting for human approval...");
        // The call blocks until approved - no special handling needed
    }
    Err(e) => eprintln!("Tool error: {}", e),
}
}

Tool Input/Output Schemas

Each tool declares input and output JSON schemas. View them with:

ash tools schema lm.complete
ash tools schema fs.write

Using Secrets in Tool Calls

Reference secrets with handle syntax in JSON strings:

#![allow(unused)]
fn main() {
let output = agent.invoke_tool("web.fetch", json!({
    "url": "https://api.openai.com/v1/models",
    "headers": {
        "Authorization": "Bearer {{secret:openai-key}}"
    }
})).await?;
}

The daemon substitutes the secret value before calling the tool handler.

Tool Invocation in the Act Phase

Tool calls should be made in the Act lifecycle state. While the daemon does not strictly enforce this in all cases, Act is the intended state for tool execution:

#![allow(unused)]
fn main() {
agent.transition(LifecycleState::Act).await?;
let result = agent.invoke_tool("lm.complete", json!({/* ... */})).await?;
agent.transition(LifecycleState::Observe).await?;
}

Available Built-in Tools

ToolCapability RequiredDescription
echotool.invoke:echoReturns input as-is
lm.completetool.invoke:lm.completeLLM completion via OpenRouter
lm.embedtool.invoke:lm.embedDense vector embedding
fs.readtool.invoke:fs.readRead a file
fs.writetool.invoke:fs.writeWrite or append to a file
fs.listtool.invoke:fs.listList directory contents
fs.deletetool.invoke:fs.deleteDelete a file
web.fetchtool.invoke:web.fetchFetch a URL
web.searchtool.invoke:web.searchWeb search via DuckDuckGo
sandbox.execsandbox.execExecute code in a sandbox
agent.infotool.invoke:agent.infoGet agent metadata
sensitive-optool.invoke:sensitive-opHuman-approval-required operation

Direct IPC (Advanced)

The Agent SDK wraps AgentdClient. For advanced use cases (custom IPC requests), access the client directly:

#![allow(unused)]
fn main() {
use libagent::ipc::Request;

let response = agent.client.send(Request::ListAgents).await?;
}