# `Jido.Agent.InstanceManager`
[🔗](https://github.com/agentjido/jido/blob/v2.3.0/lib/jido/agent/instance_manager.ex#L1)

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

# `key`

```elixir
@type key() :: term()
```

# `manager_name`

```elixir
@type manager_name() :: atom()
```

# `agent_module`

```elixir
@spec agent_module(manager_name()) :: {:ok, module()} | {:error, :not_found}
```

Returns the agent module managed by the given `InstanceManager`.

This is useful for orchestration layers that need to validate manager/module
compatibility without reaching into supervisor config directly.

# `child_spec`

```elixir
@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`

```elixir
@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`

```elixir
@spec lookup(manager_name(), key(), keyword()) :: {: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`

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

Returns statistics for an instance manager.

## Examples

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

# `stop`

```elixir
@spec stop(manager_name(), key(), keyword()) :: :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")

