The public entry points for driving a conversation.
ensure_started/2 is the only addressing point: it returns the live agent
(via Agentix.Addressing) or starts and rehydrates one under
Agentix.ConversationSupervisor. Both new messages and resolutions enter through
it, so a conversation killed mid-flight is revived transparently on the next call.
A conversation is single-in-flight: a send_message/3 while a turn is running
returns {:error, :busy}.
Summary
Types
A user message — plain text or a prebuilt ReqLLM.Message.
Functions
Cancels the in-flight turn from any non-idle state. A no-op if not running.
Returns the running agent for conversation_id, starting one if absent.
Sends a user message to the conversation under scope, starting the agent if
needed (pass config: in opts for a new conversation). Returns :ok once the
turn is accepted, or {:error, :busy} if a turn is already in flight.
Stops the conversation's agent process. A no-op when it isn't running.
Types
@type message() :: String.t() | ReqLLM.Message.t()
A user message — plain text or a prebuilt ReqLLM.Message.
Functions
@spec cancel(String.t()) :: :ok
Cancels the in-flight turn from any non-idle state. A no-op if not running.
Returns once the turn is actually down: the streaming task is terminated, the provider's cancel closure has run so the socket is closed, the partial assistant message is persisted and the conversation is back to idle.
Which agents this can reach depends on Agentix.Addressing. Under the default
:local mode a turn running on another node is not found, and the call
returns :ok having cancelled nothing.
Returns the running agent for conversation_id, starting one if absent.
For a brand-new conversation pass config: %Agentix.Conversation.Config{}. On
revival the config is rebuilt from the persisted settings, so :config may be
omitted; without either, {:error, :unknown_conversation} is returned.
tenant_key: "acme" sets the conversation's owning tenant (see
Agentix.Conversation.Config). Write-once: setting it when the conversation
starts (or revives) unkeyed, and re-passing the same value, is fine; a
conflicting value returns {:error, :tenant_key_conflict}. Keying a currently
running unkeyed conversation is also a conflict — stop the agent first so
the key applies on revival.
@spec send_message(String.t(), message(), Agentix.Scope.t(), keyword()) :: :ok | {:error, term()}
Sends a user message to the conversation under scope, starting the agent if
needed (pass config: in opts for a new conversation). Returns :ok once the
turn is accepted, or {:error, :busy} if a turn is already in flight.
Per-turn opts:
:schema— structured output for this turn only. A NimbleOptions keyword or a JSON Schema map makes the model return a conforming object (surfaced viaAgentix.object/1);falseopts out of the conversation'sresponse_formatdefault for this one turn. Omitting it uses that default (or plain text).
When scope carries a tenant_key and opts do not, the scope's key is used
as the call's tenant_key: — so a host that authenticates the tenant into the
scope gets the write-once isolation check on every send without extra plumbing.
An explicit tenant_key: opt always wins.
@spec stop(String.t()) :: :ok
Stops the conversation's agent process. A no-op when it isn't running.
The conversation itself is not ended — its persisted events remain, and the next
ensure_started/2 revives it (pass config: again if the conversation relies on
non-persisted settings such as api_key, tools, or hooks). Use this to release an
idle agent without losing history.