Auto-compaction destroys history on a bodyless HTTP 400 (misclassified as CONTEXT_WINDOW_EXCEEDED, bypasses thresholdRatio) #5263
bochen2029-pixel
started this conversation in
General
Replies: 1 comment
|
会话才用了 ~4% 的上下文窗口,一次空 400 就把 120 条历史(~72k tokens)不可逆地摘要掉——这不是使用姿势问题,是压缩误判加绕过阈值的组合拳。官方修复合并前,被摘要掉的历史在活动会话里是回不来的。 我的兜底做法:定时备份让每个时点的完整历史都有副本。真被误压了, dsh plugin --profile web add @xiaoyuyu6420/dsh-backup
/backup auto 12 # 每12小时自动备份,重启不断
/backup restore latest --dry-run备份不修 bug,但它把「历史被算法误删」从不可逆变成可回退。要求 DSH 0.1.1-rc.2 或兼容版本(安装时会校验 peer)。 |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Summary
A provider returning HTTP 400 with an empty body is classified as
CONTEXT_WINDOW_EXCEEDED, and the compaction engine's overflow-recovery path acts on that classification while deliberately bypassingthresholdRatio. The result is that a session at ~4% of its context window had 120 history items (~72k tokens) irreversibly summarised away, after a request in which zero tokens were processed.The misclassification originates in a Cerebras-specific workaround inside the vendored
@earendil-works/pi-ai, which is applied to every provider.Two independent design issues combine here; either alone would be survivable.
Impact
Conversation history is destroyed with no confirmation, no undo, and a misleading cause. The user sees
Context compactedand a summary checkpoint replacing real turns.Because the trigger is a string match on an error message, any provider that emits a bodyless 400 for any unrelated reason (payload rejected at an ingress, malformed request, gateway/proxy error, auth edge case) can silently trigger it — at any context size, including an almost-empty session.
Root cause
1. The classifier: a vendor-specific pattern applied globally
node_modules/@earendil-works/pi-ai/dist/utils/overflow.js, inOVERFLOW_PATTERNS:isContextOverflow()case 1 testsmessage.errorMessageagainst that whole list wheneverstopReason === 'error'. The list is not scoped per provider, so a workaround for one backend's quirk decides overflow for all of them.Notably the same file already carries a
NON_OVERFLOW_PATTERNSexclusion list, added because AWS Bedrock throttling text ("Too many tokens, please wait…") was matching/too many tokens/i. So this class of false positive is known; this is another instance of it.A bodyless 400 carries no evidence whatsoever. There is nothing in it that indicates a context bound was exceeded.
2. dsh trusts that verdict, then bypasses its own safety threshold
packages/llm/llm-pi-ai/src/stream.ts:dsh's own
isContextWindowExceededErrorpatterns (packages/llm/llm/src/error.ts) are narrow and well-scoped — they require "context length/window" adjacent to an exceed/overflow verb, and correctly do not match a bodyless status line. The false positive enters only throughpiAiOverflow.Then
packages/compaction/compaction-basic/src/index.tsreacts:The
'context-overflow'trigger is documented as bypassing the normal threshold, which is reasonable if the classification is trustworthy. Here it meansthresholdRatio(default0.8) provides no protection at all: the pressure path was never consulted.Evidence
Session event trail,
provider/model= a self-hosted OpenAI-compatible endpoint,contextWindow: 1048576:The session was at roughly 4% of its 1M window.
thresholdTokenswould have beenfloor(1048576 * 0.8)≈ 838,860.Note line 32488:
inputTokens: 0. The request was rejected before any tokens were processed.Suggested fixes
1. A zero-token request cannot be a context overflow. This is the cheapest and strongest guard. If the provider reports
usage.input == 0, nothing was submitted for processing, so "the input was too large" is incoherent. Refusing to classify overflow in that case would have prevented this entirely, independent of any regex.2. Scope provider-specific patterns to their provider. The Cerebras rule should apply when talking to Cerebras. As written, one backend's quirk is every backend's behaviour. (Filed separately upstream may be more appropriate — happy to do so if preferred.)
3. Don't let error-text heuristics drive irreversible actions. The usage-arithmetic cases in
isContextOverflow(cases 2 and 3 —input > contextWindow) are sound because they are measurements. Case 1 is a guess. A guess is fine for choosing a retry strategy; it should not be sufficient to discard conversation history. One minimal change, entirely inside dsh:4. Consider making automatic compaction confirmable. Compaction is irreversible and lossy. An opt-in "ask before compacting" policy — reusing the existing
ctx.approval.request()frompackages/interaction/user-approval, which already fails closed and writes an audit pair — would make the entire class of misclassification harmless rather than destructive. Users who want the current behaviour keep it; users who value their history can require a decision.Workaround for anyone hitting this
compaction-basicgates both automatic triggers (pressure and overflow) on one flag, so in the profile patch layer ($DSH_HOME/profiles/<profile>/cordis.patch.yml):/compactremains available on demand. Requires a restart, since profile patches are applied at boot.Environment
0.1.2-alpha.2(developer preview)@earendil-works/pi-ai0.84.2api: openai-completions, 1M context window declaredHappy to open a PR for suggestion 3 (a one-line change plus a regression test asserting that a bodyless 400 with zero usage is not classified as overflow), and/or to raise the pattern-scoping issue upstream with
pi-ai. Analysis was done by reading the session event log and the vendored library; the reproduction is the event trail above rather than a synthetic test case.All reactions