Skip to content

Fix thread scope propagation in AtomicMemory provider#2

Merged
ethanj merged 1 commit into
mainfrom
sync/python-42211c1
May 17, 2026
Merged

Fix thread scope propagation in AtomicMemory provider#2
ethanj merged 1 commit into
mainfrom
sync/python-42211c1

Conversation

@ethanj
Copy link
Copy Markdown
Contributor

@ethanj ethanj commented May 17, 2026

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.

## 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.
@ethanj ethanj merged commit 346f6ff into main May 17, 2026
4 checks passed
@ethanj ethanj deleted the sync/python-42211c1 branch May 17, 2026 08:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant