# `Planck.Agent.ToolRunner`
[🔗](https://github.com/alexdesousa/planck/blob/v0.2.2/lib/planck/agent/tool_runner.ex#L1)

Tracks in-flight tool executions and handles the full tool execution pipeline:
resolution, argument validation, safe execution, and loop detection.

`loop_counts` persists across tool batches within the same user turn and
resets when a new user turn begins (`do_run_llm` with `:new_turn`).

# `t`

```elixir
@type t() :: %Planck.Agent.ToolRunner{
  loop_counts: %{optional({String.t(), non_neg_integer()}) =&gt; non_neg_integer()},
  results: list(),
  running: %{required(String.t()) =&gt; %{name: String.t(), pid: pid()}}
}
```

- `:running` — map of `call_id => %{name: name, pid: pid}` for in-flight tools
- `:results` — accumulated `{call_id, result}` pairs as tools complete
- `:loop_counts` — consecutive call counts keyed by `{tool_name, args_hash}`;
  persists across tool batches within a user turn, reset on new turns

# `cancel_all`

```elixir
@spec cancel_all(t()) :: :ok
```

Kill all in-flight tool task processes.

# `done?`

```elixir
@spec done?(t()) :: boolean()
```

Return `true` when all tool calls have completed.

# `mark_done`

```elixir
@spec mark_done(t(), String.t(), term()) :: {:ok, t()} | :not_running
```

Mark a tool call as done.

Returns `{:ok, updated_runner}` or `:not_running` when `call_id` is not
in the running set (stale result after an abort).

# `new`

```elixir
@spec new() :: t()
```

Return an empty runner.

# `next_batch`

```elixir
@spec next_batch(t(), [{String.t(), String.t(), pid()}]) :: t()
```

Start a new tool batch while preserving `loop_counts` from the previous batch.

Used for tool continuations within the same user turn so loop detection
accumulates across multiple LLM rounds.

# `prepare_call`

```elixir
@spec prepare_call(
  t(),
  %{required(String.t()) =&gt; Planck.Agent.Tool.t()},
  String.t(),
  String.t(),
  String.t(),
  map()
) ::
  {t(),
   (-&gt; {:ok, String.t()}
       | {:ok, String.t(), %{ui: Planck.Agent.Tool.ui_content()}}
       | {:error, term()})}
```

Resolve, wrap, and track a tool call. Returns `{updated_runner, wrapped_fn}`.

Looks up the tool by name, records the call for loop detection, and returns
a zero-arg closure that:
- Validates arguments against the tool schema
- Executes safely, catching all exceptions and normalising them to strings
- Appends a loop nudge to binary results when consecutive identical calls
  reach `@loop_threshold`

# `start`

```elixir
@spec start([{String.t(), String.t(), pid()}]) :: t()
```

Register a set of running tool tasks.

---

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