Agentix requires Elixir 1.20+. It installs in three tiers: a host adds only the dependencies its use case needs — the LiveView and durable-persistence layers are optional: true and compile-gated, so an unused layer is neither fetched nor compiled. Two runnable example apps in this repo prove each end of the range:

Tier 1 — Headless / API-only (no LiveView, no database)

The core runtime needs no web framework and no database. The Agentix OTP application boots its own registry, task supervisor, ETS persistence, and Phoenix.PubSub (the live-event backbone).

# mix.exs
{:agentix, "~> 0.1"}      # pulls req_llm + phoenix_pubsub (both light)

No configuration is required to start — ETS persistence and an Agentix.PubSub server start automatically. Override only what differs from the defaults (see the config table below), e.g. to reuse a Phoenix.PubSub your app already supervises:

# config/config.exs
config :agentix, pubsub: MyApp.PubSub

Configure a model provider

Agentix talks to LLMs through ReqLLM. Choose a model with a "<provider>:<model>" string (per conversation, via Agentix.Conversation.Config), and give ReqLLM the provider credential — an ANTHROPIC_API_KEY-style env var (.env is loaded via dotenvy), inline config, or ReqLLM.put_key/2:

# config/runtime.exs  — or just export ANTHROPIC_API_KEY
config :req_llm, anthropic_api_key: System.get_env("ANTHROPIC_API_KEY")

# starting a conversation with that provider:
Agentix.Conversation.ensure_started("conv-1",
  config: Agentix.Conversation.Config.new(model: "anthropic:claude-haiku-4-5"))

Drive conversations through Agentix.Conversation.* and Agentix.resolve/4, and stream output to clients off the live-event union over any transport you write (SSE, channel, JSON polling) by subscribing to Agentix.Events.Publisher.topic(conversation_id).

In this tier Agentix.Chat, the Ecto adapter, and the Oban worker are never compiledCode.ensure_loaded?(Agentix.Chat) == false. See examples/agentix_api for a working headless conversation (streaming + a :server tool) on ETS.

Tier 2 — + LiveView

Add phoenix_live_view (most Phoenix apps already have it) to light up the rendering layer:

{:phoenix_live_view, "~> 1.2"}

use Agentix.Chat in a host LiveView installs an on_mount hook that projects the conversation onto assigns (:messages, :streaming_message, :state, :streaming?, :in_flight_tools, :pending) and imports the verbs (send_message/3, resolve/4, cancel/1, load_older/1). Render the assigns yourself, or:

mix agentix.gen.components lib/my_app_web/components

copies an ownable set of default components (message_list, composer, tool, pending, …) into your project. The streaming text and composer hooks ship at priv/static/agentix_stream_hook.js.

Tier 3 — + Durable persistence

Add the host-provided Ecto/Oban stack to make conversations and suspension expiry survive a restart:

{:ecto_sql, "~> 3.14"}, {:postgrex, "~> 0.22"}, {:oban, "~> 2.20"}
# config/config.exs
config :agentix, persistence: {Agentix.Persistence.Ecto, repo: MyApp.Repo}

config :my_app, Oban, repo: MyApp.Repo, queues: [agentix_expiry: 10]

You own the Repo and the Oban instance. Oban needs its own migration, and it must run before the Agentix migration (the expiry worker references Oban's jobs table) — give it an earlier timestamp:

# priv/repo/migrations/<EARLIER_TIMESTAMP>_add_oban_jobs.exs
defmodule MyApp.Repo.Migrations.AddObanJobs do
  use Ecto.Migration

  def up, do: Oban.Migration.up()
  def down, do: Oban.Migration.down(version: 1)
end

Then generate the Agentix tables migration and run both:

mix agentix.gen.migration         # writes priv/repo/migrations/*_create_agentix_tables.exs
mix ecto.migrate

The template creates the tables once, at install time — it is not re-run on upgrades. If you ran it on a version before tenant_key existed (0.3.x and earlier), add the column with your own migration (the exact snippet is in the CHANGELOG's 0.4.0 Breaking section).

If you ran it before 0.5.0, apply upgrade_agentix_model_calls.exs from the same directory — a fresh install already has those columns and must not run it.

Durable expiry is scheduled as Oban jobs. Recording model calls is off by default; turn it on with config :agentix, model_call_log: :records for cost and observability, or :full to also store each rendered prompt for replay. See examples/agentix_demo for the full wiring, including a Postgres-backed Phoenix.LiveViewTest that drives a HITL elicitation end-to-end.

Config keys

Application config — config :agentix, … (all tiers):

keymeaning
:persistenceadapter module or {module, opts} (default Agentix.Persistence.ETS)
:notifierlive-event notifier (default Agentix.Notifier.PubSub; or …None)
:pubsubthe Phoenix.PubSub server name (default Agentix.PubSub)
:providerLLM provider module (default Agentix.Provider.ReqLLM)
:tokenizertokenizer module (default Agentix.Tokenizer.Heuristic)
:addressing:local (default, node-local Registry) or :global (cluster-wide)
:model_call_log:off (default), :records (call + usage + cost), :full (+ prompt)
:auditdeprecatedtrue means model_call_log: :full

Per-conversation options are passed to Agentix.Conversation.Config.new/1 (not application config): :model, :system_prompt, :tools, :hooks, :working_budget (default 30k tokens), :default_timeout (suspension expiry, default 300_000 ms), and more — see Agentix.Conversation.Config.