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
{{ message }}
Repository navigation
<system-reminder> keeps moving, causing unnecessary prompt processing in llama.cpp #23595
For some reason, OpenCode keeps moving <system-reminder> around. As a result, the prompt history changes, so the cache cannot work correctly. In llama.cpp, this leads to a lot of unnecessary prompt processing time being wasted.
Would it be possible to keep <system-reminder> in the same place once it has been inserted for the first time? If something changes, you could add a new <system-reminder> instead.
Switch to "build" mode to trigger <system-reminder>
Tell opencode to do something complex, like use lots of tools and write lots of files
Use any next prompt (like "thank you") and observe
<system-reminder> moves from the previous prompt to the next prompt and all the tokens (thousands!) between prompts must be processed again because cache is broken
Screenshot and/or share link
example:
1184: <|im_start|>user
1185: add one comment to it
1186: <system-reminder>
1187: Your operational mode has changed from plan to build.
1188: You are no longer in read-only mode.
1189: You are permitted to make file changes, run shell commands, and utilize your arsenal of tools as needed.
1190: </system-reminder><|im_end|>
1191: <|im_start|>assistant
1192: <think>
1193: The user wants me to add a comment to the `build_norm_gated` function. I should add a descriptive comment before the function.
1194: </think>
1195:
1196: <tool_call>
then it moves after another prompt:
1213: <|im_start|>assistant
1214: Added.<|im_end|>
1215: <|im_start|>user
1216: thank you
1217: <system-reminder>
1218: Your operational mode has changed from plan to build.
1219: You are no longer in read-only mode.
1220: You are permitted to make file changes, run shell commands, and utilize your arsenal of tools as needed.
1221: </system-reminder><|im_end|>
1222: <|im_start|>assistant
1223: <think>
If your issue is distinct (e.g. it affects all turns, not just queued messages), please clarify that in a comment so maintainers can track it separately.
I also see this issue. I am using Deepseek v4 Flash hosted on ik_llama.cpp (a CPU-inference fork of llama.cpp) with OpenCode. I'm currently running with 300k context for this model, so what I'm about to describe has nothing to do with compaction because I'm nowhere near the limit in this project at the moment.
Here's exactly what I am seeing right now in a current multi-hour project I'm working on in build mode:
At the start of EACH STEP, the context is invalidated back down to 40986 tokens (down from about ~65k tokens at this point in the run)
My LLM hosted on ik_llama.cpp needs to re-process those ~25k tokens of context.
Only then can it execute the current work, generating new tokens, running, a tool, etc.
Then for the next step this all repeats.
It's making the run at least an order of magnitude slower than it would otherwise be, because of this cache invalidation.
The fact that every step of the build agent invalidates back to the exact same point in the cache feels like a hint about what's going on here, but I don't know what it is (hopefully the creator of this issue report has nailed it).
EDIT TO ADD: Note that I had a length "plan" mode run before this "build" mode situation I described above. I can't help but wonder if the plan mode resulted in those 40986 tokens that the build mode keeps reverting to in the cache and having to reprocess everything after that for some reason.
If helpful to anyone else, the following patch resolved this issue for me (I'm not familiar enough with this code base to feel comfortable submitting a PR for it, but this got it working for me):
--- a/packages/opencode/src/session/reminders.ts
+++ b/packages/opencode/src/session/reminders.ts
@@ -24,27 +24,32 @@ export const apply = Effect.fn("SessionReminders.apply")(function* (input: {
if (!userMessage) return input.messages
if (!flags.experimentalPlanMode) {
- if (input.agent.name === "plan") {
- userMessage.parts.push({
- id: PartID.ascending(),
- messageID: userMessage.info.id,
- sessionID: userMessage.info.sessionID,
- type: "text",
- text: PROMPT_PLAN,
- synthetic: true,
- })
- }
- const wasPlan = input.messages.some((msg) => msg.info.role === "assistant" && msg.info.agent === "plan")
- if (wasPlan && input.agent.name === "build") {
- userMessage.parts.push({
- id: PartID.ascending(),
- messageID: userMessage.info.id,
- sessionID: userMessage.info.sessionID,
- type: "text",
- text: BUILD_SWITCH,
- synthetic: true,
- })
- }
+ const assistantMessage = input.messages.findLast((msg) => msg.info.role === "assistant")
+
+ // Only emit a reminder at the moment a mode change actually happens, and
+ // persist it onto exactly the user message that crossed the boundary.
+ // Anchoring the reminder in place (rather than re-attaching it to the
+ // newest user message every turn) keeps the serialized history
+ // byte-stable across turns, so the provider's prompt cache stays valid.
+ const switchToBuild = input.agent.name === "build" && assistantMessage?.info.agent === "plan"
+ const enterPlanMode = input.agent.name === "plan" && assistantMessage?.info.agent !== "plan"
+ const reminder = switchToBuild ? BUILD_SWITCH : enterPlanMode ? PROMPT_PLAN : null
+ if (!reminder) return input.messages
+
+ const alreadyAdded = userMessage.parts.some(
+ (p) => p.type === "text" && p.text === reminder && p.synthetic,
+ )
+ if (alreadyAdded) return input.messages
+
+ const part = yield* sessions.updatePart({
+ id: PartID.ascending(),
+ messageID: userMessage.info.id,
+ sessionID: userMessage.info.sessionID,
+ type: "text",
+ text: reminder,
+ synthetic: true,
+ })
+ userMessage.parts.push(part)
return input.messages
}
After this change, when switching from "plan" to "build" mode, there is one big reprocessing of the context, but after that each build turn does NOT reprocess everything in the context since the switch. I'm assuming that initial big reprocess is just how opencode works (presumably freeing up part of the context for build mode to use now that plan is done?).
For me, this speeds up development massively since each turn doesn't have to waste so much time reprocessing context.
After testing more with the patch I previously shared, I see now that there are still other situations in opencode workflows where something happens to the context such that my model's cache has to go back and reprocess on each turn of the agent (and this compounds once it starts happening, each turn of the agent adds a bit more that needs to be reprocessed on subsequent turns).
However, the patch above does seem to significantly improve things around the plan/build mode switch at least.
Also noteworthy: after a compaction event, any compounded context resetting goes away and the agent is back to a good / fast state, at least for a while.
Fully resolving this issue will result in massive overall speedups / token reductions for wasted prefill on what should be cached context.
Activity
github-actions commentedon Apr 20, 2026
This issue might be a duplicate of existing issues. Please check:
<system-reminder>being placed inconsistently across turns, causing KV cache invalidation, which is very similar to what you're reporting.If your issue is distinct (e.g. it affects all turns, not just queued messages), please clarify that in a comment so maintainers can track it separately.
qiujunhan commentedon Jun 14, 2026
#32246 When updating agents.md for the qwen3.6 27b model, it always triggers prompt processing at 100%.
jacekpoplawski commentedon Jun 14, 2026
But I think this is expected, AGENTS.md is probably in the beginning
VanceVagell commentedon Aug 1, 2026
I also see this issue. I am using Deepseek v4 Flash hosted on ik_llama.cpp (a CPU-inference fork of llama.cpp) with OpenCode. I'm currently running with 300k context for this model, so what I'm about to describe has nothing to do with compaction because I'm nowhere near the limit in this project at the moment.
Here's exactly what I am seeing right now in a current multi-hour project I'm working on in build mode:
It's making the run at least an order of magnitude slower than it would otherwise be, because of this cache invalidation.
The fact that every step of the build agent invalidates back to the exact same point in the cache feels like a hint about what's going on here, but I don't know what it is (hopefully the creator of this issue report has nailed it).
EDIT TO ADD: Note that I had a length "plan" mode run before this "build" mode situation I described above. I can't help but wonder if the plan mode resulted in those 40986 tokens that the build mode keeps reverting to in the cache and having to reprocess everything after that for some reason.
VanceVagell commentedon Aug 1, 2026
If helpful to anyone else, the following patch resolved this issue for me (I'm not familiar enough with this code base to feel comfortable submitting a PR for it, but this got it working for me):
After this change, when switching from "plan" to "build" mode, there is one big reprocessing of the context, but after that each build turn does NOT reprocess everything in the context since the switch. I'm assuming that initial big reprocess is just how opencode works (presumably freeing up part of the context for build mode to use now that plan is done?).
For me, this speeds up development massively since each turn doesn't have to waste so much time reprocessing context.
VanceVagell commentedon Aug 1, 2026
After testing more with the patch I previously shared, I see now that there are still other situations in opencode workflows where something happens to the context such that my model's cache has to go back and reprocess on each turn of the agent (and this compounds once it starts happening, each turn of the agent adds a bit more that needs to be reprocessed on subsequent turns).
However, the patch above does seem to significantly improve things around the plan/build mode switch at least.
Also noteworthy: after a compaction event, any compounded context resetting goes away and the agent is back to a good / fast state, at least for a while.
Fully resolving this issue will result in massive overall speedups / token reductions for wasted prefill on what should be cached context.
VanceVagell commentedon Aug 3, 2026
At least for Deepseek v4 Flash (where I'm seeing this issue), this insight seems relevant: https://www.reddit.com/r/LocalLLaMA/comments/1vdbgw5/psa_for_deepseekv4flash0731_users_dont_blow_out/
TL;DR the user found that the "system" role gets interleved in a way that invalidates cache, whereas the "latest_reminder" does not.
"latest_reminder" is documented here: https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash/blob/main/encoding/README.md
github-actions commentedon Oct 3, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.
frontend: "system_reminders": "tail" moves a client's system-reminderâĶ