Per-Tool Policies
Per-tool policies let you attach execution constraints to individual tools
without changing their implementation. They are expressed in the T:: namespace
and composed with the same | operator as the rest of the tool algebra.
Each wrapper takes any tool: in the examples below, search_kb(),
get_weather() and the like are #[tool] functions, and a
T:: composite works the same way.
PolicyTool
Section titled “PolicyTool”Internally, T::timeout, T::cached, and T::confirm all wrap the target
tool in a PolicyTool — a ToolFunction decorator that carries a ToolPolicy
and enforces it on every call.
pub struct ToolPolicy { pub timeout: Option<Duration>, pub cache: bool, pub confirm: bool, pub confirm_message: Option<String>,}Policies compose: wrapping the same tool twice (e.g.,
T::cached(T::timeout(tool, dur))) applies both timeout and cache.
Timeout
Section titled “Timeout”T::timeout(tool, duration) races each call against tokio::time::timeout.
If the tool’s future does not complete within the given duration the call
returns ToolError::Timeout(duration) and the inner future is dropped.
use std::time::Duration;
Live::builder() .tools( T::timeout( search_kb(), Duration::from_secs(10), ) + T::google_search() )Timeout enforcement is fully implemented: tokio::time::timeout wraps the
inner call future, and any elapse produces ToolError::Timeout.
Caching
Section titled “Caching”T::cached(tool) memoizes successful results keyed by (tool name, canonical-JSON args). Repeat calls with identical arguments return the cached
value without re-invoking the tool. Errors are never cached.
Canonical JSON sorts object keys lexicographically, so {"b":2,"a":1} and
{"a":1,"b":2} produce the same cache key.
// Weather results cached for the lifetime of the sessionLive::builder() .tools( T::cached(get_weather()) )Cache entries live for the lifetime of the PolicyTool instance (i.e., the
session). There is no TTL or maximum entry count. If you need time-bounded
caching, combine with a timeout and manage expiry in your tool implementation.
Caching is fully implemented in PolicyTool::call.
Confirmation flag
Section titled “Confirmation flag”T::confirm(tool, message) marks a tool as requiring user confirmation before
it runs. The flag and optional hint message are recorded on the ToolPolicy
and surfaced at runtime via PolicyTool::requires_confirmation().
Live::builder() .tools( T::confirm( send_email(), "This will send an email to the customer. Are you sure?", ) )Enforcing confirmation with a provider
Section titled “Enforcing confirmation with a provider”Confirmation is enforced at dispatch by a ConfirmationProvider. Before
running any tool that reports requires_confirmation(), the ToolDispatcher
consults the provider; a declined call returns ToolError::Declined(reason) —
which the model sees, so it can tell the user why — instead of executing the
tool.
A T::confirm tool with nothing to confirm it is a configuration error, not a
tool that quietly runs unconfirmed: AgentBuilder::build and Live::connect
refuse it, and check_live reports it (LiveViolation::UnconfirmedTools)
before connecting. A bare ToolDispatcher with no provider still runs gated
tools — the checks live where configuration is declared.
Wire one via Live::confirmation_provider, AgentBuilder::confirmation_provider,
or on a dispatcher directly:
use std::sync::Arc;
let handle = Live::builder() .tools(T::confirm(send_email_tool, "Send this email?")) // Any async closure `Fn(ConfirmationRequest) -> impl Future<Output = ToolConfirmation>` // works, or implement the `ConfirmationProvider` trait. .confirmation_provider(Arc::new(|req: ConfirmationRequest| async move { if approved_by_operator(&req.tool_name, &req.args).await { ToolConfirmation::confirmed() } else { ToolConfirmation::denied("operator rejected the action") } })) .connect_from_env() .await?;For tests and simple defaults, StaticConfirmation::allow_all() /
StaticConfirmation::deny_all("reason") provide uniform providers. The same
ToolDispatcher::with_confirmation_provider / set_confirmation_provider API
gates tools in text-agent pipelines too.
Async / Background Tool Execution
Section titled “Async / Background Tool Execution”For tools that take significant time — database queries, external API calls, LLM sub-pipelines — background execution eliminates dead air in voice sessions.
ToolExecutionMode
Section titled “ToolExecutionMode”pub enum ToolExecutionMode { Standard, // tool runs inline; model waits for the result (default) Background { formatter: Option<Arc<dyn ResultFormatter>>, scheduling: Option<FunctionResponseScheduling>, },}How background execution works
Section titled “How background execution works”- The model sends a
FunctionCallfor a background-declared tool. - An immediate “running” acknowledgment is sent to the model.
- The tool is spawned as a Tokio task. The model continues speaking.
- When the task completes, the result is injected into the conversation using
the configured
FunctionResponseSchedulingmode.
FunctionResponseScheduling modes
Section titled “FunctionResponseScheduling modes”| Mode | Behaviour |
|---|---|
Interrupt |
Model halts current output and immediately handles the result |
WhenIdle |
Model waits until it finishes current output before handling (default) |
Silent |
Model integrates the result without notifying the user |
Platform support: async tool calling is only supported on Google AI.
On Vertex AI, behavior: NonBlocking is automatically stripped from
FunctionDeclaration setup messages and scheduling is stripped from
FunctionResponse. You can set these fields unconditionally; the SDK handles
the platform difference. Use config.supports_async_tools() to check at runtime.
L2 fluent API
Section titled “L2 fluent API”Live::builder() .dispatcher(dispatcher) .tool_background("search_kb") // WhenIdle scheduling by default .tool_background_with_scheduling( "log_event", FunctionResponseScheduling::Silent, // quiet integration ) .connect_from_env() .await?;L1 builder API
Section titled “L1 builder API”LiveSessionBuilder::new(config) .dispatcher(dispatcher) .tool_execution_mode("search_kb", ToolExecutionMode::Background { formatter: None, scheduling: Some(FunctionResponseScheduling::WhenIdle), }) .connect() .await?;Custom result formatting
Section titled “Custom result formatting”Implement ResultFormatter to control the JSON shape of acknowledgment and
completion messages:
use gemini_adk_rs::live::background_tool::{ResultFormatter, DefaultResultFormatter};
struct VoiceFriendlyFormatter;
impl ResultFormatter for VoiceFriendlyFormatter { fn format_running(&self, call: &FunctionCall) -> Value { json!({ "status": "searching", "for": call.args["query"] }) }
fn format_result(&self, call: &FunctionCall, result: Result<Value, ToolError>) -> Value { match result { Ok(v) => json!({ "status": "found", "tool": call.name, "data": v }), Err(e) => json!({ "status": "failed", "tool": call.name, "error": e.to_string() }), } }
fn format_cancelled(&self, call_id: &str) -> Value { json!({ "status": "cancelled", "id": call_id }) }}
// Register with custom formatter:Live::builder() .tool_background_with_formatter("search_kb", Arc::new(VoiceFriendlyFormatter))If formatter is None, DefaultResultFormatter is used, which produces:
{ "status": "running", "tool": "search_kb" }{ "status": "completed", "tool": "search_kb", "result": { ... } }{ "status": "error", "tool": "search_kb", "error": "..." }{ "status": "cancelled", "call_id": "fc_123" }Cancellation
Section titled “Cancellation”Background tasks are cancelled when:
- The server sends
ToolCallCancellation - The session disconnects
LiveHandleis dropped
BackgroundToolTracker provides belt-and-suspenders cleanup: both the
CancellationToken is triggered and the JoinHandle is aborted.
Combining policies
Section titled “Combining policies”Policies and execution modes compose freely:
Live::builder() .tools( // 10-second timeout + in-session cache T::cached(T::timeout( get_stock_price(), Duration::from_secs(10), )) // confirmation required on dangerous action + T::confirm( cancel_order(), "Cancel this order — are you sure?", ) + T::google_search() ) .tool_background("get_stock_price") // also run it in backgroundSee also
Section titled “See also”- Tools —
SimpleTool,TypedTool,ToolDispatcher, and tool registration - MCP Tools — applying policies to Model Context Protocol tools
- Middleware —
before_tool/after_toolhooks in agent pipelines - cookbook 34 — tool policies