Add a Tool
Use this when you need to expose a custom capability to the agent by implementing the Tool trait.
Prerequisites
Section titled “Prerequisites”awakencrate added toCargo.tomlasync-traitandserde_jsonavailable
- Implement the
Tooltrait.
use async_trait::async_trait;use serde_json::{Value, json};use awaken::contract::tool::{Tool, ToolCallContext, ToolDescriptor, ToolError, ToolResult, ToolOutput};
async fn fetch_weather(_city: &str) -> Result<String, ToolError> { Ok("Sunny, 22°C".to_string())}
pub struct WeatherTool;
#[async_trait]impl Tool for WeatherTool { fn descriptor(&self) -> ToolDescriptor { ToolDescriptor::new("get_weather", "Get Weather", "Fetch current weather for a city") .with_parameters(json!({ "type": "object", "properties": { "city": { "type": "string", "description": "City name" } }, "required": ["city"] })) }
async fn execute(&self, args: Value, _ctx: &ToolCallContext) -> Result<ToolOutput, ToolError> { let city = args["city"] .as_str() .ok_or_else(|| ToolError::InvalidArguments("Missing 'city'".into()))?;
let weather = fetch_weather(city).await?;
Ok(ToolResult::success("get_weather", json!({ "forecast": weather })).into()) }}- Optionally override argument validation.
use async_trait::async_trait;use serde_json::{Value, json};use awaken::contract::tool::{Tool, ToolCallContext, ToolDescriptor, ToolError, ToolOutput, ToolResult};
pub struct WeatherTool;
#[async_trait]impl Tool for WeatherTool { fn descriptor(&self) -> ToolDescriptor { ToolDescriptor::new("get_weather", "Get Weather", "Fetch current weather for a city") .with_parameters(json!({ "type": "object", "properties": { "city": { "type": "string", "description": "City name" } }, "required": ["city"] })) }
fn validate_args(&self, args: &Value) -> Result<(), ToolError> { if !args.get("city").and_then(|v| v.as_str()).is_some_and(|s| !s.is_empty()) { return Err(ToolError::InvalidArguments("'city' must be a non-empty string".into())); } Ok(()) }
async fn execute(&self, _args: Value, _ctx: &ToolCallContext) -> Result<ToolOutput, ToolError> { Ok(ToolResult::success("get_weather", json!({})).into()) }}validate_args runs before execute and lets you reject malformed input early.
- Register the tool with the builder.
use std::sync::Arc;use async_trait::async_trait;use serde_json::{Value, json};use awaken::engine::GenaiExecutor;use awaken::registry_spec::ModelSpec;use awaken::{AgentRuntimeBuilder, AgentSpec};use awaken::contract::tool::{Tool, ToolCallContext, ToolDescriptor, ToolError, ToolOutput, ToolResult};
pub struct WeatherTool;
#[async_trait]impl Tool for WeatherTool { fn descriptor(&self) -> ToolDescriptor { ToolDescriptor::new("get_weather", "Get Weather", "Fetch current weather for a city") .with_parameters(json!({"type": "object", "properties": {}})) }
async fn execute(&self, _args: Value, _ctx: &ToolCallContext) -> Result<ToolOutput, ToolError> { Ok(ToolResult::success("get_weather", json!({})).into()) }}
fn main() -> Result<(), Box<dyn std::error::Error>> { let spec = AgentSpec::new("assistant").with_model_id("claude-sonnet");
let runtime = AgentRuntimeBuilder::new() .with_tool("get_weather", Arc::new(WeatherTool)) .with_agent_spec(spec) .with_provider("anthropic", Arc::new(GenaiExecutor::new())) .with_model(ModelSpec::new("claude-sonnet", "anthropic", "claude-sonnet-4-20250514")) .build()?;
let _runtime = runtime; Ok(())}The string ID passed to with_tool must match the id in ToolDescriptor::new.
-
Register via a plugin (alternative).
Tools can also be registered inside a
Plugin::registermethod through thePluginRegistrar:
use std::sync::Arc;use async_trait::async_trait;use serde_json::{Value, json};use awaken::{Plugin, PluginDescriptor, PluginRegistrar, StateError};use awaken::contract::tool::{Tool, ToolCallContext, ToolDescriptor, ToolError, ToolOutput, ToolResult};
pub struct WeatherTool;
#[async_trait]impl Tool for WeatherTool { fn descriptor(&self) -> ToolDescriptor { ToolDescriptor::new("get_weather", "Get Weather", "Fetch current weather for a city") .with_parameters(json!({"type": "object", "properties": {}})) }
async fn execute(&self, _args: Value, _ctx: &ToolCallContext) -> Result<ToolOutput, ToolError> { Ok(ToolResult::success("get_weather", json!({})).into()) }}
pub struct WeatherPlugin;
impl Plugin for WeatherPlugin { fn descriptor(&self) -> PluginDescriptor { PluginDescriptor { name: "weather" } }
fn register(&self, registrar: &mut PluginRegistrar) -> Result<(), StateError> { registrar.register_tool("get_weather", Arc::new(WeatherTool))?; Ok(()) }}Plugin-registered tools are scoped to agents that activate that plugin.
Tool availability is config, not code
Section titled “Tool availability is config, not code”with_tool puts the tool in the runtime registry — every running agent can potentially call it. Which tools a given agent is allowed to call is config, not Rust:
curl -sS -X PUT http://localhost:3000/v1/config/agents/assistant \ -H 'content-type: application/json' \ -d '{ "id": "assistant", "model_id": "gpt-4o-mini", "system_prompt": "You help with weather questions.", "allowed_tools": ["get_weather"], "excluded_tools": [] }'allowed_tools whitelists; excluded_tools blacklists. Both apply on the next run — no rebuild, no restart. Add a tool in code once; gate it per agent via config.
For finer per-call control (allow/deny/ask on argument shape, not just tool name), use the Permission plugin.
Verify
Section titled “Verify”Send a message that should trigger the tool. Inspect the run result to confirm the tool was called and returned the expected output.
Common Errors
Section titled “Common Errors”| Error | Cause | Fix |
|---|---|---|
ToolError::InvalidArguments | The LLM passed malformed JSON | Tighten the JSON Schema in with_parameters to guide the model |
| Tool never called | Descriptor id does not match the registered ID | Ensure the ID in ToolDescriptor::new and with_tool are identical |
ToolError::ExecutionFailed | Runtime error inside execute | Return a descriptive error; the agent will see it and may retry |
Related Example
Section titled “Related Example”examples/src/research/tools.rs — SearchTool and WriteReportTool implementations.
Key Files
Section titled “Key Files”crates/awaken-runtime-contract/src/contract/tool.rs—Tooltrait,ToolDescriptor,ToolResult,ToolErrorcrates/awaken-runtime/src/builder.rs—with_toolregistration