# `Arcana.Loop.Tools`
[🔗](https://github.com/georgeguimaraes/arcana/blob/main/lib/arcana/loop/tools.ex#L1)

Default tool definitions and execution for `Arcana.Loop`.

Tools are exposed to the controller LLM as `ReqLLM.Tool` structs. The
callbacks on those structs are placeholders: the loop runner executes
tools itself via `execute/4` so it can mutate the loop state (accumulate
chunks, record history, terminate the loop).

## Default toolset

| Tool      | What it does                                                |
|-----------|-------------------------------------------------------------|
| `search`  | Search the knowledge base. Uses graph data when available. |
| `answer`  | Provide the final answer (terminates the loop).            |
| `give_up` | Admit defeat and stop (terminates the loop).               |

Terminating tools (`answer` and `give_up`) end the loop. `search` is the
only tool that touches your Repo and the only one that accumulates state.

Query rewriting and decomposition aren't separate tools — they happen
inside the `search` tool's `query` parameter, guided by the system prompt.

## Custom tools

Pass `tools: [...]` to `Loop.run/2` with your own `ReqLLM.Tool` structs
appended to (or replacing) the defaults. Custom tools are invoked via
their `:callback` function when the controller calls them.

The callback is a 1-arity function `(args) -> {:ok, text} | {:error, text}`
where `text` is the string returned to the controller as the tool result.
Custom tools always continue the loop (they never terminate it). The
controller can still call `answer` or `give_up` to end the loop.

    web_search = ReqLLM.Tool.new!(
      name: "web_search",
      description: "Search the web for current information.",
      parameter_schema: [query: [type: :string, required: true]],
      callback: fn %{query: q} ->
        case MyApp.WebSearch.run(q) do
          {:ok, results} -> {:ok, format_results(results)}
          {:error, reason} -> {:error, inspect(reason)}
        end
      end
    )

    Loop.run(ctx, tools: Tools.default() ++ [web_search], controller_llm: llm)

# `default`

```elixir
@spec default(Arcana.CollectionScope.input() | [nil]) :: [ReqLLM.Tool.t()]
```

Returns the default list of tool definitions as `ReqLLM.Tool` structs.

These are the tools shipped with `Arcana.Loop`. Pass `tools: default/0`
(or omit `:tools`) to use them. Replace with your own list to customize.

## Context-aware search tool

When called with a list of collection names longer than one, the
`search` tool gains an optional `collection` parameter that the
controller can use to narrow the search per call. When called with
`:all`, no collections, or a single-element list, the parameter is
omitted and the caller-supplied collection (if any) is always used.

That way, `Arcana.Loop.new(collection: "docs")` locks the controller
to a single collection programmatically (the tool can't even express
a different one), while `Arcana.Loop.new(collection: ["a", "b"])`
lets the controller pick per call.

# `execute`

```elixir
@spec execute(Arcana.Loop.Context.t(), String.t(), map(), keyword()) ::
  {:continue, Arcana.Loop.Context.t(), String.t(), map()}
  | {:terminate, Arcana.Loop.Context.t(), atom(), String.t()}
```

Executes a tool call against the loop context.

Returns one of:

  * `{:continue, updated_ctx, summary_text, meta}` - the loop should
    continue; `summary_text` is what gets sent back to the controller
    LLM as the tool result, and `meta` is a map of extra metadata to
    record in the tool history (e.g. `%{returned_chunk_ids: [...]}`
    for search).
  * `{:terminate, updated_ctx, reason, answer_text}` - the loop should
    stop; `reason` is one of `:answered` or `:gave_up`; `answer_text`
    becomes `ctx.answer`.

---

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