pub struct ToolLoopConfig {
pub max_iterations: u32,
pub parallel_tool_execution: bool,
pub on_tool_call: Option<ToolApprovalFn>,
pub stop_when: Option<StopConditionFn>,
pub loop_detection: Option<LoopDetectionConfig>,
pub timeout: Option<Duration>,
pub max_depth: Option<u32>,
}Expand description
Configuration for tool_loop and tool_loop_stream.
Fields§
§max_iterations: u32Maximum number of generate-execute iterations. Default: 10.
parallel_tool_execution: boolWhether to execute multiple tool calls in parallel. Default: true.
on_tool_call: Option<ToolApprovalFn>Optional callback to approve, deny, or modify each tool call before execution.
Called once per tool call in the LLM response, after the response
is assembled but before any tool is executed. Receives the
ToolCall as parsed from the LLM output.
Modified arguments are re-validated against the tool’s schema.
Panics in the callback propagate and terminate the loop.
stop_when: Option<StopConditionFn>Optional stop condition checked after each LLM response.
Called after the LLM response is received but before tools
are executed. If the callback returns StopDecision::Stop or
StopDecision::StopWithReason, the loop terminates immediately
without executing the requested tool calls.
Receives a StopContext with information about the current
iteration and returns a StopDecision. Use this to implement:
final_answertool patterns (stop when a specific tool is called)- Token budget enforcement
- Total tool call limits
- Content pattern matching
§Example
use llm_stack::tool::{ToolLoopConfig, StopDecision};
use std::sync::Arc;
let config = ToolLoopConfig {
stop_when: Some(Arc::new(|ctx| {
// Stop if we've executed 5 or more tool calls
if ctx.tool_calls_executed >= 5 {
StopDecision::StopWithReason("Tool call limit reached".into())
} else {
StopDecision::Continue
}
})),
..Default::default()
};loop_detection: Option<LoopDetectionConfig>Optional loop detection to catch stuck agents.
When enabled, tracks consecutive identical tool calls (same name and arguments) and takes action when the threshold is reached.
§Example
use llm_stack::tool::{ToolLoopConfig, LoopDetectionConfig, LoopAction};
let config = ToolLoopConfig {
loop_detection: Some(LoopDetectionConfig {
threshold: 3,
action: LoopAction::InjectWarning,
}),
..Default::default()
};timeout: Option<Duration>Maximum wall-clock time for the entire tool loop.
If exceeded, returns with TerminationReason::Timeout.
This is useful for enforcing time budgets in production systems.
§Example
use llm_stack::tool::ToolLoopConfig;
use std::time::Duration;
let config = ToolLoopConfig {
timeout: Some(Duration::from_secs(30)),
..Default::default()
};max_depth: Option<u32>Maximum allowed nesting depth for recursive tool loops.
When a tool calls tool_loop internally (e.g., spawning a sub-agent),
the depth is tracked via the context’s LoopDepth
implementation. If ctx.loop_depth() >= max_depth at entry,
returns Err(LlmError::MaxDepthExceeded).
Some(n): Error if depth >= nNone: No limit (dangerous, use with caution)
Default: Some(3) (allows master → worker → one more level)
§Example
use llm_stack::tool::ToolLoopConfig;
// Master/Worker pattern: master=0, worker=1, no grandchildren
let config = ToolLoopConfig {
max_depth: Some(2),
..Default::default()
};