Behaviour for row-shaped session storage (sessions / messages / snapshots).
This is a companion to ExAthena.Sessions.Store (the append-only event-log
behaviour). While the event-log store is optimised for sequential replay, this
behaviour exposes O(1)-lookup CRUD primitives that sub-ticket 2–4 (resume,
checkpoint, fork, rewind) will call.
Tables
- sessions — one row per session; keyed by
session_id. - messages — one row per message; keyed by
(session_id, seq, message_id)so range scans over a session's history are O(log n). - snapshots — one row per snapshot; keyed by
(session_id, message_id, snapshot_id)so every snapshot associated with a fork-point can be enumerated with a prefix scan.
Stores.ETS additionally maintains an internal :ex_athena_snapshot_index
table (snapshot_id → {session_id, message_id}) that makes get_snapshot/1
an O(1) lookup. This index is an implementation detail of Stores.ETS and is
not part of this behaviour contract.
Row shapes
See the session/0, message/0, and snapshot/0 typespecs below.
All timestamps are ISO 8601 strings produced by
DateTime.utc_now() |> DateTime.to_iso8601().
Summary
Functions
Returns true when mod exports every required SchemaStore callback.
Generate a unique message id (16 random bytes, url-safe base64).
Generate a unique snapshot id (16 random bytes, url-safe base64).
Types
@type message() :: %{ :id => message_id(), :session_id => session_id(), :role => :system | :user | :assistant | :tool, :content => map(), :ts => String.t(), optional(:seq) => integer() }
@type message_id() :: String.t()
@type session() :: %{ :id => session_id(), optional(:parent_id) => session_id() | nil, optional(:title) => String.t() | nil, optional(:model) => String.t() | nil, optional(:created_at) => String.t(), optional(:updated_at) => String.t(), optional(:metadata) => map() }
@type session_id() :: String.t()
@type snapshot() :: %{ id: String.t(), session_id: session_id(), message_id: message_id(), state: map(), created_at: String.t() }
Callbacks
@callback delete_messages_after(session_id(), message_id()) :: :ok
@callback delete_messages_for_session(session_id()) :: :ok
@callback delete_session(session_id()) :: :ok
@callback delete_snapshots_for_session(session_id()) :: :ok
@callback get_session(session_id()) :: {:ok, session()} | {:error, :not_found}
@callback list_messages(session_id()) :: {:ok, [message()]}
@callback list_sessions() :: [session()]
@callback list_snapshots(session_id()) :: {:ok, [snapshot()]}
@callback put_message(message()) :: :ok
@callback put_session(session()) :: :ok
@callback put_snapshot(snapshot()) :: :ok
Functions
Returns true when mod exports every required SchemaStore callback.
Used by ExAthena.Session to opportunistically dual-write to row tables
and to select the SchemaStore read path on resume/2. Stores that only
implement the event-log Store behaviour return false.
@spec new_message_id() :: message_id()
Generate a unique message id (16 random bytes, url-safe base64).
@spec new_snapshot_id() :: String.t()
Generate a unique snapshot id (16 random bytes, url-safe base64).