Packages
nous
0.15.8
0.17.0
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.17
0.12.16
0.12.15
0.12.14
0.12.13
0.12.12
0.12.11
0.12.9
0.12.7
0.12.6
0.12.5
0.12.3
0.12.2
0.12.0
0.11.3
0.11.0
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.5.0
AI agent framework for Elixir with multi-provider LLM support
Current section
Files
Jump to
Current section
Files
lib/nous/persistence/ets.ex
defmodule Nous.Persistence.ETS do
@moduledoc """
ETS-based persistence backend.
Stores serialized context data in a named ETS table. The table is owned
by a dedicated GenServer started under the Nous application supervisor,
so the table outlives transient callers - previously the table died
with whichever process happened to call save/load first.
Data does not survive node restarts. Useful for development, testing,
and short-lived sessions.
## Usage
# In AgentServer config
AgentServer.start_link(
session_id: "session_123",
agent_config: %{model: "openai:gpt-4", instructions: "Be helpful"},
persistence: Nous.Persistence.ETS
)
"""
@behaviour Nous.Persistence
@table :nous_persistence
defmodule TableOwner do
@moduledoc false
use GenServer
def start_link(_opts) do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
@impl true
def init(:ok) do
table_name = :nous_persistence
table =
case :ets.whereis(table_name) do
:undefined ->
:ets.new(table_name, [:named_table, :set, :public, read_concurrency: true])
_ref ->
table_name
end
{:ok, %{table: table}}
end
end
@doc false
def child_spec(_opts) do
%{id: __MODULE__, start: {TableOwner, :start_link, [[]]}, type: :worker}
end
@impl true
def save(session_id, data) when is_binary(session_id) and is_map(data) do
ensure_table()
try do
true = :ets.insert(@table, {session_id, data})
:ok
rescue
e -> {:error, {:ets_insert_failed, Exception.message(e)}}
end
end
@impl true
def load(session_id) when is_binary(session_id) do
ensure_table()
case :ets.lookup(@table, session_id) do
[{^session_id, data}] -> {:ok, data}
[] -> {:error, :not_found}
end
end
@impl true
def delete(session_id) when is_binary(session_id) do
ensure_table()
:ets.delete(@table, session_id)
:ok
end
@impl true
def list do
ensure_table()
keys = :ets.foldl(fn {key, _val}, acc -> [key | acc] end, [], @table)
{:ok, keys}
end
# Fallback for callers used before the supervisor started the owner
# (mainly ad-hoc tests). Production code goes through TableOwner.
defp ensure_table do
case :ets.whereis(@table) do
:undefined ->
try do
:ets.new(@table, [:named_table, :set, :public, read_concurrency: true])
rescue
ArgumentError ->
# Another process created the table between whereis and new
:ok
end
_ref ->
:ok
end
end
end