Fix thread scope propagation in AtomicMemory provider#2
Merged
Conversation
## Summary Thread scope (`Scope.thread`) was silently dropped before reaching the Core API. Ingest, search, and list operations never forwarded `session_id` on the wire, so thread-scoped reads returned unfiltered results and thread-scoped writes stored memories without a session association. This fix propagates `thread` as `session_id` on every route that Core honors it, strips it from routes that do not (get, delete, expand), and validates that responses echo back a matching `session_id` for thread-scoped requests. ## Changes - Add `thread: str | None` field to `UserScope` and `WorkspaceScope`. - Add `include_thread` flag to `scope_to_fields` and `scope_to_query_pairs`; emit `session_id` when opted in. - Add `strip_read_filters` to `scope_mapper` — drops both `agent_scope` and `thread` for routes (get, delete, expand) that do not accept those filters. - Fix `strip_agent_scope` to preserve `thread` when clearing workspace filter. - Propagate `session_id` on ingest and search request bodies in `provider.py` / `async_provider.py`. - Extract `_build_list_path` helper that appends `session_id` to the list query string when thread is set; use it in both sync and async providers. - Use `include_thread=True` in `scope_to_fields` / `scope_to_query_pairs` calls for ingest, search, and list in `handle_impl.py` and `async_handle_impl.py`. - Add `_build_memory_scope` in `handle_impl.py` and `_build_scope` in `mappers.py`: project `session_id` from the backend response back into the returned scope, and raise `ValueError` when a thread-scoped request receives a response without a matching `session_id`. - Add comprehensive test coverage across `test_provider.py`, `test_async_provider.py`, `test_handle_base.py`, `test_mappers.py`, and `test_scope_mapper.py` for all affected operations. ## Why The Core API uses `session_id` to partition memories by conversation thread. Without forwarding `thread` as `session_id`, thread-scoped ingests write memories outside any session, thread-scoped searches return the full unfiltered memory set, and there is no way to verify the backend actually applied the requested scope. The validation step on responses closes a silent correctness gap where a mismatch between requested and returned session could go undetected. ## Validation All new tests are exercised via `respx` mocks against the full HTTP path. Tests cover: - `session_id` present in request body/query for ingest, search, list, package, and `search_as_of`. - `session_id` absent from get and delete query strings (routes that do not filter by thread). - Expand strips `thread` from returned memory scope. - Response mapper raises on a missing or mismatched `session_id` when thread scope was requested. - `strip_read_filters` and `strip_agent_scope` preserve or clear `thread` as expected.
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
Thread scope (
Scope.thread) was silently dropped before reaching the Core API. Ingest, search, and list operations never forwardedsession_idon the wire, so thread-scoped reads returned unfiltered results and thread-scoped writes stored memories without a session association. This fix propagatesthreadassession_idon every route that Core honors it, strips it from routes that do not (get, delete, expand), and validates that responses echo back a matchingsession_idfor thread-scoped requests.Changes
thread: str | Nonefield toUserScopeandWorkspaceScope.include_threadflag toscope_to_fieldsandscope_to_query_pairs; emitsession_idwhen opted in.strip_read_filterstoscope_mapper— drops bothagent_scopeandthreadfor routes (get, delete, expand) that do not accept those filters.strip_agent_scopeto preservethreadwhen clearing workspace filter.session_idon ingest and search request bodies inprovider.py/async_provider.py._build_list_pathhelper that appendssession_idto the list query string when thread is set; use it in both sync and async providers.include_thread=Trueinscope_to_fields/scope_to_query_pairscalls for ingest, search, and list inhandle_impl.pyandasync_handle_impl.py._build_memory_scopeinhandle_impl.pyand_build_scopeinmappers.py: projectsession_idfrom the backend response back into the returned scope, and raiseValueErrorwhen a thread-scoped request receives a response without a matchingsession_id.test_provider.py,test_async_provider.py,test_handle_base.py,test_mappers.py, andtest_scope_mapper.pyfor all affected operations.Why
The Core API uses
session_idto partition memories by conversation thread. Without forwardingthreadassession_id, thread-scoped ingests write memories outside any session, thread-scoped searches return the full unfiltered memory set, and there is no way to verify the backend actually applied the requested scope. The validation step on responses closes a silent correctness gap where a mismatch between requested and returned session could go undetected.Validation
All new tests are exercised via
respxmocks against the full HTTP path. Tests cover:session_idpresent in request body/query for ingest, search, list, package, andsearch_as_of.session_idabsent from get and delete query strings (routes that do not filter by thread).threadfrom returned memory scope.session_idwhen thread scope was requested.strip_read_filtersandstrip_agent_scopepreserve or clearthreadas expected.