Pluggable control-flow strategy for the agent loop.
Modes decide what to do each turn. The kernel handles everything else — tool dispatch, permissions, hooks, events, accounting. Modes sit on top and shape the iteration: ReAct is "infer → tool → loop", Plan-and-Solve is "plan once → execute many", Reflexion adds a self-critique pass.
Writing a mode
defmodule MyMode do
@behaviour ExAthena.Loop.Mode
@impl true
def init(state), do: {:ok, state}
@impl true
def iterate(state) do
# Run inference, handle tool calls, update state, decide continue/halt.
# Return {:continue, state} to keep looping, {:halt, state} to stop.
{:continue, state}
end
endThe builtin ExAthena.Modes.ReAct is a reference implementation.
Atom shortcuts
:react, :plan_and_solve, :reflexion resolve to the builtin modules.
Any other atom or a module reference is used verbatim.
Summary
Callbacks
Called once before the first iteration. Use to prime mode-specific state.
Drive one iteration. Return one of
Functions
Resolve an atom shortcut or module to the Mode module.
Callbacks
@callback init(ExAthena.Loop.State.t()) :: {:ok, ExAthena.Loop.State.t()} | {:error, term()}
Called once before the first iteration. Use to prime mode-specific state.
@callback iterate(ExAthena.Loop.State.t()) :: {:continue, ExAthena.Loop.State.t()} | {:halt, ExAthena.Loop.State.t()} | {:error, term()}
Drive one iteration. Return one of:
{:continue, State.t()}— keep looping (kernel checks caps + budget).{:halt, State.t()}— stop looping; the kernel produces aResultfrom the terminal state'sfinish_reason(which the mode should set viaExAthena.Loop.set_finish_reason/2before returning).{:error, reason}— abort with an unrecoverable error. The kernel wraps this in:error_during_execution.