Tool-handler invocation contract. See spec §7.3 and §5.2.
execute/3 takes a %ALLM.Tool{}, the parsed arguments map, and a
keyword opts list carrying call context (:context, :session_id,
:request_id, :tool_call, :engine). It invokes the tool's handler
and returns the handler's return value unchanged — with two exceptions
that belong to the executor, not the handler:
A handler raise / exit / throw / bad return is converted to
{:error, %ALLM.Error.ToolError{}}with a:reasonatom drawn from the closed set:handler_raised | :handler_exit | :timeout | :invalid_return | :encoding_failed | :not_found.A
nilhandler (the%Tool{}was declared for manual-mode use) is converted to{:error, %ALLM.Error.ToolError{reason: :not_found}}— the executor cannot invoke a tool with no handler.
Handler-returned {:error, _} values are NOT converted; they pass
through unchanged so the orchestrator can pattern-match on them
(on_tool_error policy, spec §12.3 / §30). The distinction is "did the
handler crash, or did it report a failure?" — both are failures, but
the orchestrator handles them differently.
Invariants
execute/3receives a%Tool{}whosenamewas looked up by the caller; executors do not consult a registry.execute/3returns one of the fiveALLM.Tool.handler_result/0variants unchanged for handler-returned values; executor-originated failures are{:error, %ToolError{}}structs.optsis populated by the caller; executors do not synthesize:session_id/:request_id/ etc. Missing keys read asnilwhen the executor forwards them to an arity-2 handler.- Handler arity dispatch:
:erlang.fun_info(handler, :arity)—1callshandler.(arguments);2callshandler.(arguments, opts). Any other arity is an invalid handler and raisesArgumentErrorfrom the executor.
Handler result shapes
The five legal handler-returned shapes (spec §5.2):
{:ok, value}— success;valueis handed to theALLM.ToolResultEncoder.{:error, reason}— handler-originated failure; passes through unchanged foron_tool_errordispatch.{:ask_user, question}— suspend the loop and surface a question to the user (spec §12.3).{:ask_user, question, opts}— same, with caller-supplied options.{:halt, reason, result}— halt the loop with a handler-declared terminal result.
Summary
Callbacks
Invoke a tool's handler and return its result.
Callbacks
@callback execute(ALLM.Tool.t(), map(), keyword()) :: ALLM.Tool.handler_result()
Invoke a tool's handler and return its result.
Executor-originated error reasons
The following %ToolError{reason: ...} atoms are produced by the
executor itself (not by handlers). Handler-returned {:error, reason}
tuples pass through unchanged and do NOT carry these atoms.
| Reason | Fires when |
|---|---|
:handler_raised | Handler raised an exception; :cause carries the exception struct. Throws are also normalized here with cause: {:throw, value}. |
:handler_exit | Handler called exit/1 or the handling process died; :cause carries the exit reason term. |
:timeout | Emitted by Phase 6's ALLM.ToolRunner under tool_timeout. The Phase 3 default executor does not produce this directly — conformance tests that need it use a handler that returns the struct. |
:invalid_return | Handler returned a value that is not one of the five handler_result() variants; :cause carries the offending term. |
:not_found | The %Tool{} has handler: nil — the tool is not executable by this executor (typically declared for manual-mode use). |
:encoding_failed | Reserved for encoders; executors do not produce this reason. |