# `Rho.ToolRunner`
[🔗](https://github.com/GenericJam/rho/blob/v0.1.0/lib/rho/tool_runner.ex#L1)

Runs one tool call in its own task (under a `Task.Supervisor`, not linked
to the loop). The `before_tool_call` gate runs first, inside the task, so
a gate that waits (a biometric approval) never blocks the loop;
once the gate allows, the task tells the loop (`{:tool_running, pid}`),
which starts the tool's timeout clock from then.

The task's result is `{:ok, text}` or `{:error, text}`. A raise, exit or
throw in the task (the gate's or the tool's) is formatted there
(`Rho.Untrusted.format/4`) and ends the task with that text as its exit
reason; the loop turns it, or any other abnormal end of the task (a kill,
a linked process's exit), into an error result without running anything
of the reason (`Rho.Untrusted.exit_text/2`). Before a
tool runs, the registry's sources are told its task
(`Rho.ToolRegistry.watch/2`), so a source of agent-written tools can
count a crash or timeout (the loop kills it) against them.

A Dyn tool's answer (`Rho.Untrusted.untrusted?/1`) is made plain data in
the task (`Rho.Untrusted.plain/1`: structs become maps, funs text)
before it is encoded or leaves the task, so neither the loop nor its
subscribers ever hold a value whose protocols run Dyn code.

A tool may answer with pictures for the model as well as text:
`{:ok, {:images, [{mime_type, bytes}], text}}` (photos), or
`{:ok, {:image, mime_type, bytes, text}}` for one (a screenshot).

And with data for the host's screens beside what the model reads (pi's
tool result `details`): `{:ok, {:details, output, details}}`, where
`output` is any of the above, or `{:error, {:details, reason, details}}`
for a failure. The model and the session get `output` (or `reason`);
`details` goes to the loop's subscribers, with `tool_execution_end`
(`Rho.Events`), so a screen can draw the answer (a passage, a table)
without parsing the text, and into the session only as far as the
host's `:keep_details` (`Rho.Loop`) keeps it.

# `gate`

```elixir
@type gate() :: (call :: map(), ctx :: map() -&gt; :allow | {:block, String.t()})
```

# `images`

```elixir
@type images() :: {:images, [{String.t(), binary()}], String.t()}
```

# `result`

```elixir
@type result() :: {:ok, String.t() | images()} | {:error, String.t()}
```

# `allow_all`

```elixir
@spec allow_all(map(), map()) :: :allow
```

The default gate: everything is allowed.

# `crash_text`

```elixir
@spec crash_text(term()) :: String.t()
```

The error text for a tool task that ended abnormally (its `:DOWN`
reason), in the loop's process: nothing of the reason runs
(`Rho.Untrusted.exit_text/2`); a crash the task formatted itself reads
as that text.

# `start`

```elixir
@spec start(Supervisor.supervisor(), pid(), module(), map(), gate(), map()) ::
  Task.t()
```

Starts `call` (`%{"arguments" => ...}`) of `module` as a task under
`task_sup`, not linked: `gate` first, then the tool, with `ctx` as the
context `c:Rho.Tool.run/2` gets. Once the gate allows, the task sends
`loop` `{:tool_running, pid}`; it returns `to_result/1` of the tool's
answer. This is how `Rho.Loop` runs every call.

# `timeout_text`

```elixir
@spec timeout_text(module()) :: String.t()
```

The error text for a call killed at `module`'s timeout (`Rho.Tool.timeout_ms/1`).

# `to_result`

```elixir
@spec to_result(term()) :: result() | {:details, result(), term()}
```

A tool's return as the loop takes it: `{:ok, text | images}` or
`{:error, text}`, or `{:details, result, details}` for a tool that
answered with details (see the moduledoc).

---

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