Files
paseo/.tasks/9b83b90a.md
T

2.5 KiB

id, title, status, deps, created
id title status deps created
9b83b90a Refactor stream buffering to head/tail model open
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

// 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

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

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.