Replies: 3 comments
|
Reproduced, and your expected behaviour is the right one. Driving the reported shape through the projection directly — one folded append, then a wide shadow claim, then the replacement — gives Why it can go negative at allThe subtrahend and the running total do not come from the same set. A replacement's delta is So the invariant FixmessageTokens: Math.max(0, state.messageTokens + fold.deltaTokens),Under-reporting until the total recovers is the strictly better failure mode for an estimate — and it is what you asked for in the expected-result section. Worth noting the clamp belongs on A test asserting the wire view parses — rather than just reading as zero — is worth having, since the schema is the thing that was rejecting it: expect(() => definition.wire.viewSchema.parse(definition.wire.view(state))).not.toThrow() |
|
I built a bounded reference implementation against official Thank you @S-AN-Shu for the exact failure and acceptance criteria, and @nokkies for independently reproducing the negative accumulator and identifying the fail-soft clamp direction. The patch covers two consumers of the shared signed surface fold, not only the reported field:
The regression uses a contract-valid historical compatibility sequence rather than an arbitrary shadow price: a legacy unpriced replacement expands the surface without increasing the bounded accumulator, then an adjacent built-in claim subtracts the exact estimator-priced expanded surface before a smaller replacement lands. The old reducers reach Evidence:
Reference branch: https://github.com/Jstn-1g/deepseek-harness/tree/reference/discussion-4674-nonnegative-token-projections Exact commit: Jstn-1g@fd88e82 This is a verified reference implementation, not a claim of upstream adoption. |
|
A branch carrying a fix for this is available, based directly on https://github.com/nokkies/dsh-upstream-patches/tree/fix/token-meter-shadow-pricing It stops a shadow entry pricing more than the fold it shadows. Offered as-is, no attribution wanted. Take, adapt, or ignore it freely. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
复现、预期与验收
症状
{"content":[{"startSeq":7,"endSeq":1221,"summary":"..."}]}一次覆盖 1214 条 seq。压缩本身成功(日志显示"已压缩 N 条历史记录")。
3. 压缩后模型尝试继续运行,立刻失败:
{"origin":"number","code":"too_small","minimum":0,"inclusive":true,"path":["messageTokens"],"message":"Too small: expected number to be >=0"}Web UI 显示"本轮运行失败";再次重试仍然失败(连续多轮),会话卡死。
实际结果
contextBreakdown.messageTokens等于负数,请求组装被 zod 校验拒绝。预期结果
contextBreakdown.messageTokens保持 >= 0(或退化到 0),会话继续运行。环境
@deepseek-ai/dsh0.1.1-rc.2(DSH Desktop 0.5.2 内嵌运行,Windows)根因分析
版本 0.1.1-rc.2 中
dsh-token-meter的contextBreakdown投影(打包路径lib/types/breakdown-projection.js):const tokenCount = z.number().int().nonnegative()——messageTokens必须 >= 0。apply中:messageTokens: state.messageTokens + fold.deltaTokens—— 无下限保护(clamp)。共享的 surface fold(
lib/types/surface-projection.js)走"阴影价协议":compaction/summary或compaction/prune事件 → 武装一个 claim(delta=0,记录被替换范围的影子价shadowedTokenCount)。replace事件消费 claim:deltaTokens = estimateMessage(新内容) - claim.tokens(负值),累加进messageTokens。纯数学上该值不会为负(旧累计 − 被替换范围价 + 新摘要价 = 保留部分 + 摘要 ≥ 0)。但协议存在交错窗口:claim 是单槽位状态,若被相邻事件覆盖/失效后 replace 仍消费(连续两次压缩事件、tool-result-pruner 与 compaction-basic 连续、或投影从 checkpoint 恢复后 stale claim 与新 replace 配对),delta 与累计的吸收会错位;一旦
state.messageTokens + deltaTokens < 0,zod 直接拒绝,且因为投影状态已经写入缓存,后续每轮请求都失败。大范围压缩(一次覆盖 1000+ seq)显著提高交错概率,与观察到的情况吻合。
建议修复(一处即可)
或在
tokenCount约束处允许 0 并兜底(投影缓存可重建,应 fail-soft 而不是让整轮运行失败)。验收条件
contextBreakdown.messageTokens始终 >= 0;All reactions