Legion.Store.Postgres (Legion v0.5.0)

View Source

A ready-made Legion.Store backed by Postgres, through your existing Ecto repo.

The generated store uses Ecto for its schema, reads, and partial upserts. Legion depends on ecto_sql and postgrex, so this adapter needs nothing extra in your mix.exs - only an Ecto.Repo backed by Ecto.Adapters.Postgres.

Usage

Define a Postgres-backed Ecto repo and a store module that uses it:

defmodule MyApp.AgentStore do
  use Legion.Store.Postgres, repo: MyApp.Repo
end

Create the table in a migration with Legion.Store.Postgres.Migration:

defmodule MyApp.Repo.Migrations.AddLegionAgents do
  use Ecto.Migration

  def up, do: Legion.Store.Postgres.Migration.up()
  def down, do: Legion.Store.Postgres.Migration.down()
end

The Store migration also creates the ratelimit_metadata column and GIN index used by Legion.RateLimiter.Postgres; no additional migration is needed.

Then start agents with it:

{:ok, pid} = Legion.start_link(AssistantAgent, store: MyApp.AgentStore, agent_id: "user_42")

Options

  • :repo (required) - your Ecto repo module
  • :table - the table name, defaults to "legion_agents"
  • :persistence_frequency - :turn (default) or :step

To persist intermediate eval results and recoverable errors:

defmodule MyApp.AgentStore do
  use Legion.Store.Postgres,
    repo: MyApp.Repo,
    persistence_frequency: :step
end

Agent ids must be valid UTF-8 strings. Snapshots are stored as compressed :erlang.term_to_binary/2 blobs - readable only from Elixir, one row per conversation, upserted on every save. Step snapshots therefore require no additional migration.

save/1 performs partial upserts, so a row carries the conversation state and identity. Omitted payload fields preserve their existing values. The updated_at timestamp is automatically set to the current UTC time on every save. Only agent_id and inserted_at are never updated.

The row's status flips to 'running' when a turn starts and back to 'idle' when it completes. Step writes update only the conversation state, leaving the running status unchanged.

Usage is stored as a jsonb[]: each element contains one complete, string-keyed LLM usage map.

list/1 and get/1 read persisted conversations back from the same table.

The migration also installs a trigger that pg_notifys the table's channel (the table name) with the agent_id on every insert or update, so consumers can follow store changes live without polling.

Summary

Functions

encode_data(payload)