# `Jido.Persist`
[🔗](https://github.com/agentjido/jido/blob/v2.3.0/lib/jido/persist.ex#L1)

Coordinates hibernate/thaw operations for agents with thread support.

This module is the **invariant enforcer** - it ensures:

1. Journal is flushed before checkpoint
2. Checkpoint never contains full Thread, only a pointer
3. Thread is rehydrated on thaw

## API

The primary API accepts a storage configuration tuple:

    Jido.Persist.hibernate({adapter, opts}, agent)
    Jido.Persist.hibernate({adapter, opts}, agent_module, key, agent)
    Jido.Persist.thaw({adapter, opts}, agent_module, key)

Or a Jido instance with embedded storage config:

    Jido.Persist.hibernate(jido_instance, agent)
    Jido.Persist.hibernate(jido_instance, agent_module, key, agent)
    Jido.Persist.thaw(jido_instance, agent_module, key)

## hibernate Flow

1. Extract thread from `agent.state[:__thread__]`
2. Flush only missing thread entries via `adapter.append_thread/3`
3. Call `agent_module.checkpoint/2` if implemented, else use default
4. **Enforce invariant**: Remove `:__thread__` from state, store only thread pointer
5. Call `adapter.put_checkpoint/3`

## thaw/3 Flow

1. Call `adapter.get_checkpoint/2`
2. If missing, return `{:error, :not_found}`
3. Call `agent_module.restore/2` if implemented, else use default
4. If checkpoint has thread pointer, load and attach thread
5. Verify loaded thread.rev matches checkpoint pointer rev

## Agent Callbacks

Agents may optionally implement:

- `checkpoint(agent, ctx)` - Returns `{:ok, checkpoint_data}` for custom serialization
- `restore(checkpoint_data, ctx)` - Returns `{:ok, agent}` for custom deserialization

If not implemented, default serialization is used.

# `agent`

```elixir
@type agent() :: struct()
```

# `agent_module`

```elixir
@type agent_module() :: module()
```

# `checkpoint`

```elixir
@type checkpoint() :: %{
  version: pos_integer(),
  agent_module: agent_module(),
  id: term(),
  state: map() | nil,
  thread: thread_pointer() | nil
}
```

# `checkpoint_key`

```elixir
@type checkpoint_key() :: {agent_module(), term()}
```

# `key`

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

# `storage_config`

```elixir
@type storage_config() :: {module(), keyword()}
```

# `thread_pointer`

```elixir
@type thread_pointer() :: %{id: String.t(), rev: non_neg_integer()}
```

# `hibernate`

```elixir
@spec hibernate(storage_config() | module() | struct(), agent()) ::
  :ok | {:error, term()}
```

Persists an agent to storage, flushing any pending thread entries first.

Accepts a `{storage_adapter, opts}` tuple, a storage adapter module,
a map/struct with `:storage`, or a Jido instance module.

# `hibernate`

```elixir
@spec hibernate(
  storage_config() | module() | struct(),
  agent_module(),
  key(),
  agent()
) ::
  :ok | {:error, term()}
```

Persists an agent using an explicit `{agent_module, key}` identity.

This is primarily used by keyed lifecycle managers that persist with pool keys.

# `persist_scheduler_manifest`

```elixir
@spec persist_scheduler_manifest(
  storage_config() | module() | struct(),
  agent_module(),
  key(),
  agent(),
  map()
) :: :ok | {:error, term()}
```

Persists only the scheduler manifest for an existing checkpoint.

This is used by the keyed lifecycle to provide write-through durability for
dynamic cron mutations without rewriting a full checkpoint on every update.
If the checkpoint does not exist yet, falls back to full `hibernate/4`.

# `thaw`

```elixir
@spec thaw(storage_config() | module() | struct(), agent_module(), key()) ::
  {:ok, agent()} | {:error, term()}
```

Restores an agent from storage, rehydrating thread if present.

Accepts a `{storage_adapter, opts}` tuple, a storage adapter module,
a map/struct with `:storage`, or a Jido instance module.

