Expanding an LLM node shows all displayable content in execution order: Markdown, reasoning summaries, function references, parallel groups and retry boundaries. Reuse chat components. Remove fixed LLM Reply / Reasoning sections while retaining distinct underlying content types. This page defines the current protocol, persistence and UI semantics.
Authoritative designExtend execution_stream.v1 instead of creating another chat protocolStreaming, DAG and persistence share one identityReference: agentic-llm-streaming.zh.html
0. Current implementation (checked against current code)
Capability
Implementation boundary
Required invariant
execution_stream.v1
Each nested LLM node maintains attempts and ordered blocks, including types such as text, reasoning_summary, tool_ref, tool_arguments, refusal.
All deltas and snapshots use the same block_id, block_index; the frontend does not reorder by WS arrival time.
DAG / tool identity
Tool-node metadata stores the original tool_call_id and qualified tool_call_occurrence_id; tool_ref stores only the referenced ref_node_id, name and occurrence.
One occurrence corresponds to exactly one tool row. If the provider reuses a raw call ID, a round/message-qualified occurrence creates a new row. Arguments, progress, results and errors come from that DAG node; streams must not duplicate terminal data.
Attempt semantics
A model request after normal tool results remains in the current attempt. Only actual recovery, such as transport or structured-output failure, creates a new attempt.
turn_end means only that one provider round ended; it does not automatically create an attempt boundary. New attempts require an explicit failure reason.
Persistence
checkpoint, metadata.stream, live snapshots and terminal snapshots contain complete displayable attempts/blocks subject to retention policy; legacy previews may still be truncated.
After the owner disappears or the worker restarts, historical reads restore ordering, references and terminal states solely from persistent snapshots, without calling the model again. Live snapshots must not silently drop Markdown prefixes.
Tool reference identity: do not retransmit complete tool arguments/results. When execution creates or discovers a DAG tool node, it records the original tool_call_id and qualified occurrence, then the stream publishes a reference through occurrence_id → ref_node_id. If the mapping is not yet visible, publish a placeholder with a stable occurrence, then use the same block_id to fill in ref_node_id; do not create a second row or fabricate completion.
Existing tool_arguments blocks suit transient previews while arguments are being generated. Terminal display uses the tree node to avoid two sources of truth.
1. Expanded layout
Render text directly as Markdown, with no LLM Reply heading.
Reasoning summaries appear where they occurred and are collapsible by default. Show them only when the model returns a displayable summary; never infer or generate one.
Function rows appear at invocation time; expand to view arguments, progress, results and errors from the referenced node.
New text after a function follows its row and must not merge into the preceding text block.
Internal signatures are not chat content.
“Complete” means all displayable content is accessible. Large tool results may collapse or load on demand, but a truncated preview must not be the only accessible representation.
2. Execution tree and node-content responsibilities
View
Purpose
Execution tree
Call relationships, parallel branches, execution status, duration and failure location
Node content
Ordered text / reasoning / function references within the LLM node
Collapsed card: name, status, duration and short preview.
Expanded cards and right-side details use the sameLlmNodeContent component; two implementations are prohibited.
A function row references a real execution node. Clicking locates that node in the tree; do not copy tool state or results.
3. Model calls inside functions
Recursion is supported: expanded functions show inner LLM calls using the same content component.
Default: expand the selected LLM; show internal functions as summary rows until the user expands them.
At the recursion limit, show an explicit boundary and an Open in details action; the detail panel renders that DAG node as a new content root.
Persist expansion state by node_id, attempt_id, block_id in session UI state; streaming updates and remounting do not reset it.
Render at most eight nested content levels. The limit hides further inline content only; it does not change the execution tree, tool state or history.
Ordinary code functions show arguments, status, logs and return values. Nest model content only when the function contains model calls.
4. Parallel functions
Only when execution actually starts a concurrent batch with known membership may multiple tool_call entries become one parallel_group block. The current Python dispatcher executes tool_call entries sequentially, so it does not publish group_id.
Group order is invocation order, stably. Completion order changes status only, never row order.
Every member carries the same group_id from its first publication. If the group ID is created only when the second member arrives, publish updated events with that ID for all earlier members. The frontend must not infer parallelism from adjacent tool rows.
Function rows update independently; early completion must not move a row ahead of text.
Continuation after normal tools: append text / tool_ref within the same attempt.
Failure retry: create a new attempt and show the boundary; new output must not join the prior incomplete Markdown.
Duplicate events: the same qualified occurrence_id does not create a second row; raw tool_call_id may repeat in different provider rounds.
Late tool node: first show a placeholder tool_ref with a stable ID, then fill in tree data.
If a provider round produces tool results, the next model request continues writing to the same attempt. A new attempt appears only when the recovery policy explicitly calls start_attempt(reason=...).
Reuse the top-level chat renderMarkdown / ThinkingStep / FunctionStep presentation and safety policies.
Incrementally update active text blocks; do not rerender entire completed blocks.
Unclosed code fences, tables or links must not make the whole region disappear.
Virtualize large content by block or load it on demand.
Follow while at the bottom; scrolling up stops following. Function expansion and syntax highlighting should preserve the reading position.
Copy text differs from Copy full execution record; the latter may include reasoning and tool arguments.
7. Disconnect / refresh / failure
Persist ordered blocks, function references and attempts. Do not store only the final reply and reconstruct interleaving from the tree later.
After refresh, interleaving matches the live view; completed functions must not appear running.
Retain incomplete text and mark interruption, cancellation or failure.
For reasoning not persisted, show “This content was not saved” rather than an empty success state.
Recover from snapshots; never call a model merely to render history.
Live and durable snapshots retain complete displayable block content; only compatibility preview fields may be limited.
Historical reads prefer metadata.stream.snapshot; for legacy records, build a single legacy attempt from metadata.blocks and retain persisted tool blocks. Unresolved references show an unresolved state and retain tool_call_id.
8. Implementation order
Ordered protocol and persistence: extend kinds, tool_ref and parallel_group; persist complete attempt.blocks in metadata and checkpoints.
Unified content componentLlmNodeContent: reuse chat Markdown, collapsible reasoning and function rows that read the tree only.
Connect node details: remove Reply/Reasoning sections and verify sequence, refresh and recovery.
Reuse the same component for expanded cards.
Parallelism, recursion, retry boundaries and scrolling acceptance.
No stage may reduce this to concatenating two strings first and adding features later; that would violate ordering.
9. Required acceptance scenarios
The model emits Markdown, calls two functions in parallel, one function calls an inner model with read_file, and the outer model continues. Disconnect and refresh during execution. After recovery, block order, function states and hierarchy must match the earlier live state.
No Reply/Reasoning header bars.
Parallel members are not reordered by completion.
Retries show attempt boundaries without joining incomplete Markdown.
tool_ref and tree nodes correspond one to one without duplicate result copies.
After the owner disappears, historical reads restore identical block ordering; recovered tool state comes from persistent DAG nodes.
Implementation status
Scope
Status
Evidence boundary
Protocol and state merging
Implemented
CallStreamState, the execution-stream reducer and protocol tests cover ordered blocks, tool_ref, attempts and version recovery.
Unified node-content component
Implemented
Expanded cards and right-side details use LlmNodeContent; Markdown, collapsible reasoning, tool references and earlier attempts share one rendering path.
Authority boundaries, expansion state and recursive details
Implemented
Hidden nodes are discarded before projection; io/llm shows permitted descendants only. Node/attempt/block expansion persists; content beyond eight recursive levels offers a details action.
Tool identity, normal continuation and parallel-event consistency
Repaired in this task
Acceptance uses real DAG-node mappings, continuation within one attempt and explicit concurrent batches.
Complete recovery after owner loss / worker restart
Repaired in this task
Use persistent snapshots and observable historical-read results as evidence; long real-model tasks still require live acceptance.
Real-model and default-App acceptance
Awaiting live execution
Run deterministic nested-tool scenarios in the default profile, port 18100, and existing App. Unit tests are not live acceptance evidence for this page.