# `MingaAgent.Session.Transcript`
[🔗](https://github.com/jsmestad/minga/blob/main/lib/minga_agent/session/transcript.ex#L1)

Owns the ordered, identified agent transcript and its branch snapshots.

Every active message is carried by a `TranscriptEntry`, so message content and
identity cannot be reordered independently. Branches freeze those same entry
values. The identity high-water mark includes active and branched entries, so
truncation and branch switching never permit ID reuse.

## Transition contract

Total transitions return the next transcript directly. Rejectable branch
operations return `{:ok, next}` or `{:error, reason}`. All transitions are
pure: callers provide timestamps, and this module performs no persistence,
timer, broadcast, provider, or tool effects.

Compaction keeps the requested active entry IDs in their existing order and
preserves every branch snapshot and pin relationship unchanged. Retained
entries and every branch entry keep their IDs.

# `restore_branch`

```elixir
@type restore_branch() ::
  {name :: String.t(), messages :: [MingaAgent.Message.t()],
   message_ids :: [term()], created_at :: DateTime.t()}
```

Decoded branch fields accepted by the canonical restore transition.

# `stream_kind`

```elixir
@type stream_kind() :: :assistant | :thinking
```

The streaming message kind that may replace the active tail.

# `t`

```elixir
@type t() :: %MingaAgent.Session.Transcript{
  branches: [MingaAgent.Branch.t()],
  entries: [MingaAgent.TranscriptEntry.t()],
  last_changed_at: DateTime.t(),
  next_id: pos_integer(),
  pinned_ids: MapSet.t(pos_integer()),
  revision: non_neg_integer(),
  usage: MingaAgent.TurnUsage.t()
}
```

Canonical transcript state owned by one Session process.

# `add_usage`

```elixir
@spec add_usage(t(), MingaAgent.TurnUsage.t()) :: t()
```

Adds one turn's usage to the transcript aggregate.

# `append`

```elixir
@spec append(t(), MingaAgent.Message.t()) :: t()
```

Appends one newly identified message.

# `append_many`

```elixir
@spec append_many(t(), [MingaAgent.Message.t()]) :: t()
```

Appends a batch of messages and allocates one stable ID per message.

# `append_stream_tail`

```elixir
@spec append_stream_tail(t(), stream_kind(), iodata()) :: t()
```

Replaces or appends one streaming tail from a whole batch of chunks.

# `branch_at`

```elixir
@spec branch_at(t(), integer(), DateTime.t()) ::
  {:ok, t(), MingaAgent.Branch.t()} | {:error, String.t()}
```

Creates a frozen branch snapshot and truncates the active transcript.

# `branches`

```elixir
@spec branches(t()) :: [MingaAgent.Branch.t()]
```

Returns the immutable branch snapshots.

# `collapse_thinking`

```elixir
@spec collapse_thinking(t()) :: t()
```

Collapses every expanded thinking entry without changing identity.

# `compact`

```elixir
@spec compact(t(), [pos_integer()]) :: t()
```

Compacts the active transcript to retained stable IDs and preserves branches.

# `last_changed_at`

```elixir
@spec last_changed_at(t()) :: DateTime.t()
```

Returns the last externally published transcript timestamp.

# `last_message`

```elixir
@spec last_message(t()) :: MingaAgent.Message.t() | nil
```

Returns the active tail message, or nil for an empty transcript.

# `messages`

```elixir
@spec messages(t()) :: [MingaAgent.Message.t()]
```

Returns active messages in transcript order.

# `messages_with_ids`

```elixir
@spec messages_with_ids(t()) :: [{pos_integer(), MingaAgent.Message.t()}]
```

Returns active stable-ID/message pairs in transcript order.

# `new`

```elixir
@spec new([MingaAgent.Message.t()], DateTime.t()) :: t()
```

Creates a live transcript with freshly allocated stable IDs.

# `pinned_ids`

```elixir
@spec pinned_ids(t()) :: MapSet.t(pos_integer())
```

Returns pinned active entry IDs.

# `reset`

```elixir
@spec reset(t(), [MingaAgent.Message.t()]) :: t()
```

Resets the active transcript and usage for a new logical session.

# `restore`

```elixir
@spec restore(
  [MingaAgent.Message.t()],
  term(),
  [MingaAgent.Branch.t() | restore_branch()],
  MingaAgent.TurnUsage.t(),
  MapSet.t(pos_integer()),
  DateTime.t()
) :: t()
```

Restores active and branched entries through the canonical identity allocator.

# `revision`

```elixir
@spec revision(t()) :: non_neg_integer()
```

Returns the transcript mutation revision.

# `switch_branch`

```elixir
@spec switch_branch(t(), integer()) :: {:ok, t()} | {:error, String.t()}
```

Switches to a one-based branch snapshot without renumbering its entries.

# `toggle_message_collapse`

```elixir
@spec toggle_message_collapse(t(), non_neg_integer()) :: t()
```

Toggles collapse for one collapsible active entry by stable transcript ID.

# `toggle_pin`

```elixir
@spec toggle_pin(t(), pos_integer()) :: t()
```

Toggles a pin relationship for one stable transcript ID.

# `touch`

```elixir
@spec touch(t(), DateTime.t()) :: t()
```

Records the timestamp at which Session published transcript changes.

# `transform_messages`

```elixir
@spec transform_messages(t(), (MingaAgent.Message.t() -&gt; MingaAgent.Message.t())) ::
  t()
```

Transforms every message while preserving entry identity and order.

# `update_at`

```elixir
@spec update_at(t(), integer(), (MingaAgent.Message.t() -&gt; MingaAgent.Message.t())) ::
  t()
```

Transforms one message by active transcript index while preserving its identity.

# `update_tool_call`

```elixir
@spec update_tool_call(t(), String.t(), (MingaAgent.ToolCall.t() -&gt;
                                     MingaAgent.ToolCall.t())) :: t()
```

Updates the matching tool-call entry while preserving its stable identity.

# `usage`

```elixir
@spec usage(t()) :: MingaAgent.TurnUsage.t()
```

Returns cumulative transcript usage.

---

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