Skip to content

reasoning_content stripped from assistant messages on replay, causing KV cache invalidation on local inference #19081

Closed as not planned

Description

@michal-zurkowski

Description

When an assistant response contains reasoning_content (thinking tokens), OpenCode correctly handles it for the immediate turn. However, on subsequent turns, when that assistant message is replayed as part of the conversation history, the thinking block is stripped.

This silently removes tokens from the conversation history, which invalidates the KV cache on local inference backends (like llamacpp) at the point where the thinking block used to be. The backend is then forced to reprocess the entire context from that turn forward on every subsequent turn.

While strict providers like Kimi K2.5 hard-fail with a 400 error when this happens #10996 , permissive local backends suffer a "silent fail." This makes thinking models non-interactive on local setups due to the massive latency penalty of constant prompt reprocessing. This might even be present when some providers for some models, which does result in less cached prompts, resulting in added costs.

This issue does not need reprocessing when the model is in a turn with itself, making calls back and forth.

Plugins

No response

OpenCode version

1.2.27

Steps to reproduce

Setup: NixOS, OpenCode communicating with a local llama-server hosting a Qwen3.5-122B model. Qwen3.5 35B, GPT-OSS-120B/20B, were also tested and gave same results of stripping which resulted in invalidation. All Qwen3.5 models, regardless if thinking or not result in the same problem (non thinking still produced empty <think>\n\n</think> block which is also stripped and requires reprocessing.

Prompt 1: Ask the model to "Say Hello". This initializes the system prompt and tools. Context is cached normally.
Prompt 2: Ask the model to do a task (e.g., "Read a file"). The model outputs a thinking block followed by the tool call/action. It also works with normal output, but reading the can be done quicker than genereting 10k tokens.
Prompt 3: Say "Hello" again. OpenCode sends the conversation history, but strips the thinking block from the Prompt 2 assistant message. The llama-server KV cache is invalidated at the Prompt 2 boundary, forcing it to fully reprocess the output of Prompt 2.
Prompt 4: Say "Hello" again. The same thing happens; it has to reprocess the output of Prompt 3.

Screenshot and/or share link

When Prompt 2 completes (tool_call assistant message includes thinking):

<|im_start|>assistant
<think>
The user wants me to read PROJECT_PLAN.md and nothing else, then reply with "Done". Let me read this file.
</think>

<tool_call>
<function=read>
<parameter=filePath>
/home/delgon/ai-controller/PROJECT_PLAN.md
</parameter>
</function>
</tool_call><|im_end|>

When Prompt 3 replays the same message (thinking block stripped):

<|im_start|>assistant
<tool_call>
<function=read>
<parameter=filePath>
/home/delgon/ai-controller/PROJECT_PLAN.md
</parameter>
</function>
</tool_call><|im_end|>

Token counts from llama-server logs

Request Task Tokens Sent Tokens Processed Cache Behavior
Prompt 1 ("Hello") 269 18,006 18,006 Fresh start
Prompt 2a (tool call) 312 18,048 46 Not perfect cache hit (sim=0.998)
Prompt 2b (tool result) 384 37,013 18,896 Perfect cache hit (f_keep=1.000)
Prompt 3 ("Hello again") 424 36,995 18,993 Cache invalidated at token ~18,002

Prompt 3 sends 18 fewer total tokens than Prompt 2b despite having two additional messages (the "Done" response + user's Prompt 3). The missing tokens correspond exactly to the stripped <think> block.

llama-server cache rollback log (Prompt 3)

slot update_slots: id  0 | task 424 | n_past = 18046, slot.prompt.tokens.size() = 37045
slot update_slots: id  0 | task 424 | Checking checkpoint with [37008, 37008] against 18046...
slot update_slots: id  0 | task 424 | Checking checkpoint with [18001, 18001] against 18046...
slot update_slots: id  0 | task 424 | restored context checkpoint (pos_min = 18001, n_past = 18002)
slot update_slots: id  0 | task 424 | erased invalidated context checkpoint (pos_min = 26308, ...)
slot update_slots: id  0 | task 424 | erased invalidated context checkpoint (pos_min = 34500, ...)
slot update_slots: id  0 | task 424 | erased invalidated context checkpoint (pos_min = 35984, ...)
slot update_slots: id  0 | task 424 | erased invalidated context checkpoint (pos_min = 37008, ...)
prompt eval time = 75166.29 ms / 18993 tokens

The cache diverges at the tool call boundary (~token 18,002) and must reprocess everything after it.

Operating System

NixOS

Terminal

No response

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Mar 25, 2026
  2. github-actions commented on Mar 25, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

  3. added a commit that references this issue on Mar 28, 2026
    503a2df
  4. vpavlin commented on Apr 2, 2026

    @vpavlin

    Bump! I tried OpenCode today and got the same experience.

  5. KingingWang commented on Apr 5, 2026

    @KingingWang

    I have encountered the same issue

  6. 0x62ash commented on Apr 16, 2026

    @0x62ash

    I constantly have this issue on Qwen3.5.
    I tried Qwen3.6 with --chat-template-kwargs {"preserve_thinking": true}, and the problem still persists.

    22.44.646.380 I slot update_slots: id  0 | task 14968 | Checking checkpoint with [25155, 25155] against 24845...
    22.44.646.380 I slot update_slots: id  0 | task 14968 | Checking checkpoint with [23111, 23111] against 24845...
    22.44.655.798 W slot update_slots: id  0 | task 14968 | restored context checkpoint (pos_min = 23111, pos_max = 23111, n_tokens = 23112, n_past = 23112, size = 62.813 MiB)
    22.44.655.803 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 25155, pos_max = 25155, n_tokens = 25156, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.657.435 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 25258, pos_max = 25258, n_tokens = 25259, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.659.034 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 25496, pos_max = 25496, n_tokens = 25497, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.660.737 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 25697, pos_max = 25697, n_tokens = 25698, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.662.266 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 27741, pos_max = 27741, n_tokens = 27742, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.663.781 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 27830, pos_max = 27830, n_tokens = 27831, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.665.298 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 28279, pos_max = 28279, n_tokens = 28280, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.666.824 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 28377, pos_max = 28377, n_tokens = 28378, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.668.541 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 29098, pos_max = 29098, n_tokens = 29099, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.670.063 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 29201, pos_max = 29201, n_tokens = 29202, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.671.687 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 30092, pos_max = 30092, n_tokens = 30093, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.673.203 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 30178, pos_max = 30178, n_tokens = 30179, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.674.922 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 30309, pos_max = 30309, n_tokens = 30310, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.676.428 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 30390, pos_max = 30390, n_tokens = 30391, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.677.935 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 30962, pos_max = 30962, n_tokens = 30963, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.679.483 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 33006, pos_max = 33006, n_tokens = 33007, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.680.998 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 33094, pos_max = 33094, n_tokens = 33095, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.682.510 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 33167, pos_max = 33167, n_tokens = 33168, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.684.006 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 33258, pos_max = 33258, n_tokens = 33259, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.685.656 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 33446, pos_max = 33446, n_tokens = 33447, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.687.148 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 33539, pos_max = 33539, n_tokens = 33540, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.688.678 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 33697, pos_max = 33697, n_tokens = 33698, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.690.174 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 33853, pos_max = 33853, n_tokens = 33854, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.691.746 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 35356, pos_max = 35356, n_tokens = 35357, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.693.329 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 35459, pos_max = 35459, n_tokens = 35460, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.694.902 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 35656, pos_max = 35656, n_tokens = 35657, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.696.423 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 35752, pos_max = 35752, n_tokens = 35753, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.698.036 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 37461, pos_max = 37461, n_tokens = 37462, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.699.549 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 37544, pos_max = 37544, n_tokens = 37545, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.701.072 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 37674, pos_max = 37674, n_tokens = 37675, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.702.758 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 37767, pos_max = 37767, n_tokens = 37768, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.704.285 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 39010, pos_max = 39010, n_tokens = 39011, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.705.817 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 39097, pos_max = 39097, n_tokens = 39098, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.707.325 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 39205, pos_max = 39205, n_tokens = 39206, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.708.854 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 40214, pos_max = 40214, n_tokens = 40215, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.710.377 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 41067, pos_max = 41067, n_tokens = 41068, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.711.882 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 43111, pos_max = 43111, n_tokens = 43112, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.713.412 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 43987, pos_max = 43987, n_tokens = 43988, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.714.944 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 46031, pos_max = 46031, n_tokens = 46032, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.716.524 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 46121, pos_max = 46121, n_tokens = 46122, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.718.067 W slot update_slots: id  0 | task 14968 | erased invalidated context checkpoint (pos_min = 47526, pos_max = 47526, n_tokens = 47527, n_swa = 0, pos_next = 23112, size = 62.813 MiB)
    22.44.719.623 I slot update_slots: id  0 | task 14968 | n_tokens = 23112, memory_seq_rm [23112, end)
    22.44.722.605 I slot update_slots: id  0 | task 14968 | prompt processing progress, n_tokens = 25160, batch.n_tokens = 2048, progress = 0.515912
    22.44.746.394 I slot update_slots: id  0 | task 14968 | n_tokens = 25160, memory_seq_rm [25160, end)
    22.44.746.593 I slot update_slots: id  0 | task 14968 | 2048 tokens since last checkpoint at 23112, creating new checkpoint during processing at position 27208
    
  7. 0x62ash commented on Apr 17, 2026

    @0x62ash

    ggml-org/llama.cpp#19394 (comment)

    The first problem with opencode is model-independent, for some reason opencode is moving system-reminder from place to place.

    To reproduce:

    start with plan mode, say something
    switch to build model, say something
    try to use tools
    again say something even simple
    notice unnecessary prompt processing - system-reminder is moving from the middle to the last part of prompt

  8. slepkaviba commented on May 2, 2026

    @slepkaviba

    I noticed woth Qwen3.6 and preserved thinking it works well.

    For me the issue was with opencode-agent-memory or so plugin. Disabling it made context super stable.

  9. 1 remaining item

  10. removed
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  11. ar7max commented on May 14, 2026

    @ar7max

    Yep, same

  12. nilo85 commented on May 19, 2026

    @nilo85

    I seem to have created a duplcate of this issue #28333

    Closing my issue in favor of this one, see my duplicate issue for my context if it helps =)

  13. nilo85 commented on May 19, 2026

    @nilo85
    Image

    After making a build with my PR #28352 it works like a charm! 🥳

  14. hcwhan commented on May 26, 2026

    @hcwhan

    Such an obvious problem — why does it feel impossible to move forward after all this time? I’ve seen 3 PRs with no progress.

    Does opencode not pay much attention to local LLM?

  15. nilo85 commented on May 26, 2026

    @nilo85

    Such an obvious problem — why does it feel impossible to move forward after all this time? I’ve seen 3 PRs with no progress.

    Does opencode not pay much attention to local LLM?

    Seeing that they have about 1000 open pull requests, it's an agentic coder product, people probably post craplot of vibed stuff that does not meet standards or roadmap, I bet they are quite overwhelmed and things needs to take time.

    Checkout the latest release tag, ask your LLM to apply my PR and use it now already if it's blocking you :)

  16. CWaterSon commented on Jun 25, 2026

    @CWaterSon

    Hi, Is there a chance to fix this bug (quickly)?

  17. nilo85 commented on Jun 25, 2026

    @nilo85

    @rekram1-node
    Any chance we can look at my pr #28352 which should fix the issue? I have been patient (and I understand the shear volume of PRs you get is in itself problematic)

    But the lack of reaction, to a fix to a proper bug is kind of demotivating =)

    PS: alse reconsider if #28346 should be merged (was auto closed, but AFAIK once you enable native LLM api this bug will reapear if left alone)

  18. github-actions commented on Aug 25, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions