Raxol.Agent.Conversation.Store behaviour (Raxol Agent v2.6.0)

Copy Markdown View Source

Behaviour for Raxol.Agent.Conversation.Item persistence.

Mirrors Raxol.Agent.ThreadLog's pluggable-adapter shape, but for the conversation item log: the store owns durability and assigns each item a stable seq/id; live fan-out and the snapshot/tail partition live in Raxol.Agent.Conversation.Log.

One adapter ships today: Raxol.Agent.Conversation.Store.ETS (in-process, ordered_set on {conversation_id, seq}). A durable adapter (DETS/Postgrex) is a deliberate follow-up, matching ThreadLog's Ets/Postgrex split.

Append is the only writer

append/3 assigns consecutive seq values atomically per conversation and returns the materialized items. Items are never updated or deleted; the log is append-only.

Cursor pagination

list_items/3 accepts :after/:before sequence cursors (both exclusive), :limit, :order, and a :type filter. A reconnecting client pages forward by passing the last seq it has as :after.

Summary

Types

A new item to append; the store assigns id/seq/created_at.

Functions

Dispatch append to the adapter.

Dispatch get_conversation to the adapter.

Dispatch list_conversations to the adapter.

Dispatch list_items to the adapter.

Normalize a store opt into {module, config} form.

Types

config()

@type config() :: map()

conversation_id()

@type conversation_id() :: binary()

list_opts()

@type list_opts() :: keyword()

Options for list_items/3:

  • :after -- return items with seq > this value (exclusive cursor).
  • :before -- return items with seq < this value (exclusive).
  • :limit -- max items, or :all. Default 1000.
  • :order -- :asc (default) or :desc.
  • :type -- a type atom or list of types to filter by.

new_item()

@type new_item() :: %{
  :type => Raxol.Agent.Conversation.Item.type(),
  optional(:data) => map(),
  optional(:status) => Raxol.Agent.Conversation.Item.status(),
  optional(:response_id) => binary() | nil,
  optional(:created_by) => term()
}

A new item to append; the store assigns id/seq/created_at.

Callbacks

append(config, conversation_id, list)

@callback append(config(), conversation_id(), [new_item()]) ::
  {:ok, [Raxol.Agent.Conversation.Item.t()]}

get_conversation(config, conversation_id)

@callback get_conversation(config(), conversation_id()) ::
  {:ok, map()} | {:error, :not_found}

list_conversations(config)

@callback list_conversations(config()) :: {:ok, [conversation_id()]}

list_items(config, conversation_id, list_opts)

@callback list_items(config(), conversation_id(), list_opts()) ::
  {:ok, [Raxol.Agent.Conversation.Item.t()]}

Functions

append(arg, conversation_id, items)

@spec append({module(), config()}, conversation_id(), [new_item()]) ::
  {:ok, [Raxol.Agent.Conversation.Item.t()]}

Dispatch append to the adapter.

get_conversation(arg, conversation_id)

@spec get_conversation(
  {module(), config()},
  conversation_id()
) :: {:ok, map()} | {:error, :not_found}

Dispatch get_conversation to the adapter.

list_conversations(arg)

@spec list_conversations({module(), config()}) :: {:ok, [conversation_id()]}

Dispatch list_conversations to the adapter.

list_items(arg, conversation_id, opts \\ [])

@spec list_items({module(), config()}, conversation_id(), list_opts()) ::
  {:ok, [Raxol.Agent.Conversation.Item.t()]}

Dispatch list_items to the adapter.

normalize(module)

@spec normalize(module() | {module(), config()}) :: {module(), config()}

Normalize a store opt into {module, config} form.