# `Mnemosyne.Session`
[🔗](https://github.com/edlontech/mnemosyne/blob/main/lib/mnemosyne/session.ex#L1)

GenStateMachine managing the lifecycle of a memory session.

States: `:idle`, `:collecting`, `:extracting`, `:failed`, `:ready`.

Extraction is spawned under a Task.Supervisor via `async_nolink`,
keeping the session responsive while LLM work happens in the background.
Failed state preserves the closed episode for retry.

# `append_caller`

```elixir
@type append_caller() ::
  {:reply, GenServer.from()} | {:callback, (append_result() -&gt; any())}
```

# `append_result`

```elixir
@type append_result() :: :ok | {:error, Mnemosyne.Errors.error()}
```

# `op_callback`

```elixir
@type op_callback() ::
  ({:ok, term()} | {:error, Mnemosyne.Errors.error()} -&gt; any()) | nil
```

# `state`

```elixir
@type state() :: :idle | :collecting | :extracting | :ready | :failed
```

# `t`

```elixir
@type t() :: %Mnemosyne.Session{
  append_caller: append_caller() | nil,
  append_queue: :queue.queue(),
  append_task: reference() | nil,
  changeset: Mnemosyne.Graph.Changeset.t() | nil,
  committed_step_indices: MapSet.t(non_neg_integer()),
  config: Mnemosyne.Config.t() | nil,
  embedding: module() | nil,
  episode: Mnemosyne.Pipeline.Episode.t() | nil,
  extraction_task: reference() | nil,
  flush_timer: reference() | nil,
  flush_triggered: %{required(String.t()) =&gt; true},
  id: String.t() | nil,
  llm: module() | nil,
  memory_store: GenServer.server() | nil,
  notifier: module() | nil,
  pending_ops: :queue.queue(),
  prev_trajectory_id: String.t() | nil,
  registry: module() | nil,
  repo_id: String.t() | nil,
  session_timer: reference() | nil,
  stopping: boolean(),
  task_supervisor: module() | nil,
  trajectory_tasks: %{
    required(reference()) =&gt; {String.t(), [non_neg_integer()]}
  }
}
```

# `append`

```elixir
@spec append(GenServer.server(), String.t(), String.t()) ::
  :ok | {:error, Mnemosyne.Errors.error()}
```

Appends an observation-action pair to the current episode.
Blocks until the append completes or the timeout expires.

# `append_async`

```elixir
@spec append_async(
  GenServer.server(),
  String.t(),
  String.t(),
  (append_result() -&gt; any()) | nil
) :: :ok
```

Like `append/3` but returns immediately. Accepts an optional callback that
receives `:ok` or `{:error, reason}` when the append finishes.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `close`

```elixir
@spec close(GenServer.server()) :: :ok | {:error, Mnemosyne.Errors.error()}
```

Closes the current episode and starts asynchronous extraction.

# `close_async`

```elixir
@spec close_async(GenServer.server(), op_callback()) ::
  :ok | {:error, Mnemosyne.Errors.Framework.SessionError.t()}
```

Asynchronous close. Returns immediately or queues when busy.
The optional callback receives `{:ok, :closed}` or `{:error, reason}`.

# `commit`

```elixir
@spec commit(GenServer.server()) :: :ok | {:error, Mnemosyne.Errors.error()}
```

Commits the session result. In `:ready` state, applies the extracted changeset
to the MemoryStore and transitions to `:idle`. In `:failed` state, retries
the extraction by re-spawning the extraction task.

# `commit_async`

```elixir
@spec commit_async(GenServer.server(), op_callback()) ::
  :ok | {:error, Mnemosyne.Errors.Framework.SessionError.t()}
```

Asynchronous commit. Returns immediately with `:ok` when the session is idle
or ready, or queues the operation when extraction is in progress. The optional
callback receives `{:ok, :committed}` or `{:error, reason}` when the op runs.

# `discard`

```elixir
@spec discard(GenServer.server()) ::
  :ok | {:error, Mnemosyne.Errors.Framework.SessionError.t()}
```

Discards the extraction result and returns to `:idle`.

# `discard_async`

```elixir
@spec discard_async(GenServer.server(), op_callback()) ::
  :ok | {:error, Mnemosyne.Errors.Framework.SessionError.t()}
```

Asynchronous discard. Returns immediately or queues when busy.
The optional callback receives `{:ok, :discarded}` or `{:error, reason}`.

# `get_context`

```elixir
@spec get_context(GenServer.server() | String.t()) :: {:ok, map() | nil}
```

Returns session context for use by MemoryStore.recall_in_context.

Accepts a pid/name for direct calls, or a string session_id looked up
via `Mnemosyne.Registry` (the default production registry).
Returns `{:ok, %{goal: ..., recent_steps: [...]}}` or `{:ok, nil}` when idle.

# `get_context`

```elixir
@spec get_context(String.t(), atom()) :: {:ok, map() | nil}
```

Like `get_context/1` but looks up the session in the given registry.

# `id`

```elixir
@spec id(GenServer.server()) :: String.t()
```

Returns the unique session ID.

# `start_episode`

```elixir
@spec start_episode(GenServer.server(), String.t()) ::
  :ok | {:error, Mnemosyne.Errors.Framework.SessionError.t()}
```

Opens a new episode with the given goal, transitioning from `:idle` to `:collecting`.

# `start_episode_async`

```elixir
@spec start_episode_async(GenServer.server(), String.t(), op_callback()) ::
  :ok | {:error, Mnemosyne.Errors.Framework.SessionError.t()}
```

Asynchronous start_episode. Returns immediately or queues when busy.
The optional callback receives `{:ok, :started}` or `{:error, reason}`.

# `state`

```elixir
@spec state(GenServer.server()) :: state()
```

Returns the current state atom (`:idle`, `:collecting`, `:extracting`, `:ready`, `:failed`).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
