DeepSeek Harness · Context Engineering

Token Accounting: Replay for Decisions, Projection for Display

Two meters, each its own job—compaction decisions never trust the UI number. Core source: packages/llm/token-meter/src/usage-projection.ts.

Course goalBy the end you can explain three things: why the token count for compaction decisions and the one for UI display must be two systems; why the projectedTokens formula answers the size of the next request; and why occupancy-percent non-atomicity is a design decision—complete with a defense written into the docs.
Interactive demo · Dual gauges

Play first, then talk. Left gauge is replay measure()—compaction reads it. Right is UI projection projectedTokens—the status line shows it. A conversation advances: big tool output, compaction, model switch, new request. You'll see the gauges stay close most of the time, then openly diverge on the model switch—and look justified doing it.

Replay measure · measure()For decisions · whether to compact looks at this number
0 0%
Threshold tick 80k · denominator 100k (decision side resolves at its own boundary)
UI projection · projectedTokensFor display · status-line occupancy is this number
waiting for first usage sample
Denominator contextWindow 100k (from latest request/context record)
This non-atomicity is intentional, not a defect. Consumers that truly need precise numbers at one boundary should call ctx.tokenMeter.measure() at their own request boundary—where both values are available together—not read this projection..agents/notes/implemented/architecture/2026-07-29-projected-token-usage-and-request-context.zh.md line 35, original quote
Hit Play to start, or scroll here to auto-play.
Why two systems · two questions, two costs

Up front: the two numbers answer different questions. Compaction needs “how big would a request be right now”—accurate, may be expensive. The UI status line only needs occupancy for the user—cheap, durable, available immediately after reconnect; decimal precision doesn't matter. DSH simply builds separate roads and makes neither compromise.

Decision numbers must be accurate—expensive is fine.
Display numbers must be cheap—rough is fine.

The decision path is replay. Each ctx.tokenMeter.measure() call folds the durable log's current tail into an immutable snapshot: if the latest successful request's provider usage matches the current request envelope and isn't below its full heuristic anchor, use it as the anchor; surface (model-visible surface) changes after that are re-priced with signed deltas; with no reusable anchor, price the whole thing with a fixed heuristic.

The cost is plain: O(surface) per call—so only decision-makers like compaction call it at their own request boundary.

Source:docs/subsystems/token-meter.zh.md definitions of the two baseline kinds (usage anchor / estimated heuristic anchor).

The display path is projection. Ordinary durable session projection state with two last-writer-wins fields: pressureTokens—prompt-side size from the latest request report, input plus cache read/write, excluding output (usage-projection.ts lines 70–72); and contextWindow from the latest request/context log record. Numerator and denominator write separately—never one atomic observation.

Durable session log usage samples · surface deltas request/context records measure() replay anchor + signed delta · O(surface) each time Computed on call—answers next-request size Projection contextPressure pressureTokens · contextWindow each last-writer-wins O(1) state · ready right after restart/reconnect Compaction decision Threshold compare · overflow recovery Web / TUI status line Occupancy % · reference number Accurate, expensive Rough, cheap & durable
Teaching diagram:one durable log splits into two metering paths, each serving one consumer—no cross-talk.
Projection's trick · answers the next, not the last

pressureTokens alone is awkward: it only updates when a request reports usage, sits still during Turn streaming, and can't see compaction. Compaction replaces a big surface stretch but the status-line number doesn't move—users think compaction did nothing. So fold also carries a running surface total; what's published is the sample plus signed surface change after it. Source comments state the intent plainly: occupancy answers for the next request rather than the last one (usage-projection.ts lines 150–161 comments). Demo step 5 is that effect: compaction just hit disk, no new request yet, and the projection already dropped.

One timing detail: usage samples are stamped BEFORE the same event joins surface—so an assistant/message anchors the surface its own request saw, and the delta start doesn't skew.

Numerator and denominator aren't an atomic pair

On a model switch, the new contextWindow takes effect immediately while pressureTokens is still the previous route's sample. Occupancy is approximate until the next request reports usage. Docs call this a tradeoff, not a bug.

pressureTokens counts prompt side only

Scope is inputTokens plus cache read/write, excluding output. It describes how big the outbound request is—a different projection unit from billing total tokenUsage. Don't mix them.

Decision path doesn't read the projection

Agent Note, verbatim: nothing in the harness decides from occupancy percent—compaction reads measure() directly. However pretty the UI number is, it never enters a decision function's argument list.

Key evidence · Projection formula, original

The whole outward projection view is these 7 lines. The third spread is the lesson's punchline: pressureTokens + surfaceTokens - sampledSurfaceTokens—sample plus net surface change after sampling, floored with Math.max(0, …). If either source field is missing, that output simply doesn't appear.

packages/llm/token-meter/src/usage-projection.tslines 198–204
  view: ({ contextWindow, pressureTokens, surfaceTokens, sampledSurfaceTokens }) => ({
    ...contextWindow === undefined ? {} : { contextWindow },
    ...pressureTokens === undefined ? {} : { pressureTokens },
    ...pressureTokens === undefined || sampledSurfaceTokens === undefined
      ? {}
      : { projectedTokens: Math.max(0, pressureTokens + surfaceTokens - sampledSurfaceTokens) },
  }),
Source snapshot note:Based on the local deepseek-harness-master repo; verified against packages/llm/token-meter/src/usage-projection.ts, verified on 2026-08-13. Code blocks keep the original source text.

Two edge cases fall out of this code. First, provider returns no usage: projection-side pressureTokens stays absent, spreads two and three never appear, UI shows no occupancy (Agent Note: show occupancy only when pressure and capacity are both known); decision side is unaffected—measure() falls back to an estimated heuristic anchor and still returns a number. Second, non-atomic occupancy divergence—the doc's defense already pops in demo step 6, from Agent Note lines 29–35, titled “Context occupancy is approximate, and that is the decision.”

Side-by-side · Life with one number vs life with two
Grok Build: one number rules them all

A dedicated pure-function crate is the single source of truth: bytes/4 heuristic plus derived display math—/context, /session-info, auto-compact gates, preflight overflow checks, and every client renderer use it. Decision and display share one number by nature—never fight.

The cost: decisions also get only a coarse heuristic—4 bytes = 1 token (BYTES_PER_TOKEN = 4), one image counts 765. The crate's module comment calls itself the single source of truth for bytes/4 heuristics and derived display math. Thresholds use u32 integer percents with saturating multiply; details and source evidence are in the verified on-site lesson Token usage rate & threshold boundaries. Real provider usage doesn't enter occupancy on this path.

Source: grok-build-main crates/codegen/xai-token-estimation/src/lib.rs lines 3–19, verified 2026-08-13.

Claude Code: display accounting has its own traps

Sub-Agent progress stores token counts in two fields: input_tokens is the API's per-turn cumulative, keeping only the latest; output_tokens is per-turn delta, summed. Adding both raw double-counts input and inflates the display (manuscript ch. 6 citing restored-src ProgressTracker, study/chapters/06-task-system.md lines 169–181).

Compaction thresholds are buffer constants—13000 reserved for autocompact, 3000 for manual /compact (manuscript ch. 3 citing autoCompact.ts). Directionally, display and decision also have separate accounts—but in verified public materials we haven't seen an explicit formula that makes projection answer the next request like this.

Side by side, all three answer the same question: who uses the occupancy number, and who pays when it's wrong. Grok chose a forever-consistent coarse number; Claude Code patched display counting against double-count; DSH fully separates the two consumers and admits in docs that the display copy is approximate.

Classroom Exercise
01

Hand-walk a divergence scene

Compaction just hit disk; surface shrank from 92k to 8k; no new request yet; the user immediately switches the model from a 100k window to 200k. Answer four numbers now: UI status-line numerator and denominator—which fields; if compaction calls measure() right now, roughly what total and which anchor kind. Finally use Agent Note line 35's original sentence to explain why the two sets may differ, and what a consumer that needs precision should do.

Takeaway:Decisions use replay—measure() computes at its own boundary, accurate and expensive; display uses projection—pressureTokens plus net surface change, rough, cheap, durable. The two numbers may differ; occupancy non-atomicity is a documented design decision. When you see the UI percent, remember: no decision reads it.