Jido.Agent.InstanceManager (Jido v2.0.0)

View Source

Keyed singleton registry with lifecycle management and optional storage-backed hibernation.

InstanceManager provides a pattern for managing one agent per logical context (user session, game room, connection, conversation). This is NOT a pool—each key maps to exactly one agent instance. Features:

  • Keyed singletons — one agent per key, lookup or start on demand
  • Automatic lifecycle — idle timeout with attachment tracking
  • Optional storage — hibernate/thaw with pluggable storage backends
  • Multiple registries — different agent types, different configurations

Architecture

Each instance manager consists of:

  • A Registry for unique key → pid lookup
  • A DynamicSupervisor for agent lifecycle
  • Optional storage config for hibernate/thaw persistence

Usage

# In your supervision tree
children = [
  Jido.Agent.InstanceManager.child_spec(
    name: :sessions,
    agent: MyApp.SessionAgent,
    idle_timeout: :timer.minutes(15),
    storage: {Jido.Storage.ETS, table: :session_cache}
  )
]

# At runtime
{:ok, pid} = Jido.Agent.InstanceManager.get(:sessions, "user-123")
:ok = Jido.AgentServer.attach(pid)  # Track this caller as attached

Options

  • :name - Instance manager name (required, atom)
  • :agent - Agent module (required)
  • :idle_timeout - Time in ms before idle agent hibernates/stops (default: :infinity)
  • :storage - nil, storage module, or {StorageModule, opts} (optional)
    • Omitted: derive from Jido instance (:jido, or agent_opts[:jido], or Jido)
    • nil: disable hibernate/thaw for this manager
  • :jido - Jido instance atom used for default storage resolution (optional)
  • :registry_partitions - Partition count for manager registry (default: schedulers online)
  • :agent_opts - Additional options passed to AgentServer

Lifecycle

  1. get/3 looks up by key in Registry
  2. If not found and storage enabled, tries to thaw from storage
  3. If still not found, starts fresh agent
  4. Callers use attach/1 to track interest
  5. When all attachments gone, idle timer starts
  6. On idle timeout: hibernate to storage (if configured) then stop

Persistence keys are manager-scoped ({manager_name, pool_key}), so multiple managers can safely share the same storage backend without checkpoint collisions.

Phoenix Integration

# LiveView mount
def mount(_params, %{"session_key" => key}, socket) do
  if connected?(socket) do
    {:ok, pid} = Jido.Agent.InstanceManager.get(:sessions, key)
    :ok = Jido.AgentServer.attach(pid)
    {:ok, assign(socket, agent_pid: pid)}
  else
    {:ok, socket}
  end
end

Summary

Functions

Returns a child specification for starting an instance manager under a supervisor.

Gets or starts an agent by key.

Looks up an agent by key without starting.

Returns statistics for an instance manager.

Stops an agent by key.

Types

key()

@type key() :: term()

manager_name()

@type manager_name() :: atom()

Functions

child_spec(init_arg)

@spec child_spec(keyword()) :: Supervisor.child_spec()

Returns a child specification for starting an instance manager under a supervisor.

Options

  • :name - Instance manager name (required)
  • :agent - Agent module (required)
  • :idle_timeout - Idle timeout in ms (default: :infinity)
  • :storage - nil, storage module, or {StorageModule, opts} (optional)
  • :jido - Jido instance atom used for default storage resolution (optional)
  • :registry_partitions - Partition count for manager registry (optional)
  • :agent_opts - Options passed to AgentServer (optional)

Examples

Jido.Agent.InstanceManager.child_spec(
  name: :sessions,
  agent: MyApp.SessionAgent,
  idle_timeout: :timer.minutes(15)
)

get(manager, key, opts \\ [])

@spec get(manager_name(), key(), keyword()) :: {:ok, pid()} | {:error, term()}

Gets or starts an agent by key.

If an agent for the given key is already running, returns its pid. If storage is configured and a hibernated state exists, thaws it. Otherwise starts a fresh agent.

Options

  • :initial_state - Initial state for fresh agents (default: %{})

Examples

{:ok, pid} = Jido.Agent.InstanceManager.get(:sessions, "user-123")
{:ok, pid} = Jido.Agent.InstanceManager.get(:sessions, "user-123", initial_state: %{foo: 1})

lookup(manager, key)

@spec lookup(manager_name(), key()) :: {:ok, pid()} | :error

Looks up an agent by key without starting.

Examples

{:ok, pid} = Jido.Agent.InstanceManager.lookup(:sessions, "user-123")
:error = Jido.Agent.InstanceManager.lookup(:sessions, "nonexistent")

stats(manager)

@spec stats(manager_name()) :: %{count: non_neg_integer(), keys: [key()]}

Returns statistics for an instance manager.

Examples

%{count: 5, keys: [...]} = Jido.Agent.InstanceManager.stats(:sessions)

stop(manager, key)

@spec stop(manager_name(), key()) :: :ok | {:error, :not_found}

Stops an agent by key.

If storage is configured, the agent will hibernate before stopping. Uses a graceful shutdown to ensure the agent's terminate callback runs.

Examples

:ok = Jido.Agent.InstanceManager.stop(:sessions, "user-123")
{:error, :not_found} = Jido.Agent.InstanceManager.stop(:sessions, "nonexistent")