1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! Tool execution ordering policy.
//!
//! By default the agent loop executes all tool calls concurrently via
//! `tokio::spawn`. This module provides [`ToolExecutionPolicy`] to control
//! dispatch ordering — sequential, priority-based, or fully custom via the
//! [`ToolExecutionStrategy`] trait.
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use serde_json::Value;
// ─── ToolCallSummary ─────────────────────────────────────────────────────────
/// Lightweight view of a pending tool call, exposed to policy callbacks.
///
/// This is intentionally a borrowed view so priority functions do not need
/// to clone arguments.
#[non_exhaustive]
#[derive(Debug)]
pub struct ToolCallSummary<'a> {
/// Unique identifier for this tool call.
pub id: &'a str,
/// Name of the tool being invoked.
pub name: &'a str,
/// Arguments passed to the tool.
pub arguments: &'a Value,
}
impl<'a> ToolCallSummary<'a> {
/// Create a new tool call summary from its borrowed fields.
#[must_use]
pub const fn new(id: &'a str, name: &'a str, arguments: &'a Value) -> Self {
Self {
id,
name,
arguments,
}
}
}
// ─── PriorityFn ──────────────────────────────────────────────────────────────
/// Callback that assigns an integer priority to a tool call.
///
/// Higher values execute first. Tool calls with the same priority execute
/// concurrently within their group; groups execute sequentially from highest
/// to lowest priority.
pub type PriorityFn = dyn Fn(&ToolCallSummary<'_>) -> i32 + Send + Sync;
/// A boxed future returned by a [`ToolExecutionStrategy`].
pub type ToolExecutionStrategyFuture<'a> =
Pin<Box<dyn Future<Output = Vec<Vec<usize>>> + Send + 'a>>;
// ─── ToolExecutionStrategy ───────────────────────────────────────────────────
/// Fully custom tool execution strategy.
///
/// Implementations receive the post-preprocessing tool-call slice that is
/// actually eligible for dispatch. Calls skipped by policies or rejected by
/// approval are already removed before this hook runs. The strategy returns
/// execution groups — each group is a `Vec<usize>` of indices into the
/// provided `tool_calls` slice. Tools within a group execute concurrently;
/// groups execute sequentially in order.
pub trait ToolExecutionStrategy: Send + Sync {
/// Partition tool calls into sequential execution groups.
///
/// Each inner `Vec<usize>` contains indices into the provided
/// `tool_calls` slice that should execute concurrently. The outer `Vec`
/// is processed sequentially — group 0 completes before group 1 starts,
/// etc. Every provided tool call must appear exactly once across all
/// groups; out-of-bounds, duplicate, or missing indices are rejected by
/// the dispatch layer as deterministic tool errors.
fn partition(&self, tool_calls: &[ToolCallSummary<'_>]) -> ToolExecutionStrategyFuture<'_>;
}
// ─── ToolExecutionPolicy ─────────────────────────────────────────────────────
/// Controls how tool calls within a single turn are dispatched.
///
/// The default is [`Concurrent`](ToolExecutionPolicy::Concurrent), which
/// preserves backward compatibility by spawning all tool calls at once.
#[non_exhaustive]
#[derive(Default)]
pub enum ToolExecutionPolicy {
/// Execute all tool calls concurrently via `tokio::spawn` (default).
#[default]
Concurrent,
/// Execute tool calls one at a time, in the order the model returned them.
Sequential,
/// Sort tool calls by priority (higher first), then execute groups of
/// equal priority concurrently. Groups run sequentially from highest to
/// lowest.
Priority(Arc<PriorityFn>),
/// Fully custom execution strategy.
Custom(Arc<dyn ToolExecutionStrategy>),
}
impl Clone for ToolExecutionPolicy {
fn clone(&self) -> Self {
match self {
Self::Concurrent => Self::Concurrent,
Self::Sequential => Self::Sequential,
Self::Priority(f) => Self::Priority(Arc::clone(f)),
Self::Custom(s) => Self::Custom(Arc::clone(s)),
}
}
}
impl std::fmt::Debug for ToolExecutionPolicy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Concurrent => write!(f, "Concurrent"),
Self::Sequential => write!(f, "Sequential"),
Self::Priority(_) => write!(f, "Priority(...)"),
Self::Custom(_) => write!(f, "Custom(...)"),
}
}
}
// ─── Compile-time Send + Sync assertions ─────────────────────────────────────
#[cfg(test)]
#[path = "tool_execution_policy_tests.rs"]
mod tests;