Affected Component
statewave-grounded-shop-assistant — packages/statewave-core/src/index.ts, StatewaveStore.getContext
Bug Description
In the round-robin budget loop, the first memory that does not fit ends that subject's contribution entirely:
if (bucket.used + cost > bucket.budget) {
bucket.cursor = bucket.memories.length; // subject budget exhausted
continue;
}
The budget is not exhausted — one candidate was too big for what remains. Every lower-ranked memory in that subject is then discarded, including ones that would have fit comfortably.
Concretely, with a 2000-token global budget over two subjects (1000 each): if a subject's rank-2 memory is a 900-token page and 40 tokens are already used, that page is skipped and so are ranks 3..n, even though each is 30 tokens and ~960 tokens of budget are still free. The subject contributes one memory instead of a dozen.
That matters more here than in a generic retriever: this demo's whole premise is that answers are grounded in retrieved evidence and it says "I don't know" when evidence is missing. Dropping evidence that fits turns answerable questions into refusals, and it does so on exactly the long, detailed source documents that are most likely to be authoritative.
Expected Behavior
Skip the oversized candidate and keep filling from the same subject — advance the cursor and continue, rather than jumping it to the end. A subject should stop contributing when nothing left fits, not when the first thing doesn't.
Steps to Reproduce
Ingest one long episode and several short ones under the same subject, rank the long one second for some query, then call getContext with a budget that fits the short ones but not the long one. Only the rank-1 memory comes back.
Environment
main as of 2026-09-07, Node 18+.
Severity
Medium — silently changes what the assistant can answer, with no signal that evidence was dropped.
Additional Context
Related nit in the same function: estimateTokens costs only candidate.text, while scoreMemory ranks on text plus JSON.stringify(metadata). Metadata that reaches the prompt is unbudgeted.
Affected Component
statewave-grounded-shop-assistant—packages/statewave-core/src/index.ts,StatewaveStore.getContextBug Description
In the round-robin budget loop, the first memory that does not fit ends that subject's contribution entirely:
The budget is not exhausted — one candidate was too big for what remains. Every lower-ranked memory in that subject is then discarded, including ones that would have fit comfortably.
Concretely, with a 2000-token global budget over two subjects (1000 each): if a subject's rank-2 memory is a 900-token page and 40 tokens are already used, that page is skipped and so are ranks 3..n, even though each is 30 tokens and ~960 tokens of budget are still free. The subject contributes one memory instead of a dozen.
That matters more here than in a generic retriever: this demo's whole premise is that answers are grounded in retrieved evidence and it says "I don't know" when evidence is missing. Dropping evidence that fits turns answerable questions into refusals, and it does so on exactly the long, detailed source documents that are most likely to be authoritative.
Expected Behavior
Skip the oversized candidate and keep filling from the same subject — advance the cursor and continue, rather than jumping it to the end. A subject should stop contributing when nothing left fits, not when the first thing doesn't.
Steps to Reproduce
Ingest one long episode and several short ones under the same subject, rank the long one second for some query, then call
getContextwith a budget that fits the short ones but not the long one. Only the rank-1 memory comes back.Environment
mainas of 2026-09-07, Node 18+.Severity
Medium — silently changes what the assistant can answer, with no signal that evidence was dropped.
Additional Context
Related nit in the same function:
estimateTokenscosts onlycandidate.text, whilescoreMemoryranks on text plusJSON.stringify(metadata). Metadata that reaches the prompt is unbudgeted.