BeamAgent

A general-purpose Elixir agent harness — the LLM tool-use loop, guardrails, and verification other developers can build agents on top of (a shopping agent, a research agent, a coding agent, a workflow or internal-automation agent, etc.). Named BeamAgent rather than Agent to avoid colliding with Elixir's own stdlib Agent module.

Elixir was chosen for the BEAM properties:

Usage

BeamAgent.API.run(
"Find the cheapest supermarket for a weekly shop",
llm: {MyApp.LLMClient, model: "..."},
tools: %{price_check: MyApp.Tools.PriceCheck},
guardrails: [max_iterations: 8, max_execution_time_ms: 30_000],
verification: [required_tools: [:price_check]]
)
#=> {:ok, %BeamAgent.Run{}} | {:error, %BeamAgent.Run{}}

{:ok, run} only when the run finished and verification passed; everything else — a guardrail tripped, an unknown tool, a hard timeout, a runner crash, or verification failing a plausible-looking answer — comes back as {:error, run} with the reason on run. Every run.trace entry is retained either way, so a failure is fully inspectable, not just a bare error atom.

To plug in a real model, implement BeamAgent.LLM.Client (chat/2 :: {:reply, text} | {:tool_call, atom, map}). To add a tool, implement BeamAgent.Tools.Behaviour and pass it in the :tools map — tools aren't hardcoded into the harness, each run supplies its own.

Lifecycle and supervision

BeamAgent.API.run/2 starts one BeamAgent.Runner under BeamAgent.RunSupervisor and blocks the caller until a result is ready. Runs are restart: :temporary — a finished, failed, or crashed run is a terminal result to hand back to the caller, not a transient failure to retry, so there is no automatic restart.

Two independent time limits can race, by design:

Notable features

Development

mix deps.get # fetch deps (currently none declared)
mix compile # compile
mix test # run the full test suite
mix format # format per .formatter.exs