DSH / Atlas
2026-08-06implementedbug-fix

unpriced surface replacements fold neutrally

未计价的表层替换以中性方式折叠

The `contextPressure` and `contextBreakdown` projections keep a running surface-token total plus at most one pending shadow-price claim, so their persisted checkpoints stay O(1) over a session's life. Current replace producers append a `compaction/summary` or `compaction/prune` metering event immediately before the replacement; its `shadowedTokenCount` prices the exact replaced range, and `foldSurfaceProjection` turn

English

Problem

The contextPressure and contextBreakdown projections keep a running surface-token total plus at most one pending shadow-price claim, so their persisted checkpoints stay O(1) over a session's life. Current replace producers append a compaction/summary or compaction/prune metering event immediately before the replacement; its shadowedTokenCount prices the exact replaced range, and foldSurfaceProjection turns that into the signed delta.

Sessions recorded before the shadow-price protocol log replacements with no adjacent metering event. The O(1) state cannot reconstruct the replaced range's price, and the fold treated every unpriced replacement as a contract violation and threw — so replaying such a session died at its first replacement (token surface: replace at seq … has no adjacent shadow price), leaving the session permanently unopenable.

Decision

A replace that arrives with no armed claim folds price-neutrally: foldSurfaceProjection returns deltaTokens: 0, pricing the replaced range as if it had cost exactly what its replacement costs, and replay continues. A claim expired by an intervening event reaches the same neutral path, since the fold cannot distinguish it from a log that never metered.

An armed claim naming a different range still throws. There the metering event was adjacent, so the producer wrote contradictory adjacent events — a live shadow-price contract violation, not historical data, and it must fail loud rather than let the total drift silently.

Both projections share the one fold, so neither gains state fields nor bumps its stateVersion. surface-fold.ts and ctx.tokenMeter.measure() are unaffected: they hold the per-node priced surface and never needed the claim protocol.

Alternatives considered

Keep throwing. Preserves the strict producer contract, but every pre-protocol session stays permanently unreplayable, and the projections exist to serve replay.

Persist the full priced surface in the projection state. Could price any replaced range exactly, but grows the checkpoint by one node per model-visible message without bound — defeating the O(1) constraint the shadow-price protocol exists to preserve (see the context-meter note).

Consequences

An unpriced replacement holds the total still instead of shrinking it, so the compacted-away span stays counted: contextBreakdown.messageTokens retains the overcount, and contextPressure.projectedTokens overestimates occupancy only until the next usage sample re-anchors it, because that figure tracks movement since the sample rather than the absolute level. The error direction is safe — overestimating occupancy at worst invites an earlier compaction.

The loud failure survives where it still means something: a range-mismatched adjacent claim is a current producer bug and still throws.

Testing

packages/llm/token-meter/tests/context-breakdown-projection.spec.ts pins the neutral fold for the no-claim and expired-claim replacements, the throw for a mismatched claim, and the exact pricing for a matched one. packages/llm/token-meter/tests/token-usage-projection.spec.ts pins contextPressure holding still across an unpriced replacement.

中文

问题

contextPressurecontextBreakdown 两个投影只维护一份滚动累计的表层 token 总量,外加至多一条待结算的影子价格(shadow price)声明,因此其持久化检查点在会话整个生命周期内保持 O(1)。当前的替换生产方会紧贴在替换之前追加一条 compaction/summarycompaction/prune 计量事件;其 shadowedTokenCount 对被替换区间精确计价,foldSurfaceProjection 再把它换算成有符号增量。

影子价格协议引入之前录制的会话,其日志中的替换没有相邻的计量事件。O(1) 状态无法重建被替换区间的价格,而折叠此前把每一次未计价替换都当作约定违规并抛出异常,于是回放这类会话会在第一处替换就中断(token surface: replace at seq … has no adjacent shadow price),会话从此永远无法打开。

决策

到达时没有已就位声明的替换以价格中性的方式折叠:foldSurfaceProjection 返回 deltaTokens: 0,相当于把被替换区间计价为恰好等于其替换内容的成本,回放随即继续。因中间插入的事件而过期的声明也走同一条中性路径,因为折叠无法把它与从未计量过的日志区分开。

已就位但指向另一个区间的声明仍会抛出异常。此时计量事件确实相邻,说明生产方写入了互相矛盾的相邻事件:这是现行影子价格约定的违规,不是历史数据,必须响亮失败,而不能任由总量悄然漂移。

两个投影共用同一个折叠,因此二者都不新增状态字段,也不提升 stateVersionsurface-fold.tsctx.tokenMeter.measure() 不受影响:它们持有逐节点的已计价表层,本来就不需要声明协议。

备选方案

**维持抛出异常。**保住了严格的生产方约定,但协议之前的每个会话都将永远无法回放,而投影本就是为服务回放而存在的。

**在投影状态中持久化完整的已计价表层。**可以对任意被替换区间精确计价,但检查点会随每条模型可见消息各增加一个节点、无上限地增长,恰恰破坏了影子价格协议所要守住的 O(1) 约束(见上下文仪表的 Agent Note)。

影响

未计价的替换让总量保持不动而不是缩小,因此被压缩(compaction)掉的区段仍被计入:contextBreakdown.messageTokens 保留这部分多计的量;contextPressure.projectedTokens 会高估占用率,但只持续到下一个用量样本重新锚定为止,因为该数字追踪的是自样本以来的增减,而非绝对水平。误差方向是安全的:高估占用率最坏不过是招致一次更早的压缩。

响亮失败保留在它仍有意义的地方:区间不匹配的相邻声明是现行生产方的缺陷,仍会抛出异常。

测试

packages/llm/token-meter/tests/context-breakdown-projection.spec.ts 钉住了无声明与声明过期两种替换的中性折叠、声明区间不匹配时的抛出异常,以及声明匹配时的精确计价。packages/llm/token-meter/tests/token-usage-projection.spec.ts 钉住了 contextPressure 在一次未计价替换前后保持不动。