Repository navigation
Replies: 6 comments 2 replies
What already exists for the recognition half, and exactly where it stopsYour root cause 1 is the one I can contribute to concretely, because the failure you describe has a published partial remedy: the refusal is real, the reason is named by the provider, and the harness still does not read it. The plugin:
Every rewrite keeps the provider's words verbatim and adds both numbers, so it is checkable in the session log. Where it stops, for your report specifically. It restores the gate, not the recovery. If compaction's own request is itself over the window — your root cause 1 — the plugin gets you to the call and the call still fails; the unconditional non-LLM pruner you propose is the missing piece and it is a core change, not something I can put in front of the gate from outside. Two further boundaries, stated because they matter for a CJK session like yours:
So: worth installing if the symptom you hit is "every turn 400s and it never compacts", which is what the four triggers target. Not a fix for a session that is already past the window, and not a substitute for the pruner-first ordering you propose. |
|
配置好后现象还存在吗,我发现我之前的对话也出现了这个问题 |
|
回 @workibear 的问题——答案要分两种情况,取决于你的会话卡在哪一段: 装上
对你已经出问题的旧对话,先说定心的:数据没丢。这类会话能正常加载、历史在界面里都看得见——只是发不出新请求。最稳的"恢复"是把要紧内容手动搬进新会话继续;不要反复重试同一轮(每次都在烧 token 打同一堵墙)。 两个实操建议:
|
|
The compaction-deadlock in root cause 1 is the sharp part here, and I think it's structural rather than a tuning bug: compaction is modeled as a recovery step, but the recovery step itself needs a model call sized to the surface it's trying to shrink. Once the surface is past the hard wall, every path that could rescue it — including the summary request — is already over the wall. There is no non-model floor that runs first, so the only mechanism that can shrink the surface presupposes you can send the surface. We hit the same wall and ended up treating the model window as a write-time invariant, not a recovery target. Two things that made the deadlock structurally impossible rather than just rarer:
Root cause 2 (trigger line in a unit that can land outside the real wall) is the same class of bug: the only safe trigger unit is real provider tokens with the completion reservation subtracted, because that's the only number the provider actually rejects against. We run a single-orchestrator agent OS where the context budget is an engine-owned invariant enforced at write time, with a no-model pruning floor that always runs before any summarization call. More context on SwarmAI. Discussion: T-MEM |
|
@xg-gh-25 thanks - and I agree it is structural rather than a tuning bug. Two measured data points from this side that land on your second point, because "real 1. The reservation is literal, and it is an absolute ceiling The route here advertises contextWindow 1,000,000, but refuses anything whose So on this route the reservation is subtracted literally, not heuristically. Lowering 2. A correction I owe the thread: the meter is ~1:1 here I posted earlier that the harness meter carries a ~2x calibration gap on CJK content. So 3. On append-time enforcement - one boundary I would like your view on "a surface that can't be served never gets persisted" is the half I cannot reproduce The middle one is the interesting case: 4% under the ceiling, still usable, history Which is what I keep coming back to. If the no-model floor has run and the turn still One note for anyone patching the compaction policy from outside on DSH, since it cost |
|
Second data point, as promised - same machine, same session, one config change apart. And this What changed between the two days Only Measured - the provider-reported request size on the turn compaction fired Three firings across two configurations, all within 0.1% of the line the formula predicts. What the second day actually looks like The daily path is inside the wall either way. The largest request today was Two honest caveats
And the part that has not changed The daily path was never the failure mode. The failure is the reload path: a restart Real improvement, but still a line the compaction policy cannot see - because the policy |
Uh oh!
There was an error while loading. Please reload this page.
一句话 / TL;DR
一个长时间运行的会话,会因为一次与它无关的
dsh web重启而永久报废 —— 此后每一轮都 400,且产品内没有任何手段能让它复活。最硬的证据:三个数字咬合 / The decisive numbers
contextPressure.surfaceTokens(会话原始体积)messagesdsh web重启报错原文:
根因 1 · 已经超窗的会话,压缩救不回来(死锁)
dsh-compaction-basic的文档写了溢出恢复("after a context-overflow error, it condenses and retries"),但摘要请求本身必须把整个表面发给模型。线级代理日志实拍到:一个 115 万 token 的压缩请求会被和原请求一模一样地拒掉。
建议:检测到规范溢出时,先无条件把工具结果裁剪器(
dsh-compaction-tool-result-pruner,按设计不调模型)跑到收敛;若裁剪后重放已低于路由窗口就直接重试,再考虑摘要。根因 2 · 压缩触发线用的单位,可能落在真实墙外面
触发线 =
floor(min(W × thresholdRatio, W − O − headroomTokens)),默认0.8/65536。取
W = 1e6、O = 256e3→ 678,464(计量器单位)。而中文内容在真实分词下约为计量器的 2.07 倍 → 678,464 ≈ 140 万真实 token,已经在墙外。
建议:用 provider 回报的
prompt_tokens(响应里本来就有真实值)校准计量器,或直接以"上一次真实用量"作为触发依据。根因 3 · 一次重启,静默杀死任何大会话
重启会从日志按完整原始体量重建表面。此前让它「装得下」的内存摘要、之前做过的裁剪,在第一个请求之前不会被重新建立。
建议:加载会话时把重放出的表面与路由窗口比一下;超出就在加载时跑一遍裁剪,或至少给出指名道姓的警告。
根因 4 · 绑在死会话上的定时任务静默失效,而且删不掉
dsh-schedule把每个任务绑到sessionId(deliveryMode是硬编码const: "host",没有"投递到全新会话"这个选项)。会话超窗后每次投递都以 400 失败,而用户侧什么都看不到 —— 没有横幅,任务状态也不会变成错误。我们 7 个每日检查点任务(
09:26 / 10:00 / 10:30 / 11:15 / 13:30 / 14:45 / 15:05,status: "active")就这样全瞎了,界面上还显示着正常排期。更糟的是清不掉:
schedule_delete是会话隔离的,从别的会话去删一个确实存在于$DSH_HOME/storages/schedule.json里的任务,返回:{"deleted": false, "code": "schedule_not_found"}建议:(a) 投递失败时把任务标为错误并通知;(b) 允许按 id 从任意会话删除任务。
附带 5 ·
DEFAULT_MAX_TOKENS = 256e3每次请求占掉 100 万窗口的约 24%dsh-llm-deepseek/lib/index.jsL19 / L21。provider 把这个预留算进窗口(报错原文可见256000 in the completion)。本机实测:一天 381 次请求的真实输出合计仅 661,635,平均 1,737,峰值 9,748。预留量是实测峰值的约 26 倍,直接从可用输入里挖走四分之一。
建议:默认值显著调小。
附带 6 ·
DEFAULT_CONTEXT_WINDOW = 1e6与提供方真实值 1,048,576 差 48,576数值不大,但所有派生阈值都会跟着偏。
环境 / Environment
我们本机的绕过方案(纯配置,无需改代码)
复现步骤 / Reproduction
surfaceTokens越过约 55 万计量器单位。dsh web。CONTEXT_WINDOW_EXCEEDED,并且永远这么报下去。English summary (click to expand)
Symptom. A long-running session returns HTTP 400
CONTEXT_WINDOW_EXCEEDEDand never recovers.Trigger. An unrelated
dsh webrestart. The harness rebuilds the session surface at full raw size; if that exceeds the model window, every turn fails forever. Measured: same session answered at 174,998 tokens before the restart and failed at 1,149,949 one minute after it.Root causes.
floor(min(W×thresholdRatio, W−O−headroomTokens))is evaluated in the meter's unit, which under-counts CJK by ~2.07×. A 678,464 trigger corresponds to ~1.4M real tokens — past the 1,048,576 wall.schedule_deleteis session-scoped and returnsschedule_not_foundfor a task that demonstrably exists in$DSH_HOME/storages/schedule.json.DEFAULT_MAX_TOKENS = 256e3reserves ~24% of a 1M window per request (measured real output: mean 1,737, max 9,748).DEFAULT_CONTEXT_WINDOW = 1e6vs the real 1,048,576.Workaround (config only).
maxTokens: 64000on the provider row +thresholdRatio: 0.4oncompaction-basic→ trigger 678,464 → 400,000.Repro. Grow a CJK-heavy, tool-output-heavy session past ~550K meter units → confirm it still answers → restart
dsh web→ send anything.All reactions