fix(session_search): scope recall by chat origin - #70105
Conversation
|
On the scope-policy question: I opened both #59295 and this PR, and this PR is the successor. #59295 narrows only the default-discovery path with a tool-local post-search filter; this PR scopes recall by stored chat-origin metadata, fails closed in group-like contexts, and keeps explicit cross-conversation recall via |
04cd44e to
c1fa65e
Compare
|
Refreshed this branch onto current Local verification:
|
teknium1
left a comment
There was a problem hiding this comment.
Thanks for carrying origin metadata through the result shape and pushing the initial filter into the database query.
Problems
tools/session_search_tool.py:272-276definessame_originas onlysource + chat_id. That crosses distinct forum topics and the default per-user group boundary:gateway/config.py:918defaultsgroup_sessions_per_user=True, andgateway/session.py:1053-1133includes thread and participant identity in live routing.- The same incomplete predicate is used for browse at
tools/session_search_tool.py:520-521and discovery at:816-830, so affected foreign sessions can be returned as local history. - Current main moved the search implementation to
hermes_state_search.pyin21c7ae8563; the required predicate must cover its FTS, CJK, trigram, LIKE, and unindexed-gap paths.website/docs/user-guide/sessions.md:549also still says search spans all past conversations.
Suggested changes
- Scope against the persisted live routing identity (or an exactly equivalent source/chat/thread/user predicate) across every retrieval path, and add topic and per-user isolation regressions.
- Update the public session-search documentation with the scoped default and explicit global escape hatch.
Automated hermes-sweeper review.
| origin = _origin_from_meta(meta) | ||
| same_origin = None | ||
| if current_origin and current_origin.get("chat_id"): | ||
| same_chat = bool(meta.get("chat_id") and meta.get("chat_id") == current_origin.get("chat_id")) |
There was a problem hiding this comment.
same_origin only compares chat_id and source. Gateway routing distinguishes thread_id and, for ordinary groups by default, the participant (gateway/session.py:1053-1133); this labels those foreign sessions as local and bypasses the intended fail-closed boundary.
| exclude_sources=list(_HIDDEN_SESSION_SOURCES), | ||
| limit=_DISCOVER_SCAN_LIMIT, # widen so dedup-by-lineage can find | ||
| # distinct sessions AND so interactive matches buried under a wall | ||
| # of cron rows are still in hand for the demotion pass below. | ||
| offset=0, | ||
| sort=sort, | ||
| chat_id=scoped_chat_id, |
There was a problem hiding this comment.
Filtering only on chat_id and source leaves same-chat forum topics and per-user group sessions in scope. Please use the persisted routing identity, or an equivalent predicate including thread and the applicable participant boundary, across every search backend.
c1fa65e to
fffa5bc
Compare
|
Rebased this onto current The refresh keeps main's current Verification run on the refreshed head: |
fffa5bc to
23b268b
Compare
|
Refreshed this branch onto current The chat-scoped search path now prefers the stored Focused verification passed locally: |
4d6727f to
0a93400
Compare
Summary
session_searchresults so recall hits identify the conversation they came from.scope="all".same_origin,origin, andrecall_scopecounts.gateway_session_keythrough background turns and update English and zh-Hans session-search docs.Why
A fresh or compacted gateway session can receive an ambiguous follow-up after its local context has expired. Before this change,
session_searchsearched the whole profile by default and returned matching sessions without enough visible room provenance. That made a hit from another conversation easy to treat as if it came from the active chat.The session table already stores routing provenance such as
source,chat_id,chat_type,display_name, andsession_key. This PR carries that metadata through the search layer and uses it to make the safer default enforceable in SQL instead of relying only on prompt guidance.When same-chat origin cannot be established,
scope="chat"fails closed. Legacy rows withoutchat_idorsession_keyare excluded fromscope="chat", counted inrecall_scope, and remain reachable through explicitscope="all".Testing
python3 -m pytest tests/tools/test_session_search.py -q53 passed in 2.33suv run --with pytest --with pytest-asyncio --with python-dotenv python -m pytest tests/tools/test_session_search.py tests/test_hermes_state.py tests/agent/test_prompt_builder.py tests/gateway/test_background_command.py -q --maxfail=1345 passed in 13.65spython3 -m py_compile tools/session_search_tool.py hermes_state.py hermes_state_search.py agent/tool_executor.py agent/agent_runtime_helpers.py gateway/run.py tests/tools/test_session_search.py tests/gateway/test_background_command.pygit diff --checkOverlap check
This refresh keeps this PR as the implementation vehicle and folds in runtime pieces that were missing relative to current
main. #59295 covers a narrower default-scoping approach and is superseded by this PR's broader search-path coverage, provenance labels, docs, and tests. The schema-backed scope-key design in #50030 remains the main open alternative; if maintainers prefer that direction, this PR's provenance labeling could be rebased onto it. Related prompt-only guidance (#30502) remains useful, but it does not replace code-level SQL scoping and visible provenance insession_searchitself.Notes
scope="chat"; CLI and no-origin contexts continue to default to global recall.profile=recall deliberately defaults to global scope rather than applying the current gateway room to a different profile database.scope="all"; the safety improvement is scoped defaults plus visible provenance, not a hard access-control boundary.gateway_session_keyhandoff seam rather than by a full end-to-end gateway test.