# `Nous.ToolExecutor`
[🔗](https://github.com/nyo16/nous/blob/v0.17.1/lib/nous/tool_executor.ex#L1)

Executes tool functions with retry logic, timeout handling, and error handling.

The ToolExecutor is responsible for:
- Calling tool functions with the correct arguments
- Managing the RunContext
- Implementing retry logic on failures
- Handling timeouts
- Processing ContextUpdate returns
- Logging execution

## Telemetry & sensitive data

The `[:nous, :tool, :execute, :exception]` event carries the raw `:reason`
(the exception/exit term) and full `:stacktrace` in its metadata so local
handlers can debug failures. A tool that fails while holding a secret (an API
key in an error message, a token in a struct field) can therefore surface
that secret in the event. This is safe for in-process handlers but **any
subscriber that forwards these events off-box (Sentry, Honeycomb, log
shippers) MUST scrub `:reason`/`:stacktrace` first.** The emitter intentionally
does not redact, to preserve debuggability for trusted handlers.

# `execute_result`

```elixir
@type execute_result() ::
  {:ok, any()} | {:ok, any(), Nous.Tool.ContextUpdate.t()} | {:error, term()}
```

# `execute`

```elixir
@spec execute(Nous.Tool.t(), map(), Nous.RunContext.t()) :: execute_result()
```

Execute a tool with the given arguments.

Automatically handles:
- Passing RunContext to tools that need it
- Retrying on failure (up to tool.retries times), except on timeout
- Timeout enforcement (if tool.timeout is set), which is terminal: a killed
  tool may have already had half its side effects, so it is never re-run
- ContextUpdate extraction from tool results
- Error wrapping and logging

## Return Values

- `{:ok, result}` - Tool executed successfully
- `{:ok, result, context_update}` - Tool executed and wants to update context
- `{:error, reason}` - Tool failed after all retries, or the tool requires
  approval and the context could not supply it

## Approval

A tool with `requires_approval: true` is REJECTED unless the context either
carries an `:approval_handler` that approves it, or is flagged
`approval_gated?: true` by a caller that already ran its own approval
pipeline. `Nous.AgentRunner` sets that flag; other entry points do not.

## Examples

    ctx = RunContext.new(%{database: MyApp.DB})
    arguments = %{"query" => "elixir"}

    case ToolExecutor.execute(tool, arguments, ctx) do
      {:ok, result} ->
        # Tool executed successfully
        result

      {:ok, result, context_update} ->
        # Tool executed and wants to update context
        new_ctx = ContextUpdate.apply_to_run_context(context_update, ctx)
        {result, new_ctx}

      {:error, reason} ->
        # Tool failed after all retries
        handle_error(reason)
    end

---

*Consult [api-reference.md](api-reference.md) for complete listing*
