Tool Trait
The Tool trait is the primary extension point for giving agents capabilities.
Tools are stateless functions that receive JSON arguments and a read-only context,
and return a ToolOutput.
Trait definition
#[async_trait]
pub trait Tool: Send + Sync {
/// Return the descriptor for this tool.
fn descriptor(&self) -> ToolDescriptor;
/// Validate arguments before execution. Default: accept all.
fn validate_args(&self, _args: &Value) -> Result<(), ToolError> {
Ok(())
}
/// Execute the tool with the given arguments and context.
async fn execute(
&self,
args: Value,
ctx: &ToolCallContext,
) -> Result<ToolOutput, ToolError>;
}
Crate path: awaken::contract::tool::Tool
ToolDescriptor
Describes a tool’s identity and parameter schema. Registered with the runtime and sent to the LLM as available functions.
pub struct ToolDescriptor {
pub id: String,
pub name: String,
pub description: String,
/// JSON Schema for parameters.
pub parameters: Value,
pub category: Option<String>,
}
Builder methods:
ToolDescriptor::new(id, name, description) -> Self
.with_parameters(schema: Value) -> Self
.with_category(category: impl Into<String>) -> Self
ToolResult
Returned by Tool::execute. Carries the execution outcome back to the agent loop.
pub struct ToolResult {
pub tool_name: String,
pub status: ToolStatus,
pub data: Value,
pub message: Option<String>,
pub suspension: Option<Box<SuspendTicket>>,
}
ToolStatus
pub enum ToolStatus {
Success, // Execution succeeded
Pending, // Suspended, waiting for external resume
Error, // Execution failed; content sent back to LLM
}
Constructors
| Method | Status | Use case |
|---|---|---|
ToolResult::success(name, data) | Success | Normal completion |
ToolResult::success_with_message(name, data, msg) | Success | Completion with description |
ToolResult::error(name, message) | Error | Recoverable failure |
ToolResult::error_with_code(name, code, message) | Error | Structured error with code |
ToolResult::suspended(name, message) | Pending | HITL suspension |
ToolResult::suspended_with(name, message, ticket) | Pending | Suspension with ticket |
Predicates
is_success() -> boolis_pending() -> boolis_error() -> boolto_json() -> Value
ToolError
Errors returned from validate_args or execute. Unlike ToolResult::Error
(which is sent to the LLM), a ToolError aborts the tool call.
pub enum ToolError {
InvalidArguments(String),
ExecutionFailed(String),
Denied(String),
NotFound(String),
Internal(String),
}
ToolCallContext
Read-only context provided to a tool during execution.
pub struct ToolCallContext {
pub call_id: String,
pub tool_name: String,
pub run_identity: RunIdentity,
pub agent_spec: Arc<AgentSpec>,
pub snapshot: Snapshot,
pub activity_sink: Option<Arc<dyn EventSink>>,
}
Methods
/// Read a typed state key from the snapshot.
fn state<K: StateKey>(&self) -> Option<&K::Value>
/// Report an activity snapshot for this tool call.
async fn report_activity(&self, activity_type: &str, content: &str)
/// Report an incremental activity delta.
async fn report_activity_delta(&self, activity_type: &str, patch: Value)
/// Report structured tool call progress.
async fn report_progress(
&self,
status: ProgressStatus,
message: Option<&str>,
progress: Option<f64>,
)
Examples
Minimal tool
use async_trait::async_trait;
use awaken::contract::tool::{Tool, ToolCallContext, ToolDescriptor, ToolError, ToolResult, ToolOutput};
use serde_json::{Value, json};
struct Greet;
#[async_trait]
impl Tool for Greet {
fn descriptor(&self) -> ToolDescriptor {
ToolDescriptor::new("greet", "greet", "Greet a user by name")
.with_parameters(json!({
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": ["name"]
}))
}
async fn execute(
&self,
args: Value,
_ctx: &ToolCallContext,
) -> Result<ToolOutput, ToolError> {
let name = args["name"]
.as_str()
.ok_or_else(|| ToolError::InvalidArguments("name required".into()))?;
Ok(ToolResult::success("greet", json!({ "greeting": format!("Hello, {name}!") })).into())
}
}
Reading state from context
use async_trait::async_trait;
use awaken::contract::tool::{Tool, ToolCallContext, ToolDescriptor, ToolError, ToolResult, ToolOutput};
use awaken::state::StateKey;
use serde::{Serialize, Deserialize};
use serde_json::{Value, json};
// Assume a state key is defined elsewhere:
// struct UserPreferences;
// impl StateKey for UserPreferences { ... }
struct GetPreferences;
#[async_trait]
impl Tool for GetPreferences {
fn descriptor(&self) -> ToolDescriptor {
ToolDescriptor::new("get_prefs", "get_preferences", "Get user preferences")
}
async fn execute(
&self,
_args: Value,
ctx: &ToolCallContext,
) -> Result<ToolOutput, ToolError> {
// Read typed state through the snapshot
// let prefs = ctx.state::<UserPreferences>().cloned().unwrap_or_default();
// Ok(ToolResult::success("get_prefs", serde_json::to_value(&prefs).unwrap()).into())
Ok(ToolResult::success("get_prefs", json!({})).into())
}
}
Tool execution hooks
Every tool call passes through plugin hooks before and after execution. This allows plugins to intercept, observe, or modify tool call behavior without changing the tool itself.
Full lifecycle
LLM selects tool
-> validate_args()
-> BeforeToolExecute phase (plugins run hooks)
Plugins may schedule ToolInterceptAction:
Block -> run terminates with reason
Suspend -> run pauses (HITL), tool not executed
SetResult -> tool skipped, pre-built result used
-> execute() (only if not intercepted)
-> AfterToolExecute phase (plugins run hooks)
Plugins observe the ToolResult and may modify state
BeforeToolExecute
Runs once per tool call, after argument validation. Plugins receive a
PhaseContext containing the tool name, call ID, and validated arguments.
Hooks return a StateCommand that may schedule ToolInterceptAction to
block, suspend, or short-circuit the call.
When multiple intercepts are scheduled, priority is: Block > Suspend > SetResult.
AfterToolExecute
Runs after execute() completes (or after an intercept produces a result).
Plugins receive the PhaseContext with the ToolResult attached. Hooks can
update state, emit events, or schedule actions for subsequent phases.
ToolCallStatus transitions
Each tool call tracks a ToolCallStatus through its lifecycle:
New -> Running -> Succeeded
Failed
Suspended -> Resuming -> Running -> ...
Cancelled
Terminal states (Succeeded, Failed, Cancelled) cannot transition further.
See Plugin Internals for intercept priority details and the full phase convergence loop.