pub trait ToolExecutor: Send + Sync {
Show 13 methods
// Required methods
fn execute(
&self,
response: &str,
) -> impl Future<Output = Result<Option<ToolOutput>, ToolError>> + Send;
fn execute_tool_call_confirmed(
&self,
call: &ToolCall,
) -> impl Future<Output = Result<Option<ToolOutput>, ToolError>> + Send;
fn checkpoint_undo(&self, n: usize) -> CheckpointActionResult;
fn checkpoint_redo(&self) -> CheckpointActionResult;
fn checkpoint_list(&self) -> CheckpointListResult;
fn is_tool_speculatable(&self, _tool_id: &str) -> bool;
fn requires_confirmation(&self, _call: &ToolCall) -> bool;
// Provided methods
fn execute_confirmed(
&self,
response: &str,
) -> impl Future<Output = Result<Option<ToolOutput>, ToolError>> + Send { ... }
fn tool_definitions(&self) -> Vec<ToolDef> { ... }
fn execute_tool_call(
&self,
_call: &ToolCall,
) -> impl Future<Output = Result<Option<ToolOutput>, ToolError>> + Send { ... }
fn set_skill_env(&self, _env: Option<HashMap<String, String>>) { ... }
fn set_effective_trust(&self, _level: SkillTrustLevel) { ... }
fn is_tool_retryable(&self, _tool_id: &str) -> bool { ... }
}Expand description
Async trait for tool execution backends.
Implementations include ShellExecutor,
WebScrapeExecutor, CompositeExecutor,
and FileExecutor.
§Contract
executeandexecute_tool_callreturnOk(None)when the executor does not handle the given input — callers must not treatNoneas an error.- All methods must be
Send + Syncand free of blocking I/O. - Implementations must enforce their own security controls (blocklists, sandboxes, SSRF protection) before executing any side-effectful operation.
execute_confirmedandexecute_tool_call_confirmedbypass confirmation gates only — all other security controls remain active.
§Two Invocation Paths
Legacy fenced blocks: The agent loop passes the raw LLM response string to execute.
The executor parses ```bash or ```scrape blocks and executes each one.
Structured tool calls: The agent loop constructs a ToolCall from the LLM’s
JSON tool-use response and dispatches it via execute_tool_call.
This is the preferred path for new code.
§Example
use zeph_tools::{ToolExecutor, ToolCall, ToolOutput, ToolError, executor::ClaimSource};
#[derive(Debug)]
struct EchoExecutor;
impl ToolExecutor for EchoExecutor {
async fn execute(&self, _response: &str) -> Result<Option<ToolOutput>, ToolError> {
Ok(None) // not a fenced-block executor
}
async fn execute_tool_call(&self, call: &ToolCall) -> Result<Option<ToolOutput>, ToolError> {
if call.tool_id != "echo" {
return Ok(None);
}
let text = call.params.get("text")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_owned();
Ok(Some(ToolOutput {
tool_name: "echo".into(),
summary: text,
blocks_executed: 1,
..Default::default()
}))
}
zeph_tools::tool_executor_no_inner_defaults!();
}§TODO (G3 — deferred: Tower-style tool middleware stack)
Currently, cross-cutting concerns (audit logging, rate limiting, sandboxing, guardrails)
are scattered across individual executor implementations. The planned approach is a
composable middleware stack similar to Tower’s Service trait:
AuditLayer::new(RateLimitLayer::new(SandboxLayer::new(ShellExecutor::new())))Blocked by: requires D2 (consolidating ToolExecutor + ErasedToolExecutor into one
object-safe trait). See critic review §S3 for the tradeoff between RPIT fast-path and
dynamic dispatch overhead before collapsing D2.
§TODO (D2 — deferred: consolidate ToolExecutor and ErasedToolExecutor)
Having two parallel traits creates duplication and confusion. The blanket impl
impl<T: ToolExecutor> ErasedToolExecutor for T works but every new method must be
added to both traits. Use trait_variant::make or a single object-safe design.
Blocked by: need to benchmark the RPIT fast-path before removing it. See critic §S3.
Required Methods§
Sourcefn execute(
&self,
response: &str,
) -> impl Future<Output = Result<Option<ToolOutput>, ToolError>> + Send
fn execute( &self, response: &str, ) -> impl Future<Output = Result<Option<ToolOutput>, ToolError>> + Send
Sourcefn execute_tool_call_confirmed(
&self,
call: &ToolCall,
) -> impl Future<Output = Result<Option<ToolOutput>, ToolError>> + Send
fn execute_tool_call_confirmed( &self, call: &ToolCall, ) -> impl Future<Output = Result<Option<ToolOutput>, ToolError>> + Send
Execute a structured tool call bypassing confirmation checks.
Called after the user has explicitly approved the tool invocation.
Required (no default): a wrapper that forgets to override this silently falls back
to whatever an inherited default would do, which has repeatedly reintroduced #6019.
Executors with no confirmation-specific behavior should delegate to
execute_tool_call; see
tool_executor_no_inner_defaults! for leaf
executors with no wrapped inner.
§Errors
Returns ToolError on execution failure.
Sourcefn checkpoint_undo(&self, n: usize) -> CheckpointActionResult
fn checkpoint_undo(&self, n: usize) -> CheckpointActionResult
Undo the last n checkpointed write commands.
Required (no default). Executors that implement checkpoints (i.e.
ShellExecutor with checkpoints_enabled = true) provide
real behavior here; all others should return CheckpointActionResult::unsupported.
Wrappers must forward to their inner executor — see
tool_executor_forward!.
Sourcefn checkpoint_redo(&self) -> CheckpointActionResult
fn checkpoint_redo(&self) -> CheckpointActionResult
Redo the last undone checkpoint.
Required (no default). See checkpoint_undo.
Sourcefn checkpoint_list(&self) -> CheckpointListResult
fn checkpoint_list(&self) -> CheckpointListResult
List the current undo stack entries and redo depth.
Required (no default). See checkpoint_undo.
Sourcefn is_tool_speculatable(&self, _tool_id: &str) -> bool
fn is_tool_speculatable(&self, _tool_id: &str) -> bool
Whether a tool call can be safely dispatched speculatively (before the LLM finishes).
Speculative execution requires the tool to be:
- Idempotent — repeated execution with the same args produces the same result.
- Side-effect-free or cheaply reversible.
- Not subject to user confirmation (
needs_confirmationmust be false at call time).
Required (no default). Return false (safe) unless the tool satisfies all three
properties above. The engine additionally gates on trust level and confirmation
status regardless of this flag.
§Examples
use zeph_tools::{ToolExecutor, ToolCall, ToolOutput, ToolError};
struct ReadOnlyExecutor;
impl ToolExecutor for ReadOnlyExecutor {
async fn execute(&self, _: &str) -> Result<Option<ToolOutput>, ToolError> {
Ok(None)
}
fn is_tool_speculatable(&self, _tool_id: &str) -> bool {
true // read-only, idempotent
}
fn requires_confirmation(&self, _call: &ToolCall) -> bool {
false
}
async fn execute_tool_call_confirmed(
&self,
call: &ToolCall,
) -> Result<Option<ToolOutput>, ToolError> {
self.execute_tool_call(call).await
}
fn checkpoint_undo(&self, _n: usize) -> zeph_tools::CheckpointActionResult {
zeph_tools::CheckpointActionResult::unsupported()
}
fn checkpoint_redo(&self) -> zeph_tools::CheckpointActionResult {
zeph_tools::CheckpointActionResult::unsupported()
}
fn checkpoint_list(&self) -> zeph_tools::CheckpointListResult {
zeph_tools::CheckpointListResult::default()
}
}Sourcefn requires_confirmation(&self, _call: &ToolCall) -> bool
fn requires_confirmation(&self, _call: &ToolCall) -> bool
Return true when call would require user confirmation before execution.
This is a pure metadata/policy query — implementations must not execute the tool. Used by the speculative engine to gate dispatch without causing double side-effects.
Required (no default). Executors with no confirmation policy of their own should
return false; executors that enforce one (e.g. TrustGateExecutor) must reflect
their actual policy without executing the tool.
Provided Methods§
Sourcefn execute_confirmed(
&self,
response: &str,
) -> impl Future<Output = Result<Option<ToolOutput>, ToolError>> + Send
fn execute_confirmed( &self, response: &str, ) -> impl Future<Output = Result<Option<ToolOutput>, ToolError>> + Send
Sourcefn tool_definitions(&self) -> Vec<ToolDef>
fn tool_definitions(&self) -> Vec<ToolDef>
Return the tool definitions this executor can handle.
Used to populate the LLM’s tool schema at context-assembly time.
Returns an empty Vec by default (for executors that only handle fenced blocks).
Sourcefn execute_tool_call(
&self,
_call: &ToolCall,
) -> impl Future<Output = Result<Option<ToolOutput>, ToolError>> + Send
fn execute_tool_call( &self, _call: &ToolCall, ) -> impl Future<Output = Result<Option<ToolOutput>, ToolError>> + Send
Sourcefn set_skill_env(&self, _env: Option<HashMap<String, String>>)
fn set_skill_env(&self, _env: Option<HashMap<String, String>>)
Inject environment variables for the currently active skill. No-op by default.
Called by the agent loop before each turn when the active skill specifies env vars.
Implementations that ignore this (e.g. WebScrapeExecutor) may leave the default.
Sourcefn set_effective_trust(&self, _level: SkillTrustLevel)
fn set_effective_trust(&self, _level: SkillTrustLevel)
Set the effective trust level for the currently active skill. No-op by default.
Trust level affects which operations are permitted (e.g. network access, file writes).
Sourcefn is_tool_retryable(&self, _tool_id: &str) -> bool
fn is_tool_retryable(&self, _tool_id: &str) -> bool
Whether the executor can safely retry this tool call on a transient error.
Only idempotent operations (e.g. read-only HTTP GET) should return true.
Shell commands and other non-idempotent operations must keep the default false
to prevent double-execution of side-effectful commands.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".