Skip to content

Shrink token accounting: static UTF-8-byte estimate, complete-usage-only bookkeeping, tool exports, stale tests - #12

Merged
nullcache merged 3 commits into
minfrom
fix/calibration-usage-followups
Aug 17, 2026
Merged

Shrink token accounting: static UTF-8-byte estimate, complete-usage-only bookkeeping, tool exports, stale tests#12
nullcache merged 3 commits into
minfrom
fix/calibration-usage-followups

Conversation

@nullcache

Copy link
Copy Markdown
Owner

All four round-4 findings confirmed and fixed in one themed PR (they are all follow-ups to the previously merged review fixes):

  1. Calibration formula: measure = messageEstimate × ratio + fixedEstimate, with observe deriving the ratio from real − fixed. The old blended form scaled the English fixed overhead by the conversation's CJK rate (~565 vs a true ~232 after compression). The existing four calibration tests pass unchanged — proof they only covered ratio≈1 — and a mixed-rate regression test is added.
  2. Usage completeness: usageSeen only flips on actual numbers (null-field usage objects are not data), new optional usageMissed marks responses without usage, and /tokens becomes three-state: unknown / Reported tokens (incomplete…) / Reported tokens. No more "total" claim.
  3. Tool exports: ALL_TOOLS/getTool deleted (zero internal/test usage) — they re-exposed the shared-SubAgentTool parent-stealing bug to library consumers. Factory only.
  4. Stale tests: npm test now cleans first; clean is a portable node -e rm. Verified with a planted ghost test in dist/ — it no longer executes.

56/56 tests pass.

Four follow-ups from the round-4 review of the merged fixes:

- Calibration: measure() is now messageEstimate x ratio + fixedEstimate
  (and observe derives the ratio from real minus fixed). The blended
  formula multiplied the English fixed overhead by a CJK conversation's
  rate, overstating a compressed history by up to 2x the overhead
  (simulated: 565 vs a true ~232). Adds the mixed-rate regression test
  the old suite lacked.
- Usage honesty: usageSeen only flips on actual numbers (a usage object
  with null fields is not data), and a new optional usageMissed flags
  responses that carried none, so /tokens now has three states:
  unknown / reported-but-incomplete / reported. The label says
  'Reported tokens' — no more claiming a complete total.
- Tool exports: drop ALL_TOOLS and getTool (zero internal or test
  usage); the shared SubAgentTool instance they exposed reintroduced
  the cross-agent parent-stealing bug for library consumers. Only the
  makeAllTools factory remains.
- npm test: clean runs first (portable node -e rm, no more rm -rf), so
  compiled remnants of deleted tests can no longer execute. Verified by
  planting a failing ghost test in dist/ — it no longer runs.
Replace the ratio-calibration subsystem with a stateless heuristic and
a strict two-lane rule — the estimate drives pre-call decisions and is
never calibrated; provider usage is post-hoc accounting and never
feeds the estimate.

- approxTokens = ceil(utf8Bytes / 3): ASCII lands at ~3 chars/token,
  CJK at ~1 char/token (3 bytes/char), and byte counting sidesteps
  UTF-16 surrogate pitfalls — no Unicode tables, no state.
- delete ratio and observe() (and the agent call site); measure() is
  the plain estimate plus the fixed prompt/tool-schema overhead.
- usage accumulates only when a chunk carries BOTH prompt and
  completion numbers; the incomplete mark now lands in a finally, so
  an aborted or torn-down stream flags the totals too (post-loop code
  never runs on teardown).
- /tokens: 'Reported tokens: P / C (incomplete)' — three states,
  no total claim.
- README: the three calibration passages now describe the byte
  estimate and the two-lane rule.
- tests: calibration cases replaced by exact deterministic byte-math
  assertions; new partial-usage and aborted-stream-incomplete cases.
@nullcache nullcache changed the title Fix calibration math, usage completeness, tool exports, and stale test runs Shrink token accounting: static UTF-8-byte estimate, complete-usage-only bookkeeping, tool exports, stale tests Aug 17, 2026
@nullcache

Copy link
Copy Markdown
Owner Author

Reworked per the final adjudication — this round deletes the problem source instead of patching it:

  • Calibration is gone (ratio, observe(), the agent call site, its tests). The estimate is now ceil(utf8Bytes/3): ASCII ≈ 3 chars/token, CJK ≈ 1 char/token — the Chinese-underestimate problem the ratio existed to solve is handled by byte counting, statelessly.
  • Usage is accounting-only: accumulates only when a chunk carries both numbers; the incomplete mark lands in a finally so aborted/torn-down streams flag it too (the old post-loop assignment never ran on teardown — good catch). /tokens: Reported tokens: P / C (incomplete).
  • Tool-export deletion and clean-before-test kept from the previous revision.
  • Estimate tests are now exact equalities (1, 2, 10, 3, 2) — no ranges, nothing to calibrate.

54/54 pass. Net vs min: the whole PR now removes more than it adds.

A stream could deliver the complete usage chunk and then die before
[DONE]: the finally saw complete numbers (so no incomplete mark) while
the accumulation after the loop was skipped by the teardown — a ledger
claiming completeness with the numbers missing. Accumulation now lives
in the finally: book the numbers when complete, mark incomplete when
not, one lifecycle event either way.

Also: agent.ts comment no longer mentions calibration, and the test
file is renamed context-estimate.test.ts to match what it tests.
@nullcache

Copy link
Copy Markdown
Owner Author

Blocker fixed exactly as specified: accumulation moved into the same finally that marks completeness — book when complete, mark incomplete when not, one lifecycle event either way. New regression test replays the timeline (complete usage chunk → abort before [DONE]) and asserts 5/2 booked with seen: true, missed: false. Both non-blocking cleanups done too (agent.ts comment, context-estimate.test.ts rename — safe now that clean runs before every build). 55/55 pass.

@nullcache
nullcache merged commit e60d0a9 into min Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant