BeamAgent
BeamAgent is a domain-neutral Elixir/OTP runtime for building tool-using AI agents. It provides the model/tool loop, configurable guardrails, deterministic verification, isolated runs, optional PostgreSQL history, and a local dashboard. Your application supplies the model client, tools, and business rules.
The screenshot shows real demo executions: one active run, one successful run, and one verification failure. Token counts and prices are demo values. See how to reproduce it.
Elixir was chosen for the BEAM properties:
- Isolated processes — each agent run is its own
GenServer, so one run crashing (a tool raising, for example) has no effect on any other. - Supervision — every run is started under a
DynamicSupervisor, so cleanup on completion, failure, or crash is automatic and any number of runs can be active concurrently. - OTP behaviours — pluggable pieces (
BeamAgent.LLM.Client,BeamAgent.Tools.Behaviour,BeamAgent.Verifier.Behaviour) are plain Elixir behaviours, not a bespoke plugin system. - Message passing keeps tool execution and the call/reply protocol between a run and its caller clean and easy to reason about.
Installation
Add BeamAgent to your application's dependencies:
{:beam_agent, "~> 0.3.0"}
Run mix deps.get. PostgreSQL persistence additionally requires an explicit
Postgrex dependency, connection setup, and schema migrations as described below.
See the changelog for compatibility changes when upgrading.
Quick start
After adding BeamAgent to your application's dependencies, start iex -S mix.
This example runs locally without provider credentials:
{:ok, run} =
BeamAgent.API.run("Hello from my agent",
llm: {BeamAgent.LLM.Mock, []},
tools: %{echo: BeamAgent.Tools.Echo},
guardrails: [max_iterations: 8, max_execution_time_ms: 30_000],
verification: [required_tools: [:echo]]
)
run.answer #=> "Done!"
run.run_key #=> opaque lookup key
Follow Build an agent for runnable synchronous and
asynchronous examples, a checked tool, an application admission guard, and
custom verifiers. The mock client is for local demonstrations; replace it
with your implementation of BeamAgent.LLM.Client for a provider-backed agent.
{: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. Normal runner outcomes
retain their trace; startup failures, crashes, and hard timeouts cannot
recover the runner's accumulated trace.
To start without blocking for completion:
{:ok, handle} = BeamAgent.API.start("Summarize the report", llm: {MyApp.LLMClient, []})
status = BeamAgent.API.lookup_active(handle.run_key)
result = BeamAgent.API.await(handle)
start/2 returns after the runner is registered. The supervised run keeps
its deadline even if await/1 is delayed. The handle must be awaited once
by the process that started it. lookup_active/1 is local to this node and
returns only a snapshot; a completed run returns :not_found. Every returned
BeamAgent.Run has the same opaque run_key. With an optional trace store,
lookup_completed/1 and list_completed/1
return sanitized completed-run evidence.
For a browser view of live and optionally retained completed runs, enable the local dashboard. It is disabled by default and shows no execution content.
Optional runtime controls are documented separately: retry policies, per-node rate limiting, and per-node circuit breakers. An opt-in structured Logger bridge reports only bounded operational fields. Remote placement runs on trusted BEAM nodes without automatically replaying lost work.
To plug in a real model, implement BeamAgent.LLM.Client. Its chat/2
callback can return a reply or tool call, optionally with provider-reported
input/output token usage. Configure application prices to calculate per-run
cost; see persistent run evidence. 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.
Dashboard in a Phoenix application
After adding BeamAgent as a dependency, enable its dashboard in the host
application's config/dev.exs:
config :beam_agent, :dashboard, port: 4080
Start or restart the Phoenix application with mix phx.server, then open
http://127.0.0.1:4080/. BeamAgent starts the dashboard
under its own supervisor. It runs on a separate HTTP port; it does not add a
Phoenix router route or mount inside Phoenix LiveDashboard. Agent runs must
execute in that application's BEAM node to appear in the active table.
Active runs need no database. For completed history, explicitly set up the optional PostgreSQL trace store:
- Apply the packaged SQL files
priv/sql/001_run_evidence.sqlandpriv/sql/002_model_usage.sql, in order, to the application's database through its normal migration process. They createbeam_agent_runsandbeam_agent_trace_steps, then add usage and cost fields. Installed files can be located withApplication.app_dir(:beam_agent, "priv/sql"). - Supervise a named Postgrex connection or pool in the host application.
- Configure the adapter, connection, and explicit retention period:
config :beam_agent, :trace_store,
adapter: BeamAgent.Trace.Store.Postgres,
retention_ms: :timer.hours(24),
options: [connection: MyApp.BeamAgentPostgres]
The connection example and complete storage contract are in persistent run evidence. A Phoenix Ecto Repo alone does not configure this adapter; it expects a Postgrex connection or pool. Installing the package and starting the application do not run migrations. There is currently no BeamAgent migration generator. Applications own schema rollout, credentials, and retention; stored history contains sanitized fields.
The dashboard binds only to loopback and has no authentication. On a remote server, access it through a secured tunnel to that server's loopback address. The current implementation does not provide a public Phoenix-mounted dashboard. See dashboard details for refresh behavior and limitations.
Lifecycle and supervision
Local BeamAgent.API.run/2 starts one BeamAgent.Runner under
BeamAgent.RunSupervisor and blocks the caller until a result is ready.
BeamAgent.API.start/2 uses a supervised task to own the same lifecycle
while the caller continues. Runners register by run_key in a local OTP
Registry for active lookup.
Runs are restart: :temporary — a finished, failed, or crashed run is a
terminal result to hand back to the caller. Optional operation-level retries
do not restart the whole run.
See the telemetry reference for the supported events and their measurement, metadata, and privacy contract.
Two independent time limits can race, by design:
BeamAgent.Guardrails'MaxExecutionTimecheck runs between steps and tool calls — graceful, produces a normal:max_execution_time_reachedfailure once the runner notices.- A tool call itself can't be preempted mid-flight (e.g. a tool blocking on
slow I/O).
BeamAgent.API.run/2is the backstop: aftermax_execution_time_msplus a grace period it force-terminates the runner and returns a:execution_timeoutresult instead.
Notable features
- Deterministic context compression (
BeamAgent.Context) — no LLM call involved — that keeps the model-facing message count bounded as a run goes on, folding aged-out messages into a running summary rather than dropping them. - Four independent, individually configurable guardrails: max iterations, max context messages, max execution time, max tool calls.
- Post-run verification (
BeamAgent.Verifier) that catches a model claiming success without actually calling the tools it needed to — pluggable viaBeamAgent.Verifier.Behaviourfor domain-specific checks. - Execution traces for normal success and failure paths, with optional sanitized PostgreSQL history. Crashes and hard timeouts cannot recover the full in-memory trace.
- Standard Elixir/OTP primitives, with
:telemetryfor optional runtime observation.
Development
mix deps.get # fetch dependencies
mix compile # compile
mix test # run the full test suite
mix format # format per .formatter.exs
See run configuration and multiple verifiers for application defaults, per-run overrides, validation, and ordered verification.