Current section

Files

Jump to
planck_agent lib planck agent tool_runner.ex
Raw

lib/planck/agent/tool_runner.ex

defmodule Planck.Agent.ToolRunner do
@moduledoc """
Tracks in-flight tool executions and accumulates their results.
Created at the start of a tool-execution phase and consumed when all
tools have reported back. Reset to empty at the end of each turn.
"""
@typedoc """
- `:running` — map of `call_id => %{name: name, pid: pid}` for in-flight tools
- `:results` — accumulated `{call_id, result}` pairs as tools complete
"""
@type t :: %__MODULE__{
running: %{String.t() => %{name: String.t(), pid: pid()}},
results: list()
}
defstruct running: %{}, results: []
@doc "Return an empty runner."
@spec new() :: t()
def new, do: %__MODULE__{}
@doc "Register a set of running tool tasks."
@spec start([{String.t(), String.t(), pid()}]) :: t()
def start(entries)
def start(entries) when is_list(entries) do
running = Map.new(entries, fn {id, name, pid} -> {id, %{name: name, pid: pid}} end)
%__MODULE__{running: running}
end
@doc """
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).
"""
@spec mark_done(t(), String.t(), term()) :: {:ok, t()} | :not_running
def mark_done(runner, call_id, result)
def mark_done(%__MODULE__{running: running, results: results} = runner, call_id, result) do
case Map.pop(running, call_id) do
{nil, _} ->
:not_running
{_, remaining} ->
{:ok, %{runner | running: remaining, results: [{call_id, result} | results]}}
end
end
@doc "Return `true` when all tool calls have completed."
@spec done?(t()) :: boolean()
def done?(runner)
def done?(%__MODULE__{running: running}), do: map_size(running) == 0
@doc "Kill all in-flight tool task processes."
@spec cancel_all(t()) :: :ok
def cancel_all(runner)
def cancel_all(%__MODULE__{running: running}) do
Enum.each(running, fn {_id, %{pid: pid}} -> Process.exit(pid, :kill) end)
end
end