fix(recall): scope L1 search by session_key for agent/user isolation#134
Open
YOMXXX wants to merge 1 commit into
Open
fix(recall): scope L1 search by session_key for agent/user isolation#134YOMXXX wants to merge 1 commit into
YOMXXX wants to merge 1 commit into
Conversation
`searchMemories` previously discarded the caller's `sessionKey` before hitting the L1 store, so: - auto-recall (`before_prompt_build` → `performAutoRecall`) merged every session's L1 records into one global pool. In a multi-agent setup (main, coder, omoc_coder, …) recall for one agent injected the other agents' memories; with multiple users on the same backend the same cross-contamination happens across users. - the `tdai_memory_search` tool had no way to scope its results, while the sibling `tdai_conversation_search` already accepted `session_key`. Wire `sessionKey` all the way through: - src/core/hooks/auto-recall.ts: thread `sessionKey` from `performAutoRecallInner` into `searchMemories` and the four strategy branches (`searchByKeyword`, `searchByEmbedding`, `searchHybrid`, native-hybrid). Add an `applySessionFilter` helper that drops raw FTS / vector results whose `session_key` does not match. Each strategy now over-retrieves (×4 for single-strategy, ×5 for hybrid RRF) when filtering so the post-filter pool can still fill `maxResults`. `l1_fts.session_key` is UNINDEXED so server-side WHERE is not possible — client-side filter is the only safe path. - src/core/tools/memory-search.ts: add `sessionKey?` to the `executeMemorySearch` params and a `session_key` field to `MemorySearchResultItem` so FTS / vector items carry the key through RRF merge. Apply the session filter alongside `type` and `scene` filters, and over-retrieve candidates when scoped. - src/core/types.ts: add `sessionKey?: string` to `MemorySearchParams`. - src/core/tdai-core.ts: forward `params.sessionKey` into `executeMemorySearch`. - index.ts: expose `session_key` as an optional parameter on the `tdai_memory_search` tool, mirroring the existing `tdai_conversation_search` shape so agents can opt into scoping. The auto-recall path is now strictly isolated by `sessionKey` — the key was already non-empty by the time `handleBeforeRecall` ran (empty keys are skipped earlier in index.ts), so this is the right default for the recall hook. The tool keeps `session_key` optional because an agent may legitimately want to recall across sessions on demand. Fixes TencentCloud#111 Signed-off-by: 李冠辰 <liguanchen@xiaomi.com>
Collaborator
|
Thanks for the contribution! We've received your PR fixing the cross-session memory pollution bug and will review it internally. We'll get back to you once we have results. Appreciate your help in improving multi-agent isolation! |
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.
Summary
Fixes #111 —
searchMemorieswas performing global L1 searches across every capturedsession_key, so in a multi-agent or multi-user setup the auto-recall hook pulled the wrong agent's / wrong user's memories into the prompt. The OP filed it as a serious cross-contamination bug with a clean root-cause analysis; this PR implements their proposed fix.Root cause (reproduced from the issue)
handleBeforeRecall(userText, sessionKey)→performAutoRecall({...sessionKey})→performAutoRecallInner(params)already carriedsessionKey, but the next callsearchMemories(userText, …)dropped it. From that point on:searchByKeywordranvectorStore.searchL1Fts(ftsQuery, maxResults * 2)with no scoping.searchByEmbeddingranvectorStore.searchL1Vector(queryEmbedding, maxResults * 2)with no scoping.searchHybriddid the same in parallel and RRF-merged.executeMemorySearchhad nosessionKeyparameter at all, while the siblingexecuteConversationSearchalready had one.l1_fts.session_keyisUNINDEXEDso server-sideWHEREis not an option — client-side filter on the rawL1SearchResult/L1FtsResult(both already carrysession_key) is the only safe path.Change
src/core/hooks/auto-recall.ts— threadsessionKeyfromperformAutoRecallInnerintosearchMemoriesand the four strategy branches (searchByKeyword,searchByEmbedding,searchHybrid, native-hybrid). AddapplySessionFilterhelper that drops raw FTS / vector results whosesession_key !== sessionKey. Each strategy now over-retrieves (×4 single, ×5 hybrid RRF) when scoped so the post-filter pool can still fillmaxResults.src/core/tools/memory-search.ts— addsessionKey?toexecuteMemorySearchparams; addsession_keytoMemorySearchResultItemso RRF-merged items carry the key. Apply the session filter alongside the existingtype/scenefilters; over-retrieve when scoped.src/core/types.ts— addsessionKey?: stringtoMemorySearchParams.src/core/tdai-core.ts— forwardparams.sessionKeyintoexecuteMemorySearch.index.ts— exposesession_keyas an optional parameter ontdai_memory_search, mirroringtdai_conversation_search.Semantics
sessionKey. The hook is already short-circuited upstream whensessionKeyis empty (seeresolveSessionKeyinindex.ts), so non-empty + strict-match is the right default.tdai_memory_searchtool:session_keyis optional. Agents that want cross-session recall ("summarize everything you know about the user") can still omit it; agents that want isolation pass it through. Same shape astdai_conversation_searchto keep the tool surface consistent.What's intentionally NOT in this PR
l1_fts.session_keystaysUNINDEXED; client-side filter is fine for the recall budget (maxResultsis single digits) and the over-retrieve multiplier compensates for the post-filter drop. If profiling later shows the multiplier is hurting BM25 quality, we can add an indexed mirror or a composite FTS index in a follow-up.session_keyis already correctly stamped at write time..test.tsfiles (verified viagit ls-files), so a one-off test for this PR wouldn't run anywhere. Verification is by hand below.Test plan
npm run buildpasses locally (tsdown clean).mainagent andcoderagent under the same user, or two users under the same host):<relevant-memories>(and the[session-filter:*]debug log shows the drop count)tdai_memory_searchcalled withoutsession_keystill returns the global pool (no regression for the cross-session use case).tdai_memory_searchcalled withsession_key=<current>filters correctly.nativeHybridSearch=true) still works — verified that we now over-retrieve and post-filter rather than re-issue the request.