Bug: Perf/message token cache - #901
Open
addyCooks wants to merge 2 commits into
Open
Conversation
addyCooks
requested review from
Avtrkrb,
akramcodez and
will-lamerton
as code owners
August 18, 2026 19:50
getMessageTokens rebuilt the entire cache on every miss: it allocated a fresh BoundedMap, copied up to 1000 entries into it (each copy also allocating a new entry wrapper), and stored the result back through setMessageTokenCache. Every uncached message therefore cost a full copy of the cache and turned all of its previous entries into garbage. Because that copy landed in state it also re-rendered the whole app and handed getMessageTokens a new identity, invalidating the memos and effects keyed on it (context percentage, usage display). And since the write was deferred to a microtask, repeated calls for the same message inside one render pass all missed, each queueing another full copy. Hold the cache in a ref and write the entry straight into it. The map instance stays stable for the session, a miss costs one Map.set, the value is visible to the very next lookup, and a cache write no longer reaches React — which is also what makes the queueMicrotask deferral (added to avoid updating state during render) unnecessary. Eviction is unchanged: BoundedMap already drops the oldest entry in O(1) using Map insertion order. setMessageTokenCache is dropped from the returned setters, nothing consumed it. Closes Nano-Collective#894.
addyCooks
force-pushed
the
perf/message-token-cache
branch
from
August 20, 2026 21:42
eecf88a to
c9f2932
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Closes #894.
getMessageTokensrebuilt the entire cache on every miss: it allocated a freshBoundedMap, copied up to 1000 entries into it (each copy also allocating a new entry wrapper), and stored the result back throughsetMessageTokenCache. Every uncached message cost a full copy of the cache and turned all of its previous entries into garbage.Because that copy landed in state, it also re-rendered the whole app and handed
getMessageTokensa new identity, invalidating the memos and effects keyed on it (context percentage, usage display). And since the write was deferred to a microtask, repeated calls for the same message inside a single render pass all missedusage-displaywalks every message twice per render each queueing another full copy.This holds the cache in a ref and writes the entry straight into it. The map instance stays stable for the session, a miss costs one
Map.set, the value is visible to the very next lookup, and a cache write no longer reaches React which is what makes thequeueMicrotaskdeferral (added to avoid updating state during render) unnecessary.Eviction is deliberately unchanged:
BoundedMapalready drops the oldest entry in O(1) using Map insertion order, so the insertion counter suggested in the issue would add bookkeeping without a win.setMessageTokenCacheis dropped from the returned setters nothing consumed it.messageTokenCachestays exposed.Type of Change
Changeset
pnpm changeset) describing this change for the changelogTesting
Automated Tests
.spec.ts/tsxfilespnpm test:allcompletes successfully) — see note belowManual Testing
Checklist