Parallel tool-call dispatcher.
When a model emits multiple tool calls in a single response, the kernel groups them:
- Read-only calls (tool's
parallel_safe?/0returnstrue) run concurrently viaTask.async_stream/3. - Mutating calls run serially in the order the model emitted them, to preserve deterministic side-effect ordering.
Regardless of execution order, results are returned in the same order as the input tool calls — the model always sees results aligned with its calls.
Summary
Functions
Emit tool-call / tool-result events and update counters.
Run the pre-tool gate (permissions + PreToolUse hooks) for a single call.
Execute a list of tool calls.
Functions
@spec emit_events( map(), ExAthena.Messages.ToolCall.t(), ExAthena.Messages.Message.t() ) :: :ok
Emit tool-call / tool-result events and update counters.
@spec pre_tool_gate(ExAthena.Messages.ToolCall.t(), map()) :: :allow | {:deny, term()} | {:halt, term()}
Run the pre-tool gate (permissions + PreToolUse hooks) for a single call.
Returns :allow, {:deny, reason}, or {:halt, reason}.
@spec run([ExAthena.Messages.ToolCall.t()], map(), (ExAthena.Messages.ToolCall.t(), map() -> {term(), map()})) :: {:ok, [term()], map()} | {:halt, term(), map()}
Execute a list of tool calls.
runner_fn is (ToolCall.t(), state -> {result, updated_state}). The
result is whatever the single-call runner returns (typically a
tool-result message tuple or a :halt tuple). Updated state threads the
phase transitions, mistake counter, and budget.
Returns {:ok, results_in_order, final_state} or
{:halt, reason, final_state} when any call returns a halt.
Ordering guarantee: results_in_order is ordered the same as the input
calls, even though parallel-safe calls may execute out-of-order.