Skip to content

07.2 Tool Execution

Nikolay Vyahhi edited this page Feb 19, 2026 · 3 revisions

Tool Execution

Relevant source files

The following files were used as context for generating this wiki page:

Purpose and Scope

This document describes the mechanics of tool execution in the ZeroClaw agent loop: how tools are discovered, validated, executed with security enforcement, and their results formatted for the conversation history. This covers the implementation details of the tool execution phase within a single agent turn.

For information about the overall agent turn cycle and tool call loop iteration, see Agent Turn Cycle. For tool definitions and available tools, see Tools and its subsections. For security policy configuration, see Security Model.


Tool Interface and Registration

Tool Trait

All tools implement the Tool trait defined in src/tools/traits.rs. The trait requires four methods:

pub trait Tool: Send + Sync {
    fn name(&self) -> &str;
    fn description(&self) -> &str;
    fn parameters_schema(&self) -> serde_json::Value;
    async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult>;
}

The ToolSpec structure packages tool metadata for LLM consumption:

src/tools/traits.rs:1-40

Sources: src/tools/traits.rs

Tool Registry Construction

Tools are registered into a Vec<Box<dyn Tool>> during agent initialization. The registry is constructed in layers:

  1. Default tools (shell, file_read, file_write): src/tools/mod.rs:72-86
  2. All tools (includes memory, cron, git, browser, HTTP, etc.): src/tools/mod.rs:121-238

The registry construction checks feature flags and configuration to conditionally include tools:

Tool Category Condition Source Lines
Core tools Always included src/tools/mod.rs:136-138
Memory tools Always included src/tools/mod.rs:145-147
Cron tools Always included src/tools/mod.rs:139-144
Browser tools browser_config.enabled src/tools/mod.rs:160-185
HTTP tools http_config.enabled src/tools/mod.rs:187-194
Web search web_search.enabled src/tools/mod.rs:196-204
Composio composio_key present src/tools/mod.rs:210-218
Delegation !agents.is_empty() src/tools/mod.rs:220-235

Tool Registry Construction Flow

graph TB
    subgraph "all_tools_with_runtime"
        Init["Initialize tools Vec"]
        CoreTools["Add core tools<br/>(shell, file_read, file_write)"]
        CronTools["Add cron tools<br/>(add, list, remove, update, run, runs)"]
        MemoryTools["Add memory tools<br/>(store, recall, forget)"]
        UtilTools["Add utility tools<br/>(schedule, proxy_config, git, pushover)"]
        VisionTools["Add vision tools<br/>(screenshot, image_info)"]
        
        CheckBrowser{"browser_config.enabled?"}
        AddBrowser["Add browser_open + browser"]
        
        CheckHttp{"http_config.enabled?"}
        AddHttp["Add http_request"]
        
        CheckWebSearch{"web_search.enabled?"}
        AddWebSearch["Add web_search"]
        
        CheckComposio{"composio_key present?"}
        AddComposio["Add composio"]
        
        CheckAgents{"agents.is_empty()?"}
        AddDelegate["Add delegate"]
        
        Return["Return Vec<Box<dyn Tool>>"]
    end
    
    Init --> CoreTools
    CoreTools --> CronTools
    CronTools --> MemoryTools
    MemoryTools --> UtilTools
    UtilTools --> VisionTools
    VisionTools --> CheckBrowser
    CheckBrowser -->|Yes| AddBrowser
    CheckBrowser -->|No| CheckHttp
    AddBrowser --> CheckHttp
    CheckHttp -->|Yes| AddHttp
    CheckHttp -->|No| CheckWebSearch
    AddHttp --> CheckWebSearch
    CheckWebSearch -->|Yes| AddWebSearch
    CheckWebSearch -->|No| CheckComposio
    AddWebSearch --> CheckComposio
    CheckComposio -->|Yes| AddComposio
    CheckComposio -->|No| CheckAgents
    AddComposio --> CheckAgents
    CheckAgents -->|No| AddDelegate
    CheckAgents -->|Yes| Return
    AddDelegate --> Return
Loading

Sources: src/tools/mod.rs:121-238


Tool Discovery

During execution, tools are discovered by name using find_tool:

src/agent/loop_.rs:275-278

This performs a linear search through the tools registry, returning Option<&dyn Tool>. Tool names must match exactly (case-sensitive).

Tool Name Resolution

graph LR
    LLMResponse["LLM Response<br/>with tool_calls"]
    ParsedCall["ParsedToolCall<br/>{name, arguments}"]
    FindTool["find_tool(tools, name)"]
    ToolRef["&dyn Tool"]
    Execute["tool.execute(args)"]
    NotFound["Tool not found<br/>error in result"]
    
    LLMResponse --> ParsedCall
    ParsedCall --> FindTool
    FindTool -->|Found| ToolRef
    FindTool -->|None| NotFound
    ToolRef --> Execute
Loading

Sources: src/agent/loop_.rs:275-278


Tool Call Parsing

The agent loop supports multiple tool call formats to accommodate different LLM providers and prompting strategies. All formats are parsed into a unified ParsedToolCall structure:

struct ParsedToolCall {
    name: String,
    arguments: serde_json::Value,
}

src/agent/loop_.rs:810-814

Parsing Strategy

The parser attempts formats in order of specificity:

  1. Native function calling (OpenAI/Anthropic format with structured tool_calls array)
  2. XML-style tags (<tool_call>, <toolcall>, <tool-call>, <invoke>)
  3. Markdown code blocks (```tool_call language specifier)
  4. GLM-style proprietary format (tool_name/param>value)

Tool Call Parsing Flow

graph TB
    Response["LLM Response Text"]
    
    TryJSON{"Parse as JSON with<br/>tool_calls array?"}
    JSONSuccess["parse_tool_calls_from_json_value"]
    
    TryXML{"Find XML tags?<br/><tool_call>, <invoke>, etc."}
    XMLParse["Extract JSON from tag body"]
    
    TryMarkdown{"Find ```tool_call<br/>code blocks?"}
    MarkdownParse["Extract JSON from block"]
    
    TryGLM{"Find GLM format?<br/>tool/param>value"}
    GLMParse["parse_glm_style_tool_calls"]
    
    NoCalls["No tool calls found<br/>Return empty Vec"]
    
    ReturnCalls["Return Vec<ParsedToolCall>"]
    
    Response --> TryJSON
    TryJSON -->|Yes| JSONSuccess
    TryJSON -->|No| TryXML
    JSONSuccess --> ReturnCalls
    
    TryXML -->|Yes| XMLParse
    TryXML -->|No| TryMarkdown
    XMLParse --> ReturnCalls
    
    TryMarkdown -->|Yes| MarkdownParse
    TryMarkdown -->|No| TryGLM
    MarkdownParse --> ReturnCalls
    
    TryGLM -->|Yes| GLMParse
    TryGLM -->|No| NoCalls
    GLMParse --> ReturnCalls
    NoCalls --> ReturnCalls
Loading

Sources: src/agent/loop_.rs:595-748

Native Function Calling

Providers that support native tool calling (OpenAI, Anthropic, OpenRouter, etc.) return structured ToolCall objects:

src/providers/traits.rs (referenced from src/agent/loop_.rs:750-759)

These are parsed by parse_structured_tool_calls:

src/agent/loop_.rs:750-759

XML-Style Tags

For providers using prompt-guided tool calling, the system prompt instructs the LLM to wrap tool calls in XML tags:

<tool_call>
{"name": "shell", "arguments": {"command": "ls -la"}}
</tool_call>

Supported tag variants: <tool_call>, <toolcall>, <tool-call>, <invoke> src/agent/loop_.rs:349

The parser extracts JSON from within the tags using extract_json_values src/agent/loop_.rs:412-448

Sources: src/agent/loop_.rs:349-748

GLM-Style Format

GLM models use proprietary tool call formats:

shell/command>ls -la
browser_open/url>https://example.com
http_request/url>https://api.example.com

These are parsed by parse_glm_style_tool_calls src/agent/loop_.rs:512-580, which:

  1. Maps tool name aliases (browser_openshell)
  2. Extracts parameter name and value from the param>value syntax
  3. Constructs appropriate JSON arguments for the target tool

Sources: src/agent/loop_.rs:491-580

Security Considerations

The parser deliberately refuses to extract arbitrary JSON from response text. Tool calls must be explicitly marked with one of the supported formats. This prevents prompt injection attacks where malicious content (in emails, files, or web pages) could include JSON that mimics a tool call.

src/agent/loop_.rs:732-740

Sources: src/agent/loop_.rs:732-740


Security Enforcement

Every tool execution passes through security checks before execution. The checks occur at two levels:

  1. Security policy enforcement (autonomy level, rate limiting)
  2. Tool-specific validation (workspace restrictions, domain allowlists, etc.)

Autonomy Level Checks

The SecurityPolicy::enforce_tool_operation method blocks execution based on autonomy level:

Autonomy Level Behavior
ReadOnly Blocks all ToolOperation::Act operations
Supervised Allows actions (may prompt for approval in CLI)
Full Allows all actions

Tools call this before performing actions:

if let Err(error) = self.security.enforce_tool_operation(ToolOperation::Act, "tool_name") {
    return Ok(ToolResult {
        success: false,
        output: String::new(),
        error: Some(error),
    });
}

Examples:

Sources: src/tools/composio.rs:491-500, src/tools/delegate.rs:174-183, src/tools/pushover.rs:115-121

Rate Limiting

After autonomy checks, tools call security.record_action() to enforce rate limits:

if !self.security.record_action() {
    return Ok(ToolResult {
        success: false,
        output: String::new(),
        error: Some("Action blocked: rate limit exceeded".into()),
    });
}

This increments the action counter and checks against max_actions_per_hour from the security policy.

Example: src/tools/pushover.rs:123-129

Sources: src/tools/pushover.rs:123-129

Approval Flow

In CLI mode with supervised autonomy, tools that require approval trigger an interactive prompt before execution:

src/agent/loop_.rs:996-1024

The approval manager checks needs_approval(tool_name), prompts the user in CLI mode (or auto-approves in channel mode), and records the decision. If denied, the tool is not executed and a "Denied by user" result is returned.

Sources: src/agent/loop_.rs:996-1024


Execution Flow

Step-by-Step Execution Sequence

Tool Execution Sequence

sequenceDiagram
    participant Loop as run_tool_call_loop
    participant Provider as LLM Provider
    participant Parser as parse_tool_calls
    participant Approval as ApprovalManager
    participant Security as SecurityPolicy
    participant Tool as Tool::execute
    participant Scrubber as scrub_credentials
    
    Loop->>Provider: chat(messages, tools, model, temp)
    Provider-->>Loop: response with tool_calls
    
    Loop->>Parser: parse_tool_calls(response_text)
    alt Native tool_calls present
        Parser-->>Loop: parse_structured_tool_calls
    else XML/Markdown/GLM format
        Parser-->>Loop: ParsedToolCall Vec
    end
    
    loop For each tool_call
        alt Approval needed (CLI + Supervised)
            Loop->>Approval: needs_approval(tool_name)?
            Approval-->>Loop: prompt_cli(request)
            alt User denied
                Loop->>Loop: Record "Denied by user"
                Note over Loop: Skip execution, continue to next
            end
        end
        
        Loop->>Loop: find_tool(tools_registry, name)
        alt Tool not found
            Loop->>Loop: Append error to results
        else Tool found
            Loop->>Tool: execute(arguments)
            
            Tool->>Security: enforce_tool_operation(Act, tool_name)
            alt Security denied
                Security-->>Tool: Err(reason)
                Tool-->>Loop: ToolResult{success: false}
            else Security allowed
                Tool->>Security: record_action() for rate limit
                alt Rate limit exceeded
                    Security-->>Tool: false
                    Tool-->>Loop: ToolResult{success: false}
                else Within limits
                    Tool->>Tool: Perform actual operation
                    Tool-->>Loop: ToolResult{success, output, error}
                end
            end
            
            Loop->>Scrubber: scrub_credentials(result.output)
            Scrubber-->>Loop: sanitized_output
            Loop->>Loop: Format as XML or native role:tool message
        end
    end
    
    Loop->>Loop: Append tool results to history
    Loop->>Provider: chat(updated_history) for next iteration
Loading

Sources: src/agent/loop_.rs:851-1132

Credential Scrubbing

All tool output passes through scrub_credentials before being added to the conversation history:

src/agent/loop_.rs:45-77

This function:

  1. Matches credential patterns (token, api_key, password, secret, etc.)
  2. Preserves first 4 characters for context
  3. Redacts the remainder with *[REDACTED]

Example transformations:

  • api_key: sk_live_abcdef123456api_key: sk_l*[REDACTED]
  • "password": "hunter2secret""password": "hunt*[REDACTED]"

Sources: src/agent/loop_.rs:45-77

Result Formatting

Tool results are formatted differently depending on whether the provider uses native function calling:

Native Function Calling Mode

For providers with supports_native_tools(), results use role: tool messages with the original tool call ID:

src/agent/loop_.rs:1067-1089

The assistant's response is recorded as JSON with both content and tool_calls:

src/agent/loop_.rs:764-787

XML Mode

For prompt-guided providers, results are wrapped in XML tags:

<tool_result name="shell">
stdout content here
</tool_result>

src/agent/loop_.rs:1046-1066

Sources: src/agent/loop_.rs:764-787, src/agent/loop_.rs:1046-1089


Tool Result Format

All tools return a ToolResult structure:

pub struct ToolResult {
    pub success: bool,
    pub output: String,
    pub error: Option<String>,
}

src/tools/traits.rs (referenced from tool implementations)

Field Purpose
success Boolean indicating whether the operation succeeded
output Primary output text (stdout, API response, etc.)
error Optional error message (populated when success: false)

Tools should:

  • Return success: true with meaningful output for successful operations
  • Return success: false with error: Some(message) for failures
  • Keep output empty when success: false (error details go in error field)

Example Tool Result Patterns

Successful execution:

Ok(ToolResult {
    success: true,
    output: "File written successfully: 42 bytes".into(),
    error: None,
})

Validation failure:

Ok(ToolResult {
    success: false,
    output: String::new(),
    error: Some("Missing required parameter 'path'".into()),
})

Security denial:

Ok(ToolResult {
    success: false,
    output: String::new(),
    error: Some("Action blocked: autonomy is read-only".into()),
})

Examples from codebase:

Sources: src/tools/traits.rs, src/tools/pushover.rs:199-206, src/tools/composio.rs:482-486, src/tools/delegate.rs:178-183


Tool Implementation Examples

Minimal Tool: Pushover Notification

The Pushover tool demonstrates the minimal implementation pattern:

  1. Check security policy src/tools/pushover.rs:115-121
  2. Record action for rate limiting src/tools/pushover.rs:123-129
  3. Validate parameters src/tools/pushover.rs:131-156
  4. Load credentials from environment src/tools/pushover.rs:157
  5. Execute external operation src/tools/pushover.rs:159-182
  6. Return ToolResult src/tools/pushover.rs:198-213

Sources: src/tools/pushover.rs:114-214

Complex Tool: Composio Integration

The Composio tool shows advanced patterns:

Sources: src/tools/composio.rs:438-584

Delegation Tool: Sub-Agent Orchestration

The delegate tool demonstrates recursive agent patterns:

Sources: src/tools/delegate.rs:104-265


Clone this wiki locally