Agent framework

lxai

Agents, OTP-idiomatic

Agent-first framework for Lx, inspired by Agno. An Agent is a supervised gen_server (one session per process) with tools, memory and storage; a Team is a leader process with its own sub-supervisor for its members. Plug any provider (OpenAI, ZAI) and run sync, streaming or fully async.

v0.1.0 ~60% complete

Highlights

  • One session per process (gen_server)
  • Pluggable models (OpenAI, ZAI)
  • Tools executed in parallel
  • Session storage & recovery
  • Per-user memory
  • Multi-agent teams

Features

Agents

Build with lxai:agent(model, tools, opts). Each agent is a gen_server holding a session; start_link, run, get_state, stop — supervised and restartable.

Models

A model is a handle %{module, state} with bind_tools / invoke / invoke_stream callbacks. lxai_openai and lxai_zai implement it; a stub is included for tests.

Tools

Tool structs dispatched via erlang:apply. Tool calls from the model run in parallel (one worker each), preserving order; results flow back into the conversation.

Sessions & storage

Sessions serialize to a storage behavior (lxai_in_memory over ETS). On restart the session is restored; agents with storage use :transient restart for recovery.

Memory

Per-user memories are stored (ETS) and injected into the system prompt, so agents remember preferences across sessions.

Teams

Coordinate multiple agents with route / coordinate / collaborate modes, a leader, a router MFA and max_rounds. Delegate to a member by role; members can even be other teams.

Examples

Build & run an agent

require "lxai"
require "lxai_openai"
require "@lx/io"

model = lxai_openai:model("gpt-4o-mini")

agent = lxai:agent(model, [], %{
  instructions: "You are a concise assistant.",
  storage: :lxai_in_memory,
  session_id: "s1"
})

{:ok, pid} = lxai:start_link(agent)
resp = lxai:run(pid, "What is the BEAM?", nil)
io:puts(resp.content)

Agent with a tool

require "lxai"
require "lxai_tools"

tool = %lxai_tools:Tool{
  name: "lookup_user",
  description: "Find a user by name",
  parameters: %{name: :string},
  module: :my_app,
  function: :lookup_user
}

agent = lxai:agent(model, [tool], %{storage: :lxai_in_memory})

Streaming tokens

ref = lxai:run_stream(pid, "tell me a story", %lxai:RunConfig{}, self())
receive do
  {:lxai_event, ref, {:content, delta}} -> io:puts(delta)
  {:lxai_event, ref, {:final, resp}}    -> io:puts("done")
end

Team: coordinate

members = [
  {:researcher, lxai:agent(model_a, [], %{instructions: "Research"})},
  {:writer,     lxai:agent(model_b, [], %{instructions: "Write"})}
]
team = lxai:team(:coordinate, members, leader_agent)
{:ok, tpid} = lxai:start_team(team)
resp = lxai:team_run(tpid, "Draft a post about Lx")

Install

# project.yml
dependencies:
  lxai:
    path: ../../lx_libs/lxai
  lxai_openai:
    path: ../../lx_libs/lxai_openai
  # or lxai_zai for the ZAI provider