Public API for executing agents.
Lifecycle and supervision
run/2 starts one BeamAgent.Runner under BeamAgent.RunSupervisor (a
DynamicSupervisor) and blocks the calling process until it gets a
result. Each run is an independent, unregistered GenServer — any number
of runs can be in flight concurrently, and one crashing (e.g. a tool
raising) has no effect on any other; it's caught here via
Process.monitor/1 and turned into a BeamAgent.Run.crash/2 result
rather than propagating to the caller.
There are two layers of time enforcement, and they can race:
BeamAgent.Guardrails'MaxExecutionTimecheck runs between steps and tool calls — it's graceful, and produces a normal{:max_execution_time_reached, ...}failure once the runner notices.- A tool call itself can't be preempted mid-execution (e.g. a tool that
blocks on I/O far longer than the guardrail's limit).
run/2is the backstop for that: it waitsmax_execution_time_ms + @timeout_grace_msand, if the runner still hasn't replied, force-terminates it viaDynamicSupervisor.terminate_child/2and returns aBeamAgent.Run.timeout/2result instead.
The grace period exists so the graceful path normally wins the race —
see the @timeout_grace_ms doc below.
Run telemetry
run/2 emits [:beam_agent, :run, :start] and then :stop on a normal
return, or :exception when an error, throw, or exit escapes the API.
Handled failures, including runner crashes and timeouts, emit :stop.
External process termination can prevent a terminal event from being emitted.
All events carry the same opaque :run_id and :telemetry_span_context.
Start measurements are :system_time and :monotonic_time. Terminal
measurements are :duration and :monotonic_time, in native time units.
Duration covers the API boundary, including verification and timeout cleanup,
and is measured independently of Run.duration_ms. Stop events also include
integer :iterations and :tool_calls measurements when available.
Stop metadata includes :outcome (:ok or :error), :execution_status
(:finished, :failed, or :unknown), and :verification_status (:passed,
:failed, :not_run, or :unknown). Failures include an :error_type:
:startup_failed, :verification_failed, :execution_failed,
:runner_crashed, :execution_timeout, :unknown_tool,
:max_iterations_reached, :max_tool_calls_reached,
:max_execution_time_reached, :max_context_messages_exceeded,
:context_limit_too_small, or :unexpected_result.
Exception metadata contains only the correlation fields, :error_type set
to :exception, and :kind (:error, :throw, or :exit). The original
exception is re-raised unchanged. Execution content, raw errors, and
stacktraces are never included in telemetry.
Summary
Functions
Runs goal to completion (or failure) and returns {:ok, run} or
{:error, run}, where run is always a BeamAgent.Run.t().
Functions
@spec run( String.t(), keyword() ) :: {:ok, BeamAgent.Run.t()} | {:error, BeamAgent.Run.t()}
Runs goal to completion (or failure) and returns {:ok, run} or
{:error, run}, where run is always a BeamAgent.Run.t().
{:ok, run} only when the runner finished and verification passed;
every other outcome (a guardrail tripped, an unknown tool was called, the
hard timeout fired, the runner crashed, or verification failed a
plausible-looking finish) comes back as {:error, run} with
run.error/run.verification_error set accordingly.
Options
:llm(required) —{module, opts}, wheremoduleimplementsBeamAgent.LLM.Client.:tools—%{atom() => module}of tools available to this run, each module implementingBeamAgent.Tools.Behaviour. Defaults to%{}; a tool call for a name not in this map fails with:unknown_tool.:guardrails— keyword opts consumed byBeamAgent.Guardrails(see its submodules for individual keys/defaults, e.g.:max_execution_time_ms,:max_iterations,:max_context_messages,:max_tool_calls).:verification— keyword opts consumed byBeamAgent.Verifier, e.g.:required_tools, or:moduleto swap in a customBeamAgent.Verifier.Behaviourimplementation.