The headless LiveView layer: conversation state and verbs, no markup.
use Agentix.Chat in a host LiveView installs an on_mount hook that routes a
conversation's live events into assigns (via an internal projection) and imports
the verbs below. The host owns the template — it renders its own HEEx against the
projected assigns (:messages, :streaming_message, :state, :streaming?,
:in_flight_tools, :pending); default components are a separate, optional layer.
defmodule MyAppWeb.ChatLive do
use MyAppWeb, :live_view
use Agentix.Chat
def mount(%{"id" => id}, _session, socket) do
{:ok, attach_conversation(socket, id)}
end
def handle_event("send", %{"text" => text}, socket) do
{:noreply, send_message(socket, text)}
end
endThe in-progress assistant text is streamed to a JS hook (shipped at
priv/static/agentix_stream_hook.js) rather than held in an assign, so a growing
string is never re-diffed. This module is defined only when phoenix_live_view is
available; headless/API consumers omit the dependency and never load it.
Summary
Functions
Binds a conversation to the socket: subscribe, read the snapshot, seed the assigns
and the message stream. Call it from the host's mount/3 (or handle_params/3)
once the conversation id is known.
Cancels the in-flight turn (a no-op when idle).
Loads one older page of history into the :messages stream (prepended). Gate the
host's affordance on the :agentix_more? assign; a no-op once the log start is reached.
on_mount hook (installed by use Agentix.Chat): attaches a handle_info hook
that applies the conversation's live events to the assigns and lets every other
message fall through to the host LiveView.
Resolves a pending tool call — an approval (:approve / %{approved: bool}) or an
elicitation answer. Pass :scope (an Agentix.Scope) in opts to attribute the
resolution to the acting user; it is threaded into a gated :server tool's callback as
the approver. Mirrors send_message/3; defaults to an anonymous scope.
Sends a user message through the conversation and optimistically streams it into
:messages. Pass :scope (an Agentix.Scope) in opts to attribute the message.
Functions
@spec attach_conversation(Phoenix.LiveView.Socket.t(), String.t(), keyword()) :: Phoenix.LiveView.Socket.t()
Binds a conversation to the socket: subscribe, read the snapshot, seed the assigns
and the message stream. Call it from the host's mount/3 (or handle_params/3)
once the conversation id is known.
The conversation should be started with its config beforehand (e.g.
Agentix.Conversation.ensure_started/2) or be revivable from persistence — attaching
only reads and tails it, it never creates one.
@spec cancel(Phoenix.LiveView.Socket.t()) :: Phoenix.LiveView.Socket.t()
Cancels the in-flight turn (a no-op when idle).
@spec load_older(Phoenix.LiveView.Socket.t()) :: Phoenix.LiveView.Socket.t()
Loads one older page of history into the :messages stream (prepended). Gate the
host's affordance on the :agentix_more? assign; a no-op once the log start is reached.
@spec on_mount(:default, map(), map(), Phoenix.LiveView.Socket.t()) :: {:cont, Phoenix.LiveView.Socket.t()}
on_mount hook (installed by use Agentix.Chat): attaches a handle_info hook
that applies the conversation's live events to the assigns and lets every other
message fall through to the host LiveView.
@spec resolve(Phoenix.LiveView.Socket.t(), String.t(), term(), keyword()) :: Phoenix.LiveView.Socket.t()
Resolves a pending tool call — an approval (:approve / %{approved: bool}) or an
elicitation answer. Pass :scope (an Agentix.Scope) in opts to attribute the
resolution to the acting user; it is threaded into a gated :server tool's callback as
the approver. Mirrors send_message/3; defaults to an anonymous scope.
@spec send_message( Phoenix.LiveView.Socket.t(), Agentix.Conversation.message(), keyword() ) :: Phoenix.LiveView.Socket.t()
Sends a user message through the conversation and optimistically streams it into
:messages. Pass :scope (an Agentix.Scope) in opts to attribute the message.
On success the :agentix_error assign is cleared; on failure (e.g. :busy for an
in-flight turn, or :unknown_conversation for one that was never started) the
message is not inserted and :agentix_error is set to the reason so the host can
surface it — the input is never dropped silently.