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

A tool the agent can call. Implementations are plain modules, so a host
can register one at runtime by module, or serve changing sets of them
from a source (see `Rho.ToolRegistry`).

`c:run/2` gets the decoded JSON arguments (string keys) and a context map
(`:session_id`, `:call_id`, `:data_dir`; from a loop also `:loop`, its
pid, and `:withheld`, the tool names that loop doesn't offer, so read it
with `Map.get(ctx, :withheld, [])`). It returns `{:ok, output}` or
`{:error, reason}`; non-binary values are JSON-encoded (or inspected) for
the model. Pictures, and data for the host's screens beside the text
(`{:ok, {:details, output, details}}`), are `Rho.ToolRunner`'s. A raise,
exit or timeout becomes an error result; it never takes the loop down.

**Untrusted tools.** The loop reads a tool's declaration (`c:name/0`,
`c:description/0`, `c:parameter_schema/0`, `c:timeout_ms/0`,
`c:concurrency/0`) in its own process. A Dyn tool's
(`Rho.Untrusted.untrusted?/1`) is read instead in one throwaway process
with a time and heap bound, checked to be plain data and kept for the
module's life: one whose name, description or schema raises or isn't a
string / JSON Schema map is left out of what the loop offers (logged
once), and a bad `c:timeout_ms/0` or `c:concurrency/0` means the default. A
read that runs over (a busy phone) leaves the tool out for now and is
tried again after 1, 5 and 30 minutes; after that it stays out.

# `info`

```elixir
@type info() :: %{
  name: String.t(),
  description: String.t(),
  parameter_schema: map(),
  timeout_ms: pos_integer(),
  concurrency: :parallel | :exclusive
}
```

A tool's declaration: its callbacks' answers, checked.

# `concurrency`
*optional* 

```elixir
@callback concurrency() :: :parallel | :exclusive
```

How a call shares its turn with the reply's other calls (omp's tool
`concurrency`). `:parallel` (the default) runs alongside them;
`:exclusive` is for a tool that acts on or reads something only one
call may touch at a time (the front's one screen): a turn with any
exclusive call runs all its calls one at a time, in the model's order.

# `description`

```elixir
@callback description() :: String.t()
```

# `name`

```elixir
@callback name() :: String.t()
```

# `parameter_schema`

```elixir
@callback parameter_schema() :: map()
```

JSON Schema (string keys) of the arguments object.

# `run`

```elixir
@callback run(args :: map(), ctx :: map()) :: {:ok, term()} | {:error, term()}
```

# `selftest`
*optional* 

```elixir
@callback selftest() :: :ok | {:error, term()}
```

Checks the tool works, without side effects outside a temp dir.

# `timeout_ms`
*optional* 

```elixir
@callback timeout_ms() :: pos_integer()
```

Per-call timeout; default `default_timeout_ms/0`.

# `concurrency`

```elixir
@spec concurrency(module()) :: :parallel | :exclusive
```

`module`'s `c:concurrency/0`, or `:parallel`. The loop asks it in its own
process, so anything but `:exclusive`, a raise included, is `:parallel`.

# `default_timeout_ms`

```elixir
@spec default_timeout_ms() :: pos_integer()
```

The per-call timeout of a tool without `c:timeout_ms/0` (30 000 ms).

# `timeout_ms`

```elixir
@spec timeout_ms(module()) :: pos_integer()
```

`module`'s per-call timeout: its `c:timeout_ms/0`, else
`default_timeout_ms/0`. A Dyn tool's comes from its checked declaration.

# `to_req_llm`

```elixir
@spec to_req_llm(module()) :: ReqLLM.Tool.t() | nil
```

The req_llm declaration sent to the model, nil for an untrusted tool
whose declaration failed (it isn't offered). Execution never goes
through req_llm's callback: the loop runs tools itself (in parallel,
gated, with timeouts).

# `tool?`

```elixir
@spec tool?(module()) :: boolean()
```

Is `module` a loaded tool (exports the required callbacks)?

---

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