Jido.Agent.InstanceManager (Jido v2.0.0-rc.0)
View SourceKeyed singleton registry with lifecycle management and optional persistence.
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 persistence — hibernate/thaw with pluggable storage backends
- Multiple registries — different agent types, different configurations
Architecture
Each instance manager consists of:
- A
Registryfor unique key → pid lookup - A
DynamicSupervisorfor agent lifecycle - Optional
Storefor hibernate/thaw persistence
Usage
# In your supervision tree
children = [
Jido.Agent.InstanceManager.child_spec(
name: :sessions,
agent: MyApp.SessionAgent,
idle_timeout: :timer.minutes(15),
persistence: [
store: {Jido.Agent.Store.ETS, table: :session_cache}
]
)
]
# At runtime
{:ok, pid} = Jido.Agent.InstanceManager.get(:sessions, "user-123")
:ok = Jido.AgentServer.attach(pid) # Track this caller as attachedOptions
:name- Instance manager name (required, atom):agent- Agent module (required):idle_timeout- Time in ms before idle agent hibernates/stops (default::infinity):persistence- Persistence configuration (optional):store-{StoreModule, opts}tuple:key_fun- Custom key function(agent_module, agent_id) -> key
:agent_opts- Additional options passed to AgentServer
Lifecycle
get/3looks up by key in Registry- If not found and persistence enabled, tries to thaw from store
- If still not found, starts fresh agent
- Callers use
attach/1to track interest - When all attachments gone, idle timer starts
- On idle timeout: hibernate to store (if configured) then stop
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
Functions
@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):persistence-[store: {Module, opts}](optional):agent_opts- Options passed to AgentServer (optional)
Examples
Jido.Agent.InstanceManager.child_spec(
name: :sessions,
agent: MyApp.SessionAgent,
idle_timeout: :timer.minutes(15)
)
@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 persistence 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})
@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")
@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)
@spec stop(manager_name(), key()) :: :ok | {:error, :not_found}
Stops an agent by key.
If persistence 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")