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
collectSummaryAnchors (src/prune.ts, dist/index.js:136-165) sets insertAt = earliest ?? 0 where earliest is the index of the block's earliest covered message in the input messages. rebuildMessages (dist/index.js:166-189) then inserts the rendered summary — renderSummary produces role: "system" (dist/index.js:190-201) — at that index.
Whenever the covered range does NOT start at the conversation head, the anchor sits after one or more still-visible messages, and the rebuilt message list carries a system message mid-conversation:
This is not limited to multi-segment compress: any compress whose range starts after the head (the normal "fold the old stuff, keep the tail" pattern) produces it. The only safe shape is a range starting at index 0.
Note the kernel already preserves the first user message unconditionally (rebuildMessages, dist/index.js:176-179: if (index === firstUserIndex && firstUserIndex >= 0) { result.push(...); continue; }), so even a head-anchored range m00001–mNNN leaves m00001 visible — the summary lands at index 0, ahead of it, which is fine. The problem is exclusively anchors at index > 0.
In-process hosts (pi/opencode plugins) that consume core messages directly carry the mid-conversation system message into whatever wire they build.
Suggested fix
Clamp the anchor to the first user message in collectSummaryAnchors — firstUserIndex is already computed in prune() (dist/index.js:117-119) but only passed to rebuildMessages:
insertAt: Math.min(earliest??0,firstUserIndex)
This folds every summary into the leading system prefix. Semantics are unchanged: a summary is a stand-in for the folded history, and a leading system prefix is accepted by both OpenAI and Anthropic (the anthropic wire already maps non-assistant roles to user, so it is unaffected today).
Alternative considered: render summaries as role: "user" — works everywhere but changes role semantics for every host.
Interim mitigation
billion-context PR ranxianglei/billion-context#356 hoists mid-conversation system/developer messages to the leading prefix at the OpenAI wire layer (both prepareOpenai and the compress-loop adapter), so proxy users are covered without a kernel release. This issue tracks the kernel-level root fix so all hosts benefit.
What
collectSummaryAnchors(src/prune.ts, dist/index.js:136-165) setsinsertAt = earliest ?? 0whereearliestis the index of the block's earliest covered message in the input messages.rebuildMessages(dist/index.js:166-189) then inserts the rendered summary —renderSummaryproducesrole: "system"(dist/index.js:190-201) — at that index.Whenever the covered range does NOT start at the conversation head, the anchor sits after one or more still-visible messages, and the rebuilt message list carries a system message mid-conversation:
This is not limited to multi-segment compress: any compress whose range starts after the head (the normal "fold the old stuff, keep the tail" pattern) produces it. The only safe shape is a range starting at index 0.
Note the kernel already preserves the first user message unconditionally (
rebuildMessages, dist/index.js:176-179:if (index === firstUserIndex && firstUserIndex >= 0) { result.push(...); continue; }), so even a head-anchored range m00001–mNNN leaves m00001 visible — the summary lands at index 0, ahead of it, which is fine. The problem is exclusively anchors at index > 0.Impact
coreToOpenaiserializesrole: "system"verbatim at its position, so strict OpenAI-compatible backends reject every subsequent request. Reproduced in production with sglang (qwen3.8-27B):400 System message must be at the beginning.on ALL requests after the compress; the session is stuck until the provider is switched or the proxy bypassed. See 多段 compress 夹 user 消息时 T1 摘要 (role:system) 落在对话中部, sglang 报 400 "System message must be at the beginning" billion-context#355.Suggested fix
Clamp the anchor to the first user message in
collectSummaryAnchors—firstUserIndexis already computed inprune()(dist/index.js:117-119) but only passed torebuildMessages:This folds every summary into the leading system prefix. Semantics are unchanged: a summary is a stand-in for the folded history, and a leading system prefix is accepted by both OpenAI and Anthropic (the anthropic wire already maps non-assistant roles to user, so it is unaffected today).
Alternative considered: render summaries as
role: "user"— works everywhere but changes role semantics for every host.Interim mitigation
billion-context PR ranxianglei/billion-context#356 hoists mid-conversation system/developer messages to the leading prefix at the OpenAI wire layer (both
prepareOpenaiand the compress-loop adapter), so proxy users are covered without a kernel release. This issue tracks the kernel-level root fix so all hosts benefit.