anomalyco / anomalyco/opencode

[Bug]: SSE reasoning stream deltas (delta.reasoning / thought) accumulate duplicate state objects in SQLite causing context explosion across turns

Open
#44,581 1 comment 1 reaction 1 assignee View on GitHub

@kitlangton is already working on this.

Since Aug 24, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Description

During multi-turn testing with reasoning models (such as ox-alpha and models outputting streaming thought/reasoning tokens), we observed severe prompt token inflation across consecutive turns. After investigating the local session storage, we found that every streaming reasoning SSE chunk (delta.reasoning / delta.thought) was being persisted as an individual { type: "reasoning", state: ... } object inside session_message.content[] instead of being concatenated / folded into a single reasoning content block.

(Attribution: Discovered during multi-turn testing with ox-alpha/reasoning models; investigated and code-isolated together with my AI pairing assistant).


Empirical Evidence & Token Metrics
1. SQLite Inspection (session_message)

Inspecting the SQLite database for a single streaming assistant message containing reasoning tokens revealed over 375 discrete reasoning objects within content:

// session_message.content (excerpt)
[
  { "type": "reasoning", "state": { "text": "Let" } },
  { "type": "reasoning", "state": { "text": " me" } },
  { "type": "reasoning", "state": { "text": " check" } },
  ... // 375+ entries (~1.09 MB of redundant JSON structure per single response)
]
2. Context Inflation Across Turns

When subsequent turns re-serialize the full conversation history to upstream providers, the repeated JSON envelope overhead and duplicated objects inflate prompt context rapidly:

Turn Count Prompt Tokens (Without Folding) Prompt Tokens (With In-Place Folding) Context Bloat Factor
Turn 1 ~2.5k ~2.5k 1.0x
Turn 5 ~118k ~14k ~8.4x
Turn 10 300k+ (Context Explosion / OOM) ~28k ~10.7x

Root Cause Analysis

In the streaming reducer / message processor, incoming SSE delta chunks containing delta.reasoning or delta.thought push a new object onto the message content[] array on each chunk arrival, rather than identifying the existing reasoning block and appending chunk text in-place:

// Problematic behavior:
// Every chunk creates a new content array element
content.push({ type: "reasoning", state: { text: chunk.delta.reasoning } });

Proposed Fix
1. In-Place Stream Accumulator / Reducer

When processing incoming delta chunks, fold into the active reasoning block or append to the existing block:

function reduceReasoningDelta(content: ContentPart[], deltaText: string): ContentPart[] {
  const lastPart = content[content.length - 1];

  if (lastPart && lastPart.type === "reasoning") {
    // Fold in-place
    lastPart.state = {
      ...lastPart.state,
      text: (lastPart.state?.text ?? "") + deltaText,
    };
  } else {
    // Initialize single reasoning part
    content.push({
      type: "reasoning",
      state: { text: deltaText },
    });
  }

  return content;
}
2. Request Scrubbing Before Dispatch

When serializing conversation history for upstream LLM completion requests, ensure consecutive/fragmented reasoning blocks are compacted into single text blocks or filtered according to provider capabilities:

function compactMessageContent(content: ContentPart[]): ContentPart[] {
  return content.reduce<ContentPart[]>((acc, current) => {
    const prev = acc[acc.length - 1];
    if (prev && prev.type === "reasoning" && current.type === "reasoning") {
      prev.state = {
        ...prev.state,
        text: (prev.state?.text ?? "") + (current.state?.text ?? ""),
      };
      return acc;
    }
    acc.push(current);
    return acc;
  }, []);
}

Additional Context
  • OpenCode Version: 1.18.x / latest
  • OS: Linux / WSL2 / macOS
  • Willingness to contribute: Happy to open a clean PR with unit tests for the stream reducer and message normalizer if maintainers would like!

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.