# Message Streaming Architecture This document explains the hierarchical streaming architecture for Agent/LLM/MCP message delivery. ## Overview The streaming system uses a hierarchical structure to handle complex scenarios including: - Single LLM calls with multiple message types (thinking, tool calls, text) - Agent logic with multiple sequential operations (LLM → MCP → LLM) - Concurrent/parallel calls to multiple LLMs or MCPs - Real-time delta updates for streaming responses ## Hierarchical Structure ``` Agent Stream (entire conversation) └─ ThreadID (concurrent stream, optional: T1, T2, T3...) └─ BlockID (output block/section: B1, B2, B3...) └─ MessageID (logical message: M1, M2, M3...) └─ ChunkID (stream fragment: C1, C2, C3...) ``` ## Field Definitions ### Message Struct Fields ```go type Message struct { // Core fields Type string `json:"type"` Props map[string]interface{} `json:"props,omitempty"` // Streaming control ChunkID string `json:"chunk_id,omitempty"` MessageID string `json:"message_id,omitempty"` BlockID string `json:"block_id,omitempty"` ThreadID string `json:"thread_id,omitempty"` // Delta control Delta bool `json:"delta,omitempty"` DeltaPath string `json:"delta_path,omitempty"` DeltaAction string `json:"delta_action,omitempty"` // ... } ``` ### Field Responsibilities | Field | Generated By | Purpose | Example Values | Required | | ----------- | -------------------- | ---------------------------------- | ------------------------------------ | ----------------------------------- | | `ChunkID` | System (auto) | Deduplication, ordering, debugging | `C1`, `C2`, `C3` | Always | | `MessageID` | LLM Provider/Handler | Delta merge target | `M1`, `M2`, `M3` or `thinking_msg_1` | Required for delta scenarios | | `BlockID` | Agent Logic | UI block/section rendering | `B1`, `B2`, `B3` or `llm_response_1` | Required when Agent controls blocks | | `ThreadID` | Agent Logic | Concurrent stream distinction | `T1`, `T2`, `T3` or `thread_llm1` | Optional (concurrent only) | ### Detailed Field Explanation #### ChunkID (Stream Fragment Identifier) - **Purpose**: Uniquely identifies each chunk in the stream - **Generated**: Automatically by the system (sequential: M1, M2, M3...) - **Used For**: - Deduplication (prevent duplicate chunks) - Ordering (maintain correct sequence) - Debugging (trace message flow) - **Scope**: Unique within entire Agent stream - **Always Present**: Yes **Example:** ```json {"chunk_id": "C1", "type": "text", "props": {"content": "Hello"}} {"chunk_id": "C2", "type": "text", "props": {"content": " World"}} {"chunk_id": "C3", "type": "thinking", "props": {"content": "..."}} ``` #### MessageID (Logical Message Identifier) - **Purpose**: Groups multiple chunks into one logical message via delta merging - **Generated**: By LLM Provider or Stream Handler - **Used For**: - Delta merge target (frontend merges all chunks with same MessageID) - Distinguishing different messages within a group - **Scope**: Unique within a Group - **Present When**: Delta streaming is used **Example:** ```json // Multiple chunks combine into one "thinking" message {"chunk_id": "C1", "message_id": "M1", "type": "thinking", "props": {"content": "Let me"}, "delta": true} {"chunk_id": "C2", "message_id": "M1", "type": "thinking", "props": {"content": " think"}, "delta": true} {"chunk_id": "C3", "message_id": "M1", "type": "thinking", "props": {"content": "..."}, "delta": true} // Another independent message {"chunk_id": "C4", "message_id": "M2", "type": "text", "props": {"content": "Hello"}, "delta": true} ``` #### BlockID (Output Block Identifier) - **Purpose**: Represents one output block/section (e.g., one LLM call, one MCP call) - **Generated**: By Agent logic - **Used For**: - Frontend UI block/section rendering (visual blocks) - Distinguishing different operations (LLM vs MCP vs custom logic) - Organizing related messages together - **Scope**: Unique within entire Agent stream - **Present When**: Agent explicitly controls output blocks **Key Concept**: Block represents a semantic unit of work from Agent's perspective, NOT from LLM's perspective. Each block is rendered as a distinct UI section in the frontend. **Example:** ```json // BLOCK 1: LLM Response (contains thinking + tool_call + text) {"chunk_id": "C1", "block_id": "B1", "message_id": "M1", "type": "thinking", ...} {"chunk_id": "C2", "block_id": "B1", "message_id": "M2", "type": "tool_call", ...} {"chunk_id": "C3", "block_id": "B1", "message_id": "M3", "type": "text", ...} // BLOCK 2: MCP Call {"chunk_id": "C4", "block_id": "B2", "message_id": "M4", "type": "loading", ...} {"chunk_id": "C5", "block_id": "B2", "message_id": "M5", "type": "text", ...} // BLOCK 3: Another LLM Response {"chunk_id": "C6", "block_id": "B3", "message_id": "M6", "type": "text", ...} ``` #### ThreadID (Concurrent Stream Identifier) - **Purpose**: Distinguishes concurrent/parallel output streams - **Generated**: By Agent logic when spawning concurrent operations - **Used For**: - Separating outputs from parallel LLM/MCP calls - Maintaining independent streaming contexts - **Scope**: Unique within entire Agent stream - **Present When**: Agent makes concurrent calls (optional) **Example:** ```json // Main thread {"chunk_id": "C1", "thread_id": "T1", "block_id": "B1", "message_id": "M1", "type": "text", ...} // Parallel MCP calls {"chunk_id": "C2", "thread_id": "T2", "block_id": "B2", "message_id": "M2", "type": "text", ...} {"chunk_id": "C3", "thread_id": "T3", "block_id": "B3", "message_id": "M3", "type": "text", ...} ``` ## Usage Scenarios ### Scenario 1: Simple Text Message **No streaming, no grouping** ```json { "chunk_id": "C1", "type": "text", "props": { "content": "Hello World" } } ``` **Fields Used:** - `chunk_id`: C1 (auto-generated) - No `message_id`, `block_id`, or `thread_id` needed --- ### Scenario 2: LLM Streaming Response (Single Message) **LLM streams one text message** ```json {"chunk_id": "C1", "message_id": "M1", "type": "text", "props": {"content": "Hello"}, "delta": true} {"chunk_id": "C2", "message_id": "M1", "type": "text", "props": {"content": " World"}, "delta": true} {"chunk_id": "C3", "message_id": "M1", "type": "text", "props": {"content": "!"}, "delta": true} ``` **Fields Used:** - `chunk_id`: C1, C2, C3 (unique per chunk) - `message_id`: M1 (same for all, merge target) - `delta`: true **Frontend Behavior:** - Merge all chunks with `message_id: "M1"` into one message - Display: "Hello World!" --- ### Scenario 3: Agent-Controlled LLM Call (One Block) **Agent wraps LLM response in an output block** ```typescript // Agent code starts a block for the LLM response // System generates block_id: "B1" // LLM returns thinking + tool_call + text // Agent ends the block ``` ```json // LLM chunks within block B1 {"chunk_id": "C1", "message_id": "M1", "block_id": "B1", "type": "thinking", "props": {...}, "delta": true} {"chunk_id": "C2", "message_id": "M1", "block_id": "B1", "type": "thinking", "props": {...}, "delta": true} {"chunk_id": "C3", "message_id": "M2", "block_id": "B1", "type": "tool_call", "props": {...}} {"chunk_id": "C4", "message_id": "M3", "block_id": "B1", "type": "text", "props": {...}, "delta": true} {"chunk_id": "C5", "message_id": "M3", "block_id": "B1", "type": "text", "props": {...}, "delta": true} ``` **Fields Used:** - `chunk_id`: C1~C5 (unique per chunk) - `message_id`: M1, M2, M3 (per logical message) - `block_id`: B1 (all belong to same LLM call) - `delta`: true (for streaming messages) **Frontend Behavior:** - Render one block/section for `block_id: "B1"` - Within this block, show 3 messages: - Thinking message (chunks C1+C2 merged into M1) - Tool call message (chunk C3 = M2) - Text message (chunks C4+C5 merged into M3) --- ### Scenario 4: Agent Sequential Operations (Multiple Blocks) **Agent orchestrates: LLM → MCP → LLM** ```typescript // Agent code orchestrates three sequential operations: // 1. Block B1: First LLM call // 2. Block B2: MCP call // 3. Block B3: Second LLM call ``` ```json // BLOCK 1: First LLM call {"chunk_id": "C1", "message_id": "M1", "block_id": "B1", "type": "text", ...} {"chunk_id": "C2", "message_id": "M1", "block_id": "B1", "type": "text", ...} // BLOCK 2: MCP call {"chunk_id": "C3", "message_id": "M2", "block_id": "B2", "type": "loading", ...} {"chunk_id": "C4", "message_id": "M3", "block_id": "B2", "type": "text", ...} // BLOCK 3: Second LLM call {"chunk_id": "C5", "message_id": "M4", "block_id": "B3", "type": "text", ...} {"chunk_id": "C6", "message_id": "M4", "block_id": "B3", "type": "text", ...} ``` **Frontend Behavior:** - Render 3 distinct blocks/sections: 1. Block 1 (B1): LLM response with text 2. Block 2 (B2): MCP call with loading + result 3. Block 3 (B3): LLM response with text --- ### Scenario 5: Concurrent Operations (Blocks + Threads) **Agent uses concurrent handler to make parallel calls** ```typescript // Agent orchestrates parallel operations within one block (B1) // The concurrent handler automatically assigns thread_id to each operation: // - MCP call for weather (thread_id: "T1") // - MCP call for news (thread_id: "T2") // - LLM call for summary (thread_id: "T3") // // Messages from different threads may arrive in any order ``` ```json // Same block, different threads (may arrive in any order) {"chunk_id": "C1", "message_id": "M1", "block_id": "B1", "thread_id": "T1", "type": "text", "props": {"content": "Weather: Sunny"}} {"chunk_id": "C2", "message_id": "M2", "block_id": "B1", "thread_id": "T2", "type": "text", "props": {"content": "News: ..."}} {"chunk_id": "C3", "message_id": "M1", "block_id": "B1", "thread_id": "T1", "type": "text", "props": {"content": ", 25°C"}} {"chunk_id": "C4", "message_id": "M3", "block_id": "B1", "thread_id": "T3", "type": "text", "props": {"content": "Summary..."}} ``` **Fields Used:** - `chunk_id`: C1, C2, C3, C4 (unique per chunk, chronological order) - `message_id`: M1, M2, M3 (per operation/message) - `block_id`: B1 (all belong to same parallel operation block) - `thread_id`: T1, T2, T3 (distinguish concurrent operations) **Frontend Behavior:** - Render one block for `block_id: "B1"` - Within this block, separate messages by `thread_id`: - Thread T1 (Weather): M1 (chunks C1+C3 merged) → "Weather: Sunny, 25°C" - Thread T2 (News): M2 (chunk C2) - Thread T3 (Summary): M3 (chunk C4) - Or interleave by `chunk_id` order (C1, C2, C3, C4) to show real-time arrival --- ## Summary | Field | Level | Purpose | Example | | ----------- | ----------- | ------------------ | ---------- | | `ChunkID` | System | Transport/debug | C1, C2, C3 | | `MessageID` | LLM/Handler | Delta merging | M1, M2, M3 | | `BlockID` | Agent | UI blocks/sections | B1, B2, B3 | | `ThreadID` | Agent | Concurrency | T1, T2, T3 | **Key Insight**: Each field serves a distinct purpose at a specific layer of the architecture. This hierarchical design supports simple single-message scenarios while enabling complex Agent orchestration with concurrent operations. Blocks provide natural UI boundaries for organizing related messages.