-
Notifications
You must be signed in to change notification settings - Fork 3k
fix(memory): improve recall reliability and candidate coverage #8716
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
aa0bc54
fix(memory): improve recall delivery and multilingual fallback
yiliang114 ec0c746
fix(memory): bound heuristic recall scoring
yiliang114 143a372
test(memory): pin initial recall budget with fake timers
yiliang114 7a079d1
test(memory): pin recall budget and scoring contracts
yiliang114 c9bec59
fix(memory): preserve recall field weighting
yiliang114 bbd040d
fix(memory): recall relevant topics beyond scan cap (#8803)
yiliang114 d8b4e2b
fix(memory): address recall review feedback
yiliang114 da48034
test(memory): measure recall rollout gate against the pre-change scorer
c50c69d
fix(memory): deliver a deterministic fast recall result on the initia…
30dda5e
docs(memory): record the fast-path decision and phase/strategy split
4563b19
test(memory): report the mixed-language slice in the rollout gate
d6aa8c5
docs(memory): align recall docs on the deterministic fast path
4cad8a3
Merge branch 'main' into codex/7040-memory-recall
wenshao 823ac2a
fix(memory): use Array<T> for the fast-path test doc lists
5d3f9b9
docs(memory): clarify recall delivery telemetry
yiliang114 9adc612
fix(memory): report already-delivered recall count
yiliang114 ef4c5fa
Merge remote-tracking branch 'origin/main' into HEAD
yiliang114 6085ef0
docs(memory): align recall delivery claims
yiliang114 57281e8
Merge branch 'main' into codex/7040-memory-recall
yiliang114 8a5a524
fix(memory): rank ties by recency and record fast-delivered discards
yiliang114 d2598bd
docs(memory): state the candidate-cap trade and the per-turn document…
yiliang114 01ef7d7
fix(memory): end the initial recall wait on the fast result, widen to…
yiliang114 db006f8
docs(memory): correct the initial-turn preference claim, pin it with …
yiliang114 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
271 changes: 271 additions & 0 deletions
271
docs/design/2026-08-08-native-memory-recall-reliability.md
Large diffs are not rendered by default.
Oops, something went wrong.
116 changes: 116 additions & 0 deletions
116
docs/design/2026-08-09-bounded-memory-recall-candidates.md
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| # Bounded Memory Recall Candidates | ||
|
|
||
| ## Problem | ||
|
|
||
| The project and user memory scanners enumerate, read, and parse every topic, | ||
| then return only the 200 most recent documents. Recall uses those shared scanner | ||
| APIs, so an older relevant document outside either 200-document window cannot | ||
| reach the heuristic or model selector even though the expensive scan work has | ||
| already happened. The truncation key is recency, applied per scope and before | ||
| anything has looked at the query. | ||
|
|
||
| The same capped APIs are also used by Forget, Indexer, Status, and Extraction. | ||
| Removing their limit globally would widen unrelated behavior. | ||
|
|
||
| ## Decision | ||
|
|
||
| Keep the existing scanner APIs and their 200-document limit unchanged. Add | ||
| explicit all-topic variants used only by recall. | ||
|
|
||
| Recall ranks the combined project and user pool before model selection: | ||
|
|
||
| - retain up to 180 documents with a lexical match using the existing scorer; | ||
| - fill the remaining candidate slots by recency, preserving at least 20 recent | ||
| opportunities when enough lexical matches exist; | ||
| - interleave recent opportunities with lexical candidates so the manifest byte | ||
| budget cannot systematically exclude the entire recent reserve; | ||
| - send at most 200 candidates to the model selector; | ||
| - append manifest entries only while their cumulative UTF-8 size remains at or | ||
| below 25,000 bytes; | ||
| - validate selector output only against documents actually present in that | ||
| bounded manifest. | ||
|
|
||
| The heuristic fallback continues to score the complete recall pool and still | ||
| returns at most five documents. Existing body and prompt limits remain | ||
| unchanged. | ||
|
|
||
| ### This is a change of truncation key, not a lifted ceiling | ||
|
|
||
| "Removes the 200-document cap" is the wrong summary, and reviewers should | ||
| read the effect per pool size rather than as a uniform widening. What the | ||
| change actually does is replace a per-scope, query-blind recency truncation | ||
| with a global, query-aware one: | ||
|
|
||
| - **Pool at or under 200 documents.** No document is excluded by count under | ||
| either design, but the 25,000-byte manifest budget is a ceiling the old path | ||
| did not have, and it binds far earlier than the document count suggests | ||
| (see below). The interleaving above exists so that truncation cannot fall | ||
| entirely on the recent reserve. | ||
| - **Pool between 200 and 400 documents, neither scope over 200.** The old | ||
| path sent every document to the selector — up to 200 project plus 200 user. | ||
| The new path sends at most 200, and in practice the byte budget cuts it | ||
| further: a measured run with 150 project plus 150 user documents sent 300 | ||
| manifest lines under the old path and **94** under the new one. The | ||
| candidates that survive are chosen by lexical relevance plus a recency | ||
| reserve rather than by recency alone, which is the intended trade, but the | ||
| reduction is larger than the document cap implies. | ||
| - **Either scope over 200 documents.** This is the case the change is for. | ||
| An old, lexically matching document that the recency cap made permanently | ||
| invisible can now be selected. Measured: with 251 documents in one scope and | ||
| the only lexical match the oldest, the old path produced a 200-line manifest | ||
| without the target; the new path produced a 96-line manifest with the target | ||
| first. | ||
|
|
||
| ### The binding constraint is `MAX_MODEL_MANIFEST_BYTES`, not the document cap | ||
|
|
||
| `MAX_MODEL_CANDIDATE_DOCS = 200` reads like the limit but rarely is one. Each | ||
| manifest line carries an absolute file path and an ISO-8601 timestamp before | ||
| the description, so its fixed overhead is on the order of 150–250 bytes for an | ||
| ordinary project path. Against a 25,000-byte budget that binds somewhere around | ||
| 90–150 documents, which is why both measurements above land in the nineties | ||
| rather than at 200. | ||
|
|
||
| Two things follow. Deployments should read the byte budget, not the document | ||
| cap, as the real candidate ceiling. And the recency reserve only survives | ||
| truncation because it is interleaved with the lexical candidates rather than | ||
| appended after them — at a cut in the nineties, an appended reserve would be | ||
| discarded in full. | ||
|
|
||
| The manifest byte budget also packs rather than prefixes: a document whose | ||
| line does not fit is skipped and later, shorter lines are still considered. | ||
| A long-description document can therefore be dropped while a lower-ranked one | ||
| is kept. | ||
|
|
||
| Forget, Indexer, Status, and Extraction keep the capped scanner. That preserves | ||
| their current behavior but means an older document can become recallable before | ||
| it becomes manageable by those non-recall flows. | ||
|
|
||
| ## Failure and compatibility boundaries | ||
|
|
||
| Project scanning remains required. User scanning remains best-effort. Invalid | ||
| or unreadable files keep the existing skip behavior. Empty candidate manifests | ||
| return no model selection rather than sending an unbounded request. | ||
|
|
||
| There is no public setting, persistent index, new dependency, provider API, or | ||
| second selection pathway. Each recall enumerates, reads, and parses the full | ||
| project and user memory trees once, then performs O(n) local ranking and | ||
| active-tool filtering over the parsed documents. The deterministic fast path | ||
| described in `2026-08-08-native-memory-recall-reliability.md` reuses the | ||
| candidates produced by that single pass, so it adds no scan, no ranking work, | ||
| and no state machine — only an earlier delivery point for results already | ||
| computed. The model candidate count and manifest are | ||
| bounded, but the local I/O and filtering work grow with the memory tree; a | ||
| persistent catalog requires separate measurement and evidence. | ||
|
|
||
| ## Verification | ||
|
|
||
| - A deliberately old relevant topic beyond the regular 200-document result is | ||
| recalled from a real temporary memory tree. | ||
| - The regular scanner still returns 200 documents and omits that topic. | ||
| - The model candidate set contains the lexical target and recent reserve while | ||
| remaining at 200 documents. | ||
| - A manifest built from large multibyte descriptions stays within 25,000 UTF-8 | ||
| bytes. | ||
| - A real temporary memory-tree integration test verifies overflow-topic recall; | ||
| client tests independently verify bounded initial waiting and later | ||
| ToolResult delivery. |
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
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.