# `Minga.Frontend.Adapter.GUI.AgentTranscriptEncoder`
[🔗](https://github.com/jsmestad/minga/blob/main/lib/minga/frontend/adapter/gui/agent_transcript_encoder.ex#L1)

Pure GUI adapter encoder for the resident agent-chat transcript stream
(`gui_agent_transcript`, 0x86).

Owns the `gui_agent_transcript` (0x86) resident framing so a frontend can
scroll the session from local data without a BEAM round-trip (#2654),
decoupled from the small `gui_agent_chat` (0x78) chrome model owned by
`Minga.Frontend.Adapter.GUI.AgentChatEncoder`. The render model owns resident
selection. This adapter encodes its `resident_messages` exactly and never
selects a suffix, marks content truncated, or drops entries. It uses
`Minga.Frontend.Adapter.GUI.AgentChatMessageCodec` for the shared per-message
bodies.

Adopts the #2652 resident-store lifecycle: a full store replace only on genuine
structural change (`transcript_epoch` flip or a non-prefix divergence such as
compaction), and id-keyed append/upsert deltas within an epoch for streaming
growth, the in-place last-message patch, and upstream resident eviction. The
append delta carries `trim_front`, the number of leading messages removed by
the model. State for the delta base lives in a
`Minga.Frontend.Adapter.GUI.AgentTranscriptSentState` inside
`Minga.Frontend.Adapter.GUI.Caches`, never on the BEAM editor state.

## Wire payload (after the len32 opcode + u32 length framing)

    version:u8 = 1
    mode:u8            # 0 = full_replace, 1 = append
    epoch:u32
    truncated:u8       # model-owned resident suffix omitted older messages
    # full_replace (mode 0):
    count:u32
    # append (mode 1):
    trim_front:u32     # leading messages evicted from the store front this delta
    base_count:u32     # unchanged leading messages of the remainder to keep
    count:u32
    # both modes:
    count * [ id:u32, body_len:u32, body:bytes ]

`body` is the shared `AgentChatMessageCodec` message body.

## Decoder contract

- `full_replace`: clear the store, then set it to the `count` entries in order.
- `append`: drop `trim_front` messages from the **front** of the store; of the
  remainder, keep the first `base_count` messages unchanged; then upsert each of
  the `count` entries by `id` (a new message appends, a matching `id` patches
  the streaming last message in place). `base_count` is the count of unchanged
  leading messages of the remainder — keep `[0, base_count)` and upsert entries
  from there. It is **not** the client's resident count; it is strictly `<= the
  remainder length` and is normally less than the resident count on every
  streaming patch. Desync (drop and await the next `full_replace`) is only
  warranted when the store's resident count is **less than** `trim_front +
  base_count`, i.e. the delta cannot be applied against what the store holds.

# `encode`

```elixir
@spec encode(
  Minga.RenderModel.UI.AgentChat.t(),
  Minga.Frontend.Adapter.GUI.Caches.t()
) ::
  {binary() | nil, Minga.Frontend.Adapter.GUI.Caches.t()}
```

---

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