Agent-loop kernel. Dispatches to a ExAthena.Loop.Mode implementation
and handles everything around it: caps, budget, hooks, counters, events,
and termination accounting.
Public entry point: run/2, returning {:ok, %ExAthena.Result{}} | {:error, reason}.
v0.3 breaking change
The return shape is now an ExAthena.Result struct instead of a loose
map. Every termination — success or error — produces a Result with the
typed finish_reason (see ExAthena.Loop.Terminations for the
enumeration). Callers can dispatch on Result.category/1
(:success | :retryable | :capacity | :fatal) instead of pattern-matching
individual atoms.
Options
:provider— required. Provider atom (:ollama,:openai,:claude,:mock,:req_llm) or a module implementingExAthena.Provider.:model,:system_prompt,:messages,:temperature,:top_p,:max_tokens,:stop,:timeout_ms,:tool_choice,:response_format,:provider_opts,:metadata— forwarded toExAthena.Request.new/2.:tools— list of modules implementingExAthena.Toolor:all(default — all builtins).nilfalls back toconfig :ex_athena, tools: ….:mode— atom (:react,:plan_and_solve,:reflexion) or module. Defaults to:react.:cwd,:phase,:assigns— threaded into every tool'sExAthena.ToolContext.:allowed_tools,:disallowed_tools,:can_use_tool— seeExAthena.Permissions.:hooks— seeExAthena.Hooks.:max_iterations(default 25) — hard iteration cap.:max_consecutive_mistakes(default 3) — counter threshold at which the loop terminates with:error_consecutive_mistakes.:max_budget_usd— optional float. Trips:error_max_budget_usdwhen cumulative cost crosses it.:tool_timeout_ms(default 60_000) — per-call timeout for parallel tool execution.:max_concurrency(default 4) —Task.async_streamconcurrency cap for parallel-safe tool calls in a single iteration.:on_event—(ExAthena.Loop.Events.t -> term)callback for streaming. Events are flat tuples ({:content, text},{:tool_call, tc},{:tool_result, tr},{:iteration, n},{:usage, u},{:error, reason},{:done, Result}).:session_id— stable identifier for this run. Threaded into theToolContextand used by hooks / storage / sidechain transcripts. Auto-generated when omitted.:parent_session_id— when this run is a subagent of another run, the parent'ssession_id.nilfor top-level runs. Used byExAthena.Sessions.Stores.Jsonl(PR5) to write subagent sidechains and byExAthena.Agents(PR4) to scope worktrees.:memory—:auto(default — discoverAGENTS.md/CLAUDE.mdfromcwdand~/.config/ex_athena/),false(skip memory entirely), or an explicit list ofMessage.t()to prepend.:skills—:auto(default — discover skills from<cwd>/.exathena/skills/and~/.config/ex_athena/skills/),false(skip), or an explicit%{name => %Skill{}}map.:preload_skills— list of skill names whose bodies should be activated up-front (skips the[skill: name]sentinel round-trip).
Returns
{:ok, Result.t()}— ran to termination (possibly with an error subtype like:error_max_turns; the Result contains the classification).{:error, reason}— unexpected failure before the loop started (e.g. unknown provider, bad tool module).
Summary
Functions
@spec run( String.t() | nil, keyword() ) :: {:ok, ExAthena.Result.t()} | {:error, term()}