Minga.Frontend.Adapter.GUI.AgentTranscriptEncoder (Minga v0.1.0)

Copy Markdown View Source

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.

Summary

Functions

encode(model, caches)

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