Sagents.Session (Sagents v0.8.0-rc.5)

Copy Markdown

Session lifecycle for conversation-centric agents. Owns the procedural work of: router consult, factory invocation, state seeding, supervisor config, subscriber wiring.

Configuration

Session functions take a config map with the following keys:

  • :factory_router - module implementing Sagents.FactoryRouter.
  • :agent_persistence - module implementing Sagents.AgentPersistence.
  • :display_message_persistence - module implementing Sagents.DisplayMessagePersistence.
  • :pubsub - {Phoenix.PubSub, name} tuple.
  • :presence_module - Phoenix Presence module.
  • :inactivity_timeout - milliseconds before agent stops on idle.
  • :agent_id_fun - 1-arity function mapping conversation_idagent_id string.

Factory contract

Factories implement Sagents.Factory.create_agent/2 taking (agent_id, config) and returning {:ok, agent, session_opts}. The router supplies config verbatim — by convention a typed struct from a paired *Config module. The library recognizes these keys in session_opts:

  • :fresh_state_attrs - map seeded into Sagents.State.new!/1 on fresh state only (ignored when persisted state is restored). Use this to pre-populate todos, scratchpad data, or any other initial state. For seeding todos:

    {:ok, agent, fresh_state_attrs: %{todos: my_todos}}
  • :supervisor_opts - keyword list merged into the supervisor_config passed to Sagents.AgentsDynamicSupervisor.start_agent_sync/1. Use this for per-factory AgentSupervisor / AgentServer configuration such as :message_preprocessor or any other supervisor-level opt. The library does NOT inspect the contents — keys are forwarded verbatim, so factories can pass anything the supervisor accepts (including future additions) without requiring a library change.

All other keys in session_opts are app-internal; the library plumbs them through but does not inspect them.

Summary

Functions

Ensure the agent session for state.conversation_id is running and that the calling process is subscribed to its events.

Whether an agent session is currently running for conversation_id.

Starts (or returns the existing) agent session for a conversation.

Stop the agent session for a conversation. No-op if nothing is running.

Types

config()

@type config() :: %{
  factory_router: module(),
  agent_persistence: module(),
  display_message_persistence: module(),
  pubsub: {module(), atom()},
  presence_module: module(),
  inactivity_timeout: pos_integer(),
  agent_id_fun: (term() -> String.t())
}

session_info()

@type session_info() :: %{agent_id: String.t(), pid: pid(), conversation_id: term()}

Functions

ensure_running(config, state, opts \\ [])

@spec ensure_running(config(), state_map :: map(), opts :: keyword()) ::
  {:ok, %{sagents_subs: map(), agent_id: String.t()}} | {:error, term()}

Ensure the agent session for state.conversation_id is running and that the calling process is subscribed to its events.

state is the host's process-level map (LiveView socket assigns, GenServer state, etc.). Required keys:

  • :conversation_id
  • :current_scope

Optional keys:

Per-request data destined for the FactoryConfig flows through opts, not the state map:

  • :request_opts — keyword list forwarded verbatim to the router as its third argument. The router converts this to a map and passes it to your *Config.from_inputs/1. Use this for per-call fields like :timezone, :tool_context, or anything else your Config consumes.

Returns {:ok, %{sagents_subs: new_subs, agent_id: agent_id}} for the caller to merge back into its state map.

running?(config, conversation_id)

@spec running?(config(), conversation_id :: term()) :: boolean()

Whether an agent session is currently running for conversation_id.

start(config, conversation_id, opts \\ [])

@spec start(config(), conversation_id :: term(), opts :: keyword()) ::
  {:ok, session_info()} | {:error, term()}

Starts (or returns the existing) agent session for a conversation.

Idempotent: if an agent is already running for conversation_id, returns the existing session info without consulting the router or factory again.

Options

  • :scope — Phoenix scope (forwarded to factory + persistence).
  • :request_opts — keyword list passed to the router as the third argument. Routers commonly forward this verbatim into factory_opts.
  • :initial_subscribers — list of {channel, pid} tuples seeded as subscribers before the agent's init/1 returns. Use to atomically start-and-subscribe.

stop(config, conversation_id)

@spec stop(config(), conversation_id :: term()) :: {:ok, :stopped | :not_running}

Stop the agent session for a conversation. No-op if nothing is running.