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
A context snapshot can publish a remainingContextTokens larger than contextBudget − effectiveInstructionTokens. That is impossible for a real prompt: the remaining headroom cannot exceed the budget left after the instructions the same snapshot reports.
Downstream effects:
syncBudgetDerivedFields (messages/budget) derives breakdown.messageTokens = max(0, contextBudget − effectiveInstructionTokens − remainingContextTokens), so the overshoot is clamped to messageTokens: 0 and hidden rather than surfaced.
LibreChat's context gauge computes used tokens as contextBudget − remainingContextTokens. That comes out below the instruction count it subtracts, so the breakdown's Messages row disappears and the percent and free space are understated (📏 fix: Keep the Context Gauge's Used Count Above Its Own Breakdown LibreChat#16368 makes the client robust to it).
Evidence
The last 400 persisted snapshots on a live deployment (LibreChat dev, @librechat/agents 3.9.6): 23 violate the invariant. Every one has breakdown.messageTokens: 0. Samples:
contextBudget
effectiveInstructionTokens
budget − instructions
remainingContextTokens
overshoot
947,625
12,887
934,738
936,899
2,161
947,625
7,800
939,825
942,151
2,326
947,625
15,538
932,087
935,020
2,933
It also reproduces deterministically in LibreChat's e2e mock: a first turn whose whole system prompt is a 57-token memory block, with a fake model reporting 2 input tokens, publishes contextBudget − remainingContextTokens = 2.
Candidates (not yet pinned)
From reading the built dist/esm:
pruneMessages, main path (messages/prune, around the rawRemaining computation): the remaining count is computed in raw token space, scaled by calibrationRatio, and clamped with Math.min(pruningBudget, …), not pruningBudget − currentInstructionTokens. Rounding across the raw/calibrated conversion and the reclaimed orphan tokens are added after the instruction subtraction.
The emergency-truncation retry recomputes the context with getMessagesWithinTokenLimit in calibrated space but the returned remaining still derives from the first, raw-space call (initialRemainingContextTokens).
currentInstructionTokens can switch between the calibrated provider overhead (bestInstructionOverhead) and the estimate between calls, so a remaining carried as a baseline into contextPressureMeter.measure may have been measured against the other value.
The ~2–3K overshoot on real traffic is larger than rounding alone would explain, which points at 1 (reclaimed tokens) or 3.
Expected
Every published snapshot satisfies remainingContextTokens ≤ contextBudget − effectiveInstructionTokens, with remainingContextTokens and effectiveInstructionTokens taken from the same measurement. A cheap guard at publish time (where syncBudgetDerivedFields runs) plus a debug log when it trips would stop the silent clamp and show which path produces it.
Separately, LibreChat's e2e fake model reports input_tokens without the system prompt; real providers count it. That should be fixed in LibreChat's harness so calibration sees realistic input.
What breaks
A context snapshot can publish a
remainingContextTokenslarger thancontextBudget − effectiveInstructionTokens. That is impossible for a real prompt: the remaining headroom cannot exceed the budget left after the instructions the same snapshot reports.Downstream effects:
syncBudgetDerivedFields(messages/budget) derivesbreakdown.messageTokens = max(0, contextBudget − effectiveInstructionTokens − remainingContextTokens), so the overshoot is clamped tomessageTokens: 0and hidden rather than surfaced.contextBudget − remainingContextTokens. That comes out below the instruction count it subtracts, so the breakdown's Messages row disappears and the percent and free space are understated (📏 fix: Keep the Context Gauge's Used Count Above Its Own Breakdown LibreChat#16368 makes the client robust to it).Evidence
The last 400 persisted snapshots on a live deployment (LibreChat dev,
@librechat/agents3.9.6): 23 violate the invariant. Every one hasbreakdown.messageTokens: 0. Samples:It also reproduces deterministically in LibreChat's e2e mock: a first turn whose whole system prompt is a 57-token memory block, with a fake model reporting 2 input tokens, publishes
contextBudget − remainingContextTokens = 2.Candidates (not yet pinned)
From reading the built
dist/esm:pruneMessages, main path (messages/prune, around therawRemainingcomputation): the remaining count is computed in raw token space, scaled bycalibrationRatio, and clamped withMath.min(pruningBudget, …), notpruningBudget − currentInstructionTokens. Rounding across the raw/calibrated conversion and the reclaimed orphan tokens are added after the instruction subtraction.getMessagesWithinTokenLimitin calibrated space but the returned remaining still derives from the first, raw-space call (initialRemainingContextTokens).currentInstructionTokenscan switch between the calibrated provider overhead (bestInstructionOverhead) and the estimate between calls, so a remaining carried as a baseline intocontextPressureMeter.measuremay have been measured against the other value.The ~2–3K overshoot on real traffic is larger than rounding alone would explain, which points at 1 (reclaimed tokens) or 3.
Expected
Every published snapshot satisfies
remainingContextTokens ≤ contextBudget − effectiveInstructionTokens, withremainingContextTokensandeffectiveInstructionTokenstaken from the same measurement. A cheap guard at publish time (wheresyncBudgetDerivedFieldsruns) plus a debug log when it trips would stop the silent clamp and show which path produces it.Separately, LibreChat's e2e fake model reports
input_tokenswithout the system prompt; real providers count it. That should be fixed in LibreChat's harness so calibration sees realistic input.