BeamWeaver Graphs and Agents
BeamWeaver translates LangGraph behavior into Elixir modules, behaviours, and supervised execution. It does not copy Python factories or base classes.
Graphs
Build an immutable graph, compile it, then invoke it:
graph =
BeamWeaver.Graph.new()
|> BeamWeaver.Graph.add_reducer(:messages, fn existing, update ->
existing ++ List.wrap(update)
end)
|> BeamWeaver.Graph.add_node(:answer, fn state ->
%{messages: ["hello #{state.name}"]}
end)
|> BeamWeaver.Graph.add_edge(BeamWeaver.Graph.start(), :answer)
|> BeamWeaver.Graph.add_edge(:answer, BeamWeaver.Graph.end_node())
|> BeamWeaver.Graph.compile!()
{:ok, state} = BeamWeaver.Graph.Compiled.invoke(graph, %{name: "Ada", messages: []})
Compiled graphs support:
-
invoke/3,stream_events/3,batch/3,async_invoke/3, andasync_batch/3 -
reducer-based state merges
-
guarded routes with
when:and dynamic fan-out withBeamWeaver.Graph.Send -
explicit routing with
BeamWeaver.Graph.Command -
interrupts before or after configured nodes
-
checkpoint-backed
get_state/2,get_state_history/3, andupdate_state/4
Event streaming returns an Enumerable of typed %BeamWeaver.Stream.Envelope{} values. Use BeamWeaver.Graph.Compiled.stream_events/3 for graph progress, custom updates, task metadata, and final lifecycle events.
Checkpoints and Memory
Checkpointing and memory are contracts:
-
BeamWeaver.Checkpoint.Saver -
BeamWeaver.Memory.Store
ETS implementations are available for local execution and tests:
checkpointer = BeamWeaver.Checkpoint.ETS.new()
store = BeamWeaver.Memory.ETS.new()
Checkpoint persistence uses Ecto with PostgreSQL or SQLite:
checkpointer = BeamWeaver.Checkpoint.Ecto.new(repo: MyApp.Repo)
Long-term memory's Ecto adapter remains PostgreSQL-backed:
store = BeamWeaver.Memory.Ecto.new(repo: MyApp.Repo)
Create the required tables in application-owned Ecto migrations with BeamWeaver.Migrations.up/1. SQLite migrations currently support the :checkpoint adapter only.
See Persistence for checkpoint state history, updates, pending writes, storage optimization, and adapter scope. See Durable Execution for checkpoint-backed recovery and resume design. See Fault Tolerance for node retries, timeouts, error handlers, and failure policies.
Agents
Agents are user modules:
defmodule MyApp.SupportAgent do
use BeamWeaver.Agent
reducer :messages, fn existing, update -> existing ++ List.wrap(update) end
node :prepare, &__MODULE__.prepare/2
node :reply, &__MODULE__.reply/2
edge BeamWeaver.Graph.start(), :prepare
edge :prepare, :reply
edge :reply, BeamWeaver.Graph.end_node()
def prepare(state, _runtime), do: Map.put_new(state, :messages, [])
def reply(state, runtime) do
greeting = Map.get(runtime.context || %{}, :greeting, "hello")
%{messages: ["#{greeting}, #{state.name}"]}
end
end
Call it directly:
{:ok, state} = MyApp.SupportAgent.invoke(%{name: "Ada"}, context: %{greeting: "hi"})
Or supervise it and call the server:
{:ok, pid} = MyApp.SupportAgent.start_link([])
BeamWeaver.Agent.Server.invoke(pid, %{name: "Ada"})
DAG Composition
Use explicit start/end edges for graph boundaries, deps: for fan-in, and when: for simple constraints:
graph =
BeamWeaver.Graph.new(name: "ResearchPipeline")
|> BeamWeaver.Graph.add_reducer(:reviews, fn left, right -> Map.merge(left, right) end)
|> BeamWeaver.Graph.add_node(:plan, fn state -> %{plan: state.topic} end)
|> BeamWeaver.Graph.add_node(:facts, FactsAgent, deps: :plan)
|> BeamWeaver.Graph.add_node(:market, MarketAgent, deps: :plan)
|> BeamWeaver.Graph.add_node(:facts_check, FactsVerifier,
deps: :facts,
output: [:reviews, :facts]
)
|> BeamWeaver.Graph.add_node(:market_check, MarketVerifier,
deps: :market,
output: [:reviews, :market]
)
|> BeamWeaver.Graph.add_node(:final, SummaryAgent,
deps: [:facts_check, :market_check],
when: %{status: :accepted}
)
|> BeamWeaver.Graph.add_edge(BeamWeaver.Graph.start(), :plan)
|> BeamWeaver.Graph.add_edge(:facts_check, :facts,
when: %{status: :needs_revision},
max_runs: 2
)
|> BeamWeaver.Graph.add_edge(:market_check, :market,
when: %{status: :needs_revision},
max_runs: 2
)
|> BeamWeaver.Graph.add_edge(:final, BeamWeaver.Graph.end_node())
|> BeamWeaver.Graph.compile!()
Testing Standard
Graph and agent tests should check behavior: state transitions, persisted history, stream payloads, interrupts, retries, tool/model errors, and adapter contract coverage. Do not add tests that only assert copied constants, assigned values, or supervisor children whose absence would crash the app.