An asynchronous tool that executes work in a supervised BEAM process.
Long-running tools spawn a Task under the ADK.RunnerSupervisor,
allowing the tool function to send intermediate status updates while
the caller awaits completion. This is the BEAM-idiomatic equivalent
of Python ADK's LongRunningFunctionTool.
Key differences from Python ADK
Python ADK marks tools with is_long_running = True and relies on
async/await coroutines. In Elixir, we use OTP processes:
- Work runs in a supervised
Task(fault-tolerant, isolated) - Status updates flow via process messages
- Timeout is enforced via
receive...after - Crashes in the tool are caught and returned as
{:error, reason}
Usage
tool = ADK.Tool.LongRunningTool.new(:fetch_report,
description: "Fetch and process a large report",
func: fn _ctx, %{"url" => url}, send_update ->
send_update.("Connecting to #{url}...")
data = fetch_data(url)
send_update.("Processing #{byte_size(data)} bytes...")
process(data)
end,
parameters: %{
type: "object",
properties: %{url: %{type: "string", description: "Report URL"}},
required: ["url"]
},
timeout: 30_000
)Function signature
The func must accept 3 arguments:
tool_ctx—ADK.ToolContext.t()(same as regular tools)args—map()of tool arguments from the LLMsend_update—(String.t() -> :ok)callback to emit status updates
Return value
{:ok, result}— success with final result{:ok, %{result: result, status_updates: [String.t()]}}— success with updates captured{:error, reason}— tool error, timeout, or crash
Description annotation
The tool description is automatically annotated with a note telling the LLM not to call the tool again if it has already returned a pending/intermediate status (matching Python ADK's behavior).
Summary
Types
@type t() :: %ADK.Tool.LongRunningTool{ description: String.t(), func: tool_func(), name: String.t(), parameters: map(), timeout: pos_integer() }
@type tool_func() :: (ADK.ToolContext.t(), map(), update_fn() -> term())
@type update_fn() :: (String.t() -> :ok)
Functions
Create a new long-running tool.
Options
:description— Human-readable description (automatically annotated with long-running notice):func— The tool function(tool_ctx, args, send_update) -> result:parameters— JSON Schema map for parameters:timeout— Milliseconds to wait before timing out (default: 60_000)
Examples
iex> tool = ADK.Tool.LongRunningTool.new(:slow_tool,
...> description: "Does slow work",
...> func: fn _ctx, _args, _send_update -> "done" end,
...> parameters: %{type: "object", properties: %{}}
...> )
iex> tool.name
"slow_tool"
iex> String.contains?(tool.description, "long-running operation")
true
@spec run(t(), ADK.ToolContext.t() | nil, map()) :: ADK.Tool.result()
Execute the long-running tool asynchronously.
Spawns a supervised Task under ADK.RunnerSupervisor, collects
any status updates the function sends, and awaits the final result
within the configured timeout.
Status updates from send_update.(msg) are collected in order.
If any updates were sent, the result is wrapped:
{:ok, %{result: final_value, status_updates: ["update 1", ...]}}.
If no updates were sent, returns {:ok, final_value} directly.
Examples
iex> tool = ADK.Tool.LongRunningTool.new(:fast_tool,
...> func: fn _ctx, %{"x" => x}, _send_update -> x * 2 end,
...> parameters: %{},
...> timeout: 5_000
...> )
iex> {:ok, result} = ADK.Tool.LongRunningTool.run(tool, nil, %{"x" => 21})
iex> result
42