Packages
A general-purpose, OTP-based agent harness for Elixir: an LLM tool-use loop with an injectable tool registry, configurable guardrails, pluggable post-run verification, deterministic context compression, a full execution trace, and supervised, concurrent agent runs.
Current section
Files
Jump to
Current section
Files
beam_agent
README.md
README.md
# 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](docs/dashboard.md).
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:
```elixir
{: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](CHANGELOG.md) 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:
```elixir
{: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](docs/building-agents.md) 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:
```elixir
{: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`](docs/persistent-traces.md)
return sanitized completed-run evidence.
For a browser view of live and optionally retained completed runs, enable the
[local dashboard](docs/dashboard.md). It is disabled by default and shows no
execution content.
Optional runtime controls are documented separately: [retry
policies](docs/retries.md), [per-node rate limiting](docs/rate-limiting.md),
and [per-node circuit breakers](docs/circuit-breakers.md). An opt-in
[structured Logger bridge](docs/structured-logging.md) reports only bounded
operational fields. [Remote placement](docs/distributed-execution.md) 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](docs/persistent-traces.md). 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`:
```elixir
config :beam_agent, :dashboard, port: 4080
```
Start or restart the Phoenix application with `mix phx.server`, then open
[http://127.0.0.1:4080/](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:
1. Apply the packaged SQL files `priv/sql/001_run_evidence.sql` and
`priv/sql/002_model_usage.sql`, in order, to the application's database
through its normal migration process. They create `beam_agent_runs` and
`beam_agent_trace_steps`, then add usage and cost fields. Installed files
can be located with `Application.app_dir(:beam_agent, "priv/sql")`.
2. Supervise a named Postgrex connection or pool in the host application.
3. Configure the adapter, connection, and explicit retention period:
```elixir
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](docs/persistent-traces.md). 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](docs/dashboard.md) 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](docs/telemetry.md) for the supported events and
their measurement, metadata, and privacy contract.
Two independent time limits can race, by design:
- `BeamAgent.Guardrails`' `MaxExecutionTime` check runs *between* steps and
tool calls — graceful, produces a normal `:max_execution_time_reached`
failure 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/2` is the backstop: after
`max_execution_time_ms` plus a grace period it force-terminates the
runner and returns a `:execution_timeout` result 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 via `BeamAgent.Verifier.Behaviour` for 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 `:telemetry` for optional runtime
observation.
## Development
```bash
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](docs/configuration.md) for
application defaults, per-run overrides, validation, and ordered verification.