ExAthena.Sessions.SchemaStore behaviour (ExAthena v0.7.1)

Copy Markdown View Source

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

message()

@type message() :: %{
  :id => message_id(),
  :session_id => session_id(),
  :role => :system | :user | :assistant | :tool,
  :content => map(),
  :ts => String.t(),
  optional(:seq) => integer()
}

message_id()

@type message_id() :: String.t()

session()

@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()
}

session_id()

@type session_id() :: String.t()

snapshot()

@type snapshot() :: %{
  id: String.t(),
  session_id: session_id(),
  message_id: message_id(),
  state: map(),
  created_at: String.t()
}

Callbacks

delete_messages_after(session_id, message_id)

@callback delete_messages_after(session_id(), message_id()) :: :ok

delete_messages_for_session(session_id)

(optional)
@callback delete_messages_for_session(session_id()) :: :ok

delete_session(session_id)

@callback delete_session(session_id()) :: :ok

delete_snapshots_for_session(session_id)

(optional)
@callback delete_snapshots_for_session(session_id()) :: :ok

get_session(session_id)

@callback get_session(session_id()) :: {:ok, session()} | {:error, :not_found}

get_snapshot(t)

@callback get_snapshot(String.t()) :: {:ok, snapshot()} | {:error, :not_found}

list_messages(session_id)

@callback list_messages(session_id()) :: {:ok, [message()]}

list_sessions()

@callback list_sessions() :: [session()]

list_snapshots(session_id)

@callback list_snapshots(session_id()) :: {:ok, [snapshot()]}

put_message(message)

@callback put_message(message()) :: :ok

put_session(session)

@callback put_session(session()) :: :ok

put_snapshot(snapshot)

@callback put_snapshot(snapshot()) :: :ok

Functions

implements?(mod)

@spec implements?(module()) :: boolean()

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.

new_message_id()

@spec new_message_id() :: message_id()

Generate a unique message id (16 random bytes, url-safe base64).

new_snapshot_id()

@spec new_snapshot_id() :: String.t()

Generate a unique snapshot id (16 random bytes, url-safe base64).