Agentix.Conversation (Agentix v0.5.2)

Copy Markdown View Source

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

message()

@type message() :: String.t() | ReqLLM.Message.t()

A user message — plain text or a prebuilt ReqLLM.Message.

Functions

cancel(conversation_id)

@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.

ensure_started(conversation_id, opts \\ [])

@spec ensure_started(
  String.t(),
  keyword()
) :: {:ok, pid()} | {:error, term()}

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.

send_message(conversation_id, message, scope, opts \\ [])

@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 via Agentix.object/1); false opts out of the conversation's response_format default 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.

stop(conversation_id)

@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.