--- id: 9b83b90a title: Refactor stream buffering to head/tail model status: open deps: [] created: 2026-01-11T08:48:35.444Z --- ## Goal Replace the current special-case buffering logic with a unified head/tail model where: - **Tail**: Committed StreamItem[] (history) - **Head**: Active StreamItem[] (being streamed, same type as tail) Both use the same `reduceStreamUpdate()` function. The only difference is that head items get flushed to tail when a stream item completes. ## Current Problems 1. `applyStreamEventWithBuffer()` has hardcoded assistant_message buffering 2. `StreamingBufferEntry` is a different type than `StreamItem` 3. Reasoning chunks go through reducer directly (no buffering) 4. Duplicate logic between `appendAssistantMessage()` and buffer handling ## Design ### Types ```typescript // Head is just StreamItem[] - no special type needed // Tail is just StreamItem[] - same as before interface StreamState { tail: StreamItem[]; // committed history head: StreamItem[]; // active streaming items } ``` ### Core Function ```typescript function applyStreamEvent(params: { tail: StreamItem[]; head: StreamItem[]; event: AgentStreamEventPayload; timestamp: Date; }): { tail: StreamItem[]; head: StreamItem[]; changedTail: boolean; changedHead: boolean; } ``` ### Flush Logic Head items are flushed to tail when: 1. A different item kind arrives (thought → tool_call flushes the thought) 2. Turn completion events (turn_completed, turn_failed, turn_canceled) 3. User message arrives (new turn starting) ### Implementation Steps 1. Remove `StreamingBufferEntry` type 2. Remove `StreamingHead` type (or repurpose) 3. Modify `applyStreamEventWithBuffer` → `applyStreamEvent` to work with head/tail 4. Head uses same `reduceStreamUpdate` as tail 5. Add flush logic based on item kind transitions 6. Update session-context.tsx to use new API 7. Update agent-stream-view.tsx to render head items 8. Update session-store.ts types ## Flush Conditions ```typescript function shouldFlushHead(head: StreamItem[], incomingKind: StreamItemKind): boolean { if (head.length === 0) return false; const lastHeadKind = head[head.length - 1].kind; // Different kind arriving = flush if (lastHeadKind !== incomingKind) return true; return false; } ``` ## Rendering In agent-stream-view.tsx: - Tail is rendered in FlatList (reversed, virtualized) - Head is rendered in ListHeaderComponent (always visible, not virtualized since it's small) This is similar to current approach but head can now contain any StreamItem types.