Repository navigation
Description
Summary
In the OpenCode plugin lane, a one-shot host state notice was folded into a compress summary. The previous state notice stayed visible (it sits next to the protected head of the session), so the model kept obeying Plan-mode restrictions for 20+ turns after the session had already switched to build.
This is not "add OpenCode plan-mode support". It is a state-inversion class: any one-shot host notice carrying state (agent / mode / permission / model switch) can be folded away, while the old state remains visible forever. The model then has no way to detect that it is acting on stale instructions.
Environment
- billion-context 0.1.211 (
dist/build-info.json→ commite226544f) - host: OpenCode v2, Linux x64
- lane: opencode plugin (the agent itself calls the
compresstool) - session:
ses_e6911d125ffe3IFBWXRLkZUIre - model:
deepseek-v4.1-flash-1017(likely irrelevant, listed for completeness)
Timeline (UTC+8, 2026-11-01)
| time | seq | message |
|---|---|---|
| 19:45:54 | 5 | synthetic: <system-reminder>You are in Plan mode. ...</system-reminder> |
| 19:49:27 | 176 | synthetic: <system-reminder>You are NO LONGER in Plan mode. The previous Plan restrictions no longer apply. Any Plan mode instructions from earlier in this conversation are no longer active.</system-reminder> |
| 19:49:27 | — | compress issued, folding the range that contained seq 176 |
| 19:49 → 20:13 | — | 20+ turns executed under plan-mode constraints ("do not modify files", "switch to the build agent first") while session_v2.agent was already build |
Evidence for the last row: sqlite3 ~/.local/share/opencode/opencode.db "select id, agent from session_v2 where id='ses_e6911d125ffe3IFBWXRLkZUIre'" → build, while the model was still refusing to run commands on plan-mode grounds.
Why this is worse than losing a history line
The result is not a missing message. It is an inverted state:
- the old state (
You are in Plan mode) is re-surfaced every turn because it sits at the head of the conversation, adjacent to the bootstrap/first user message; - the only message that cancels it is a single one-shot notice in the middle of the conversation;
- nothing re-emits it after the fold, so the loss is unrecoverable for the rest of the session.
The model has no signal that it is out of date. It keeps refusing work, and every correction from the user looks like a misunderstanding.
Root cause (partly inferred)
Same family as #4921 and #5173:
- No class rule exists.
grep -c "system-reminder" dist/index.jsreturns0in 0.1.211. There is no detector for host-injected state notices at all. - Protection only covers a few shapes. Per The "most recent user message" slot in the protected zone was preempted by a system-injected prompt, causing the just-is | 保护区「最近一条 user 消息」被系统注入提示抢占,导致刚下达的用户指令被压缩 #5173,
computeProtectedRefstakes the most recentrole === "user"message andisSyntheticOrPrunedexcludes only summary headers and already-covered messages. A synthetic reminder in the middle of a range qualifies for folding. - One-shot semantics. OpenCode emits the "NO LONGER" notice exactly once. Unlike
Instructions from:baselines ([Bug][dsh] 0.1.208 · dshWorkspaceInstructionsPath only recognizes uppercase "Instructions from:", leaving rule update/ad | [Bug][dsh] 0.1.208 · dshWorkspaceInstructionsPath 只认大写 "Instructions from:",规则更新/新增/删除消息因此不设防——折掉后模型留着过期基线,更正消失 #4921), there is no re-emission to recover from, so a single fold is permanent.
I did not trace the exact fold path inside the kernel; points 1 and 3 are measured on 0.1.211, point 2 is quoted from #5173.
Repro
- Start an OpenCode session in the plan agent.
- Run it long enough that at least one
compresshappens after the switch. - Switch the session to the build agent (host injects
You are NO LONGER in Plan mode). - Issue a
compresscovering a range that contains that notice (it is mid-conversation, not the most recent user message). - After the fold, ask the model whether it is in plan mode, or simply ask it to edit a file. It still refuses on plan-mode grounds, even though
session_v2.agentisbuild.
Suggested fix
A. Minimal, reuses the existing durable mechanism. Treat one-shot state notices as durable, ref=BLOCKED, the same way the Instructions from: baseline is protected. They are cheap to keep: one short message per state change.
B. Fold-safe carry. When a range containing such a notice is folded, force a verbatim state line into the summary (e.g. STATE: agent=build @ m00231), so the state change survives regardless of protection.
C. Host-agnostic detector. A message whose body matches a state-change shape (mode / permission / agent / plan / model, positive or negative) is never folded. This is what #4921 asks for at the Instructions from: level, generalized.
A + C is the smallest combination that removes the class rather than the instance.
Related
- [Bug][dsh] 0.1.208 · dshWorkspaceInstructionsPath only recognizes uppercase "Instructions from:", leaving rule update/ad | [Bug][dsh] 0.1.208 · dshWorkspaceInstructionsPath 只认大写 "Instructions from:",规则更新/新增/删除消息因此不设防——折掉后模型留着过期基线,更正消失 #4921: rule-update message folded, stale baseline survives, model follows outdated rules
- The "most recent user message" slot in the protected zone was preempted by a system-injected prompt, causing the just-is | 保护区「最近一条 user 消息」被系统注入提示抢占,导致刚下达的用户指令被压缩 #5173: protected "most recent user message" slot preempted by a host-injected reminder
Activity
ranxianglei commentedon Nov 1, 2026
(ai: vllm-qwen/qwen3.8-27b)
标签:bug / opencode / scope/kernel / p2(缺陷;opencode 车道;src 侧折叠决策面;与同族 #4921/#5173 一致定级——有绕行但体验破坏明显)
查重:不重复(同族不同机制,已互链)。与 #4921、#5173 是同一症状族(宿主注入的状态载体被折掉 → 模型按过期状态行事),但机制各不相同:#4921 是既有 dsh 检测器的大小写盲区、#5173 是保护区「最新用户消息」槽位被合成消息抢占,本条是一类载体完全没有保护规则(
grep -c "system-reminder" dist/index.js= 0 属实)。另查了 open/closed 全量:无其他条目针对 opencode plan-mode 通知;在途的 PR #4922/#4927 只修 #4921(dsh 车道),不覆盖本条。审核结论(三步已过,证据如下):
<system-reminder>\nYou are in Plan mode. Discuss the plan … write them only in:\n<path>\n… Tell them they need to switch agents.\n</system-reminder><system-reminder>\nYou are NO LONGER in Plan mode. The previous Plan restrictions no longer apply. Any Plan mode instructions from earlier in this conversation are no longer active.\n</system-reminder>并把整个二进制的
<system-reminder>模板族枚举了一遍:本构建里一次性状态通知就这一对(其余如 "Note: The user opened the file…" 是逐轮 UI 事件,可折)。所以检测面有界、不需要猜。src/durable-message-guards.ts把宿主注入的状态载体投影成虚拟工具路径durable/<name>,走 kernel 的protectedLatestTools: ["durable/*"]策略),但目前注册表里只有 dsh 车道(KDD release v0.1.2 #9 证据白名单纪律)。opencode 车道从未登记过这个载体 → 零保护。durable/plan-mode。关键语义:latest-per-identity 下只有最近一条声明被钉住、更早的可折——切到 build 后 "NO LONGER" 通知成为受保护活副本,而头部那条 "in Plan mode" 通知反而变得可折(事故里它正是靠位置侥幸存活)。这同时消掉「旧的可见、新的没了」和「两条矛盾声明都可见」两个方向。先例同形:dsh runtime-context 的 populated/cleared 双形态共用一个身份(dsh 车道第三个载体:runtime-context 快照(沙箱/审批策略)同属一次性 baseline user 消息,#2428 的文本标记守卫覆盖不到 #2481)。B(摘要携带)不需要:当前状态声明恒可见时,摘要再抄一份状态是冗余。C 不做成「全车道通用 detector」:那会违反 KDD release v0.1.2 #9(对没有此契约的宿主硬钉任意用户散文),类级保护通过 durable 机制按车道落地就是正确的「通用」粒度。动手:host 侧修改(
src/durable-message-guards.ts+ 测试),不动 kernel/。回归测试按事故场景端到端复现(enter → 增长 → compress 覆盖 exit 通知 → 断言重建 wire 上最新状态可见、旧状态被折;未注册车道对照组仍照旧折叠)。完成后回 PR 链接。ranxianglei commentedon Nov 1, 2026
[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b)
修好了,PR 已开:#5464(Closes #5458)。
八问
durable/plan-mode。默认 latest-per-identity 策略下只有最新一条声明被钉住(BLOCKED ref,永不折叠),被取代的旧声明正常折叠,可见状态永远收敛到当前状态(形状先例同 dsh runtime-context 的 populated/cleared 对,dsh 车道第三个载体:runtime-context 快照(沙箱/审批策略)同属一次性 baseline user 消息,#2428 的文本标记守卫覆盖不到 #2481)。另加一笔:把该分类器登记进 [Performance][dsh] Mega-sessions pay an ever-growing O(full-history) fold-reconcile tax every turn: expected host churn (one-shot carrier retirement, image/offload retroactive rewrites) misclassified as drift | 大会话每轮付不断增长的 O(全史) fold-reconcile 税:合法宿主变更(o… #5142 的 fold-drift 记账面,否则收敛性折叠本身会把"被取代通知永久退役"误记成每轮漂移。<system-reminder>模板族,确认这一类在该车道就这两条,注释里留了依据。withDurableProtection已有的durable/*默认上。标签:bug / opencode / scope/kernel / p2(opencode 车道一次性状态通知被折叠致会话持旧状态;有绕行故 p2)。
查重交叉链接:与 #4921(dsh 指令更新的大小写漏配)、#5173(合成 user 消息抢占保护槽位)同属"宿主注入的状态载体被折叠 → 模型持旧状态"家族,但机制不同(本条是整类缺失,那两条是既有机制的缝隙)且车道不同,保持各自独立处理,均不关闭。
验证状态:已 rebase 到当前 master(v0.1.211,与你事故版本一致);本地 typecheck/build 干净,定向套件 53/53 绿(新回归测试 5 个场景 + 六个相关兄弟套件 + issue5142 记账套件);全量回归以 CI 为准。等你验收合并。
ranxianglei commentedon Nov 1, 2026
[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b)
补充:PR #5464 的 CI 已全部绿——主 CI(guards + core 测试矩阵 + build)success,全部 e2e 车道 success,含与本改动直接相关的
e2e-native-opencode(真实 opencode 插件车道 vs 确定性 fake upstream)。mergeable=true,状态 blocked 仅因等待人工合并。等你验收。fix: pin OpenCode one-shot plan-mode state notices as durable carriers (
fix(#5458): account folded plan-mode notice retirement as expected re…