You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I propose fixing #5542 in the shared provider pipeline for every provider that exposes reasoning.
A reasoning stream is provider output that carries reasoning or a reasoning summary separately from the final answer. It should use the existing assistant message path. It should not need a Claude-only path.
#5542 was filed for Claude. A follow-up comment correctly widened the scope to all providers.
I run a fork and hit this bug there. I fixed it and have used the result for a few days. I am opening a discussion first because CONTRIBUTING asks contributors to agree on larger changes before sending a PR.
Claude does not register thinking blocks. This is not fixed on this branch.
The content_block_start handler still registers text and tool blocks, then returns for every other block. A thinking block therefore never gets an item ID. An item ID is the provider's identifier for that content block. Its thinking deltas arrive without an itemId.
This branch deliberately does not depend on that ID. Reasoning messages use reasoning:<threadId>:<turnId>:segment:<n>. The thread, turn, and segment number identify each burst across providers. If an event has no turn ID, the implementation uses turnless in that slot.
I can also register Claude thinking blocks at the source if maintainers prefer. That fix is not required for this design.
Ingestion dropped every stream except assistant text. This is fixed.
A stream kind is the label that says what a provider content delta contains. It can now be assistant_text, reasoning_text, or reasoning_summary_text on this path. The old ingestion code accepted only assistant_text.
The two reasoning kinds now flow through the shared path. This unblocks Codex and OpenCode as well as Claude.
Reasoning had no renderer or persisted field, and Claude did not request displayable thinking. These are fixed.
Migration 044 adds a nullable channel column to the thread-message projection. Web renders messages with channel: "reasoning" as collapsible rows.
Reasoning stays in the existing assistant message pipeline as an optional channel: "reasoning" field. There is no parallel message type and no second store. A separate path would duplicate ordering, persistence, and delivery rules that assistant messages already have.
One burst of thinking becomes one message. The event-sourced projection is the persisted read model rebuilt by replaying the event log. It stores its channel in the nullable column added by migration 044.
Web nests each reasoning message under the turn's Worked for Ns group. The row reads Thought for Ns and can be expanded or collapsed.
Before, on current main. The turn goes straight from the prompt to the answer.
After, on this branch. The same prompt now shows a collapsible reasoning row, expanded here.
Delivery follows the existing enableLegacyTokenStreaming setting. The same setting controls whether answer and reasoning deltas stream immediately or use buffered delivery. There is no new delivery mode.
Reasoning is deliberately excluded from answer semantics:
It never settles a turn.
It never lands in a checkpoint. A checkpoint is the hidden git ref that records the turn's workspace state.
It never supplies a thread title.
It is excluded from search and the minimap.
These exclusions keep provider reasoning visible without letting it act like the provider's answer.
Codex and OpenCode use the same ingestion path. Tests cover both reasoning_text and reasoning_summary_text, but I did not manually drive either provider.
Mobile filters reasoning out during derivation. Derivation is the step that turns shared thread state into data for the mobile view. There is no mobile reasoning UI yet.
Cursor and Grok expose no reasoning stream, so there is nothing to render for those providers.
Version skew
Older clients remain usable. During schema decode, they validate wire data and convert it into the client's typed shape. They strip the unknown channel field. They render reasoning as ordinary assistant text instead of failing.
Rollback also works in the other direction. If an older server starts against events already carrying channel, it still replays the log and starts.
Size and possible split
The branch is rebased on current main. It changes 32 files with about 900 additions and 65 deletions. About half of the diff is tests. This is larger than CONTRIBUTING prefers.
The work can split into three pieces:
The two-line Claude thinking.display option.
The shared server pipeline.
The web reasoning rows.
I can prepare any subset, send the pieces in that order, or keep the change together. A "we want to do this ourselves" answer is also fine. You can take the branch directly.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
I propose fixing #5542 in the shared provider pipeline for every provider that exposes reasoning.
A reasoning stream is provider output that carries reasoning or a reasoning summary separately from the final answer. It should use the existing assistant message path. It should not need a Claude-only path.
#5542 was filed for Claude. A follow-up comment correctly widened the scope to all providers.
I run a fork and hit this bug there. I fixed it and have used the result for a few days. I am opening a discussion first because CONTRIBUTING asks contributors to agree on larger changes before sending a PR.
The three gaps in #5542
Claude does not register thinking blocks. This is not fixed on this branch.
The
content_block_starthandler still registers text and tool blocks, then returns for every other block. A thinking block therefore never gets an item ID. An item ID is the provider's identifier for that content block. Its thinking deltas arrive without anitemId.This branch deliberately does not depend on that ID. Reasoning messages use
reasoning:<threadId>:<turnId>:segment:<n>. The thread, turn, and segment number identify each burst across providers. If an event has no turn ID, the implementation usesturnlessin that slot.I can also register Claude thinking blocks at the source if maintainers prefer. That fix is not required for this design.
Ingestion dropped every stream except assistant text. This is fixed.
A stream kind is the label that says what a provider content delta contains. It can now be
assistant_text,reasoning_text, orreasoning_summary_texton this path. The old ingestion code accepted onlyassistant_text.The two reasoning kinds now flow through the shared path. This unblocks Codex and OpenCode as well as Claude.
Reasoning had no renderer or persisted field, and Claude did not request displayable thinking. These are fixed.
Migration 044 adds a nullable
channelcolumn to the thread-message projection. Web renders messages withchannel: "reasoning"as collapsible rows.The Claude adapter now requests
thinking: { type: "adaptive", display: "summarized" }unless thinking is explicitly disabled. Withoutdisplay: "summarized", Claude sends redacted thinking token estimates instead of displayable summaries. This removes the--thinking-display summarizedlaunch-argument workaround described in [Bug]: Claude provider drops thinking blocks — reasoning_text deltas are emitted without an itemId, so the UI never renders them #5542.Design
Reasoning stays in the existing assistant message pipeline as an optional
channel: "reasoning"field. There is no parallel message type and no second store. A separate path would duplicate ordering, persistence, and delivery rules that assistant messages already have.One burst of thinking becomes one message. The event-sourced projection is the persisted read model rebuilt by replaying the event log. It stores its channel in the nullable column added by migration 044.
Web nests each reasoning message under the turn's
Worked for Nsgroup. The row readsThought for Nsand can be expanded or collapsed.Before, on current main. The turn goes straight from the prompt to the answer.
After, on this branch. The same prompt now shows a collapsible reasoning row, expanded here.
Delivery follows the existing
enableLegacyTokenStreamingsetting. The same setting controls whether answer and reasoning deltas stream immediately or use buffered delivery. There is no new delivery mode.Reasoning is deliberately excluded from answer semantics:
These exclusions keep provider reasoning visible without letting it act like the provider's answer.
Verification and surface coverage
Claude is verified end to end in a real client.
Demo video: https://youtu.be/VuzwmRg24uM
Codex and OpenCode use the same ingestion path. Tests cover both
reasoning_textandreasoning_summary_text, but I did not manually drive either provider.Mobile filters reasoning out during derivation. Derivation is the step that turns shared thread state into data for the mobile view. There is no mobile reasoning UI yet.
Cursor and Grok expose no reasoning stream, so there is nothing to render for those providers.
Version skew
Older clients remain usable. During schema decode, they validate wire data and convert it into the client's typed shape. They strip the unknown
channelfield. They render reasoning as ordinary assistant text instead of failing.Rollback also works in the other direction. If an older server starts against events already carrying
channel, it still replays the log and starts.Size and possible split
The branch is rebased on current main. It changes 32 files with about 900 additions and 65 deletions. About half of the diff is tests. This is larger than CONTRIBUTING prefers.
The work can split into three pieces:
thinking.displayoption.I can prepare any subset, send the pieces in that order, or keep the change together. A "we want to do this ourselves" answer is also fine. You can take the branch directly.
Branch: https://github.com/kgarg2468/t3code/tree/t3code/streaming-reasoning-transcript
Model: GPT-5.6 Sol. Harness: Codex, orchestrated from Claude Code.
All reactions