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
Section titled “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
Section titled “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>) -> SelfToolResult
Section titled “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
Section titled “ToolStatus”pub enum ToolStatus { Success, // Execution succeeded Pending, // Suspended, waiting for external resume Error, // Execution failed; content sent back to LLM}Constructors
Section titled “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
Section titled “Predicates”is_success() -> boolis_pending() -> boolis_error() -> boolto_json() -> Value
ToolError
Section titled “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
Section titled “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>>, /// Optional cancellation token for cooperative cancellation. /// Long-running tools (MCP calls, sub-agent execution) should check /// `is_cancelled()` periodically or use `cancelled()` in `tokio::select!`. pub cancellation_token: Option<CancellationToken>, /// Resume decision input — set when the runtime is replaying a suspended /// tool call (see `ToolCallResumeMode`). pub resume_input: Option<ToolCallResume>, /// Active suspension id, if this execution is a resumed suspension. pub suspension_id: Option<String>, /// Suspension reason/action, if this execution is a resumed suspension. pub suspension_reason: Option<String>,}Methods
Section titled “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
Section titled “Examples”Minimal tool
Section titled “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
Section titled “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
Section titled “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
Section titled “Full lifecycle”LLM selects tool -> validate_args() -> ToolGate phase (pure allow/block/suspend/set-result decision) -> BeforeToolExecute phase (execution-time hooks for allowed calls only) -> execute() (only if ToolGate allows) -> AfterToolExecute phase (plugins run hooks) Plugins observe the ToolResult and may modify stateToolGate
Section titled “ToolGate”Runs after argument validation and before any execution-time hook. Plugins
implement ToolGateHook and return Option<ToolInterceptPayload>:
| Variant | Effect |
|---|---|
Block { reason } | Terminate the call and fail the run with the reason |
Suspend(SuspendTicket) | Pause the run and wait for an external decision |
SetResult(ToolResult) | Skip tool execution and use the provided result |
When multiple gate hooks return a decision, priority is: Block > Suspend > SetResult.
ToolGate is pure and may be re-evaluated after earlier allowed tool calls
commit new state in the same step.
BeforeToolExecute
Section titled “BeforeToolExecute”Runs once per tool call batch after ToolGate has already allowed the call.
Plugins receive a PhaseContext containing the tool name, call ID, and
validated arguments. Hooks return a StateCommand for execution-time work such
as counters, throttling state, or observability setup.
AfterToolExecute
Section titled “AfterToolExecute”Runs after execute() completes (or after ToolGate supplies a result).
Plugins receive the PhaseContext with the ToolResult attached. Hooks can
update state, emit events, or schedule actions for subsequent phases.
ToolCallStatus transitions
Section titled “ToolCallStatus transitions”Each tool call tracks a ToolCallStatus through its lifecycle:
New -> Running -> Succeeded Failed Suspended -> Resuming -> Running -> ... CancelledTerminal states (Succeeded, Failed, Cancelled) cannot transition further.
See Plugin Internals for intercept priority details and the full phase convergence loop.